QML ItemSelectionModel does not select? - c++

I have a TreeView of a QFileSystemModel and an ItemSelectionModel:
ItemSelectionModel {
id: sel
model: fileSystemModel
onSelectedIndexesChanged: {
model.setSelected(selectedIndexes)
}
}
TreeView {
id: view
anchors.fill: parent
sortIndicatorVisible: true
model: fileSystemModel
rootIndex: rootPathIndex
selection: sel
selectionMode: 2
TableViewColumn {
id: namecolumn
title: "Name"
role: "fileName"
resizable: true
width: parent.width-sizeWidth-dateWidth-scrollBarWidth
delegate: mycomp
}
... other columns
}
When I sort the TreeView for the name or size of a file, the selection does not get updated. Therefore, in my fileSystemModel (derived from QFileSystemModel) I created a Q_INVOKABLE function which should store the selected indices:
Q_INVOKABLE void setSelected(QModelIndexList list) {
_selectedIndices.clear();
for(int i=0;i<list.count();i++) {
_selectedIndices.append(QPersistentModelIndex(list[i]));
}
}
However, I am not sure if this would work, as I am not sure whether the ItemSelectionModel indices are the same as the model indices.
After sorting, I thought I try to set the selection via retrieving this list, on the c++ side:
Q_INVOKABLE QVariantList getSelected() {
return static_cast<QVariantList>(_selectedIndices);
}
and in QML
onSortIndicatorOrderChanged: {
var list = model.getSelected())
console.log('new selection:')
for(var idx in list) {
console.log(list[idx])
selection.select(list[idx],ItemSelectionModel.Select)
console.log('now selected')
console.log(selection.selectedIndexes)
}
}
The debug output in onSortIndicatorChanged shows
qml: new selection:
qml: QPersistentModelIndex(5,0,0x1e2ad30,DisplayFileSystemModel(0x1544890))
qml: now selected
qml:
sort by column 0
qml: new selection:
qml: QPersistentModelIndex(1,0,0x1e2ad30,DisplayFileSystemModel(0x1544890))
qml: now selected
qml:
if I toggle the sortIndicator. So it seems that the row of the QPersistentModelIndex gets updated correctly. However, selection.selectedIndexes is empty and no selection is shown. What am I missing?
A different way (maybe better) would be to access the QItemSelectionModel directly in c++, but I don't know how I would access that (without searching for its object name, which hurts reusability).

Related

How to access nested QStandardItemModel's items from QML?

Background
I have a tree-like QStandardItemModel, whose items I would like to access from QML.
Here is how the model is defined on the C++ side:
backend.h
class Backend : public QObject
{
Q_OBJECT
Q_PROPERTY(QStandardItemModel *model READ model CONSTANT)
public:
explicit Backend(QObject *parent = nullptr);
QStandardItemModel *model() const;
private:
QStandardItemModel *m_model;
};
backend.cpp
Backend::Backend(QObject *parent) :
QObject(parent),
m_model(new QStandardItemModel(this))
{
auto *itemFirst = new QStandardItem(tr("First"));
auto *itemSecond = new QStandardItem(tr("Second"));
auto *subItem = new QStandardItem(tr("First_02"));
subItem->appendRow(new QStandardItem("First_02_01"));
itemFirst->appendRow(new QStandardItem(tr("First_01")));
itemFirst->appendRow(subItem);
itemFirst->appendRow(new QStandardItem(tr("First_03")));
itemSecond->appendRow(new QStandardItem(tr("Second_00")));
itemSecond->appendRow(new QStandardItem(tr("Second_01")));
m_model->appendRow(itemFirst);
m_model->appendRow(itemSecond);
}
QStandardItemModel *Backend::model() const
{
return m_model;
}
The model is exported to QML in main.cpp like this:
Backend backend;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("backend", &backend);
qmlRegisterUncreatableType<QStandardItemModel>("QStandardItemModel", 1, 0, "QStandardItemModel", "The model should be created in C++");
Using TreeView from QtQuick.Controls 1.4 in main.qml like this:
TreeView {
anchors.fill: parent
model: backend.model
TableViewColumn {
title: "Name"
role: "display"
}
}
I get the desired results, i.e. all items nested correctly:
Problem
When I try to manually iterate over the nested items using Repeater and DelegateModel like this:
ColumnLayout {
anchors.fill: parent
Repeater {
model: backend.model
Item {
Layout.fillWidth: true
Layout.fillHeight: true
ColumnLayout {
anchors.fill: parent
Text {
color: "blue"
text: model.display
}
Repeater {
model: DelegateModel {
model: backend.model
rootIndex: modelIndex(index)
Item {
Layout.fillWidth: true
Layout.fillHeight: true
ColumnLayout {
anchors.fill: parent
Text {
color: "green"
text: model.display
}
Repeater {
model: DelegateModel {
model: backend.model
rootIndex: modelIndex(index)
Text {
color: "red"
text: model.display
}
}
}
}
}
}
}
}
}
}
}
the main branches (marked with blue) and the items on the first nesting level (marked with green) are the right ones, but I get the wrong items on the second nesting level (marked with red):
How to fix the code to correctly iterate over the QStandardItemModel's items on each nesting level?
The problem is in these two lines: rootIndex: modelIndex(index).
index is the index of the 'parent' model, but modelIndex(...) is the method of the current model.
I've tried it with this (slightly modified) piece of code and it worked:
Repeater {
model: DelegateModel {
id: model1
model: backend.model
delegate: ColumnLayout{
Text {
text: "Data: " + display
}
Repeater {
model: DelegateModel {
id: model2
model: backend.model
// 'index' comes from 'model1', so use the 'modelIndex' method from 'model1'
rootIndex: model1.modelIndex(index)
delegate: ColumnLayout{
Text {
text: "- Data: " + display
}
Repeater {
model: DelegateModel {
id: model3
model: backend.model
// 'index' comes from 'model2', so use the 'modelIndex' method from 'model2'
rootIndex: model2.modelIndex(index)
delegate: Text {
text: "-- Data: " + display
}
}
}
}
}
}
}
}
}

QML TreeView display nodes by levels or custom delegate

I have a tree model derived from a QAbstractItemModel. And I can display the data in a tree like way.
What I want is to display teh data by the layers. To display only one level of a layer at a time AND put each layer on a stack and navigate backwards by poping the layer from the stack.
I guess I have to implement a custom delegate? Any advice would be highly appreciated. Thank you.
I recently implemented something similar, based on a QFileSystemModel, set as a qml contextProperty, named treeModel in the example below.
The idea was to keep track of the current QModelIndex, and to use the data() & rowCount() functions of the QAbstractItemModel to get the actual model data, and to use a recursive stack view for the navigation
General layout
ApplicationWindow {
id: main
visible: true
width: 640
height: 480
ColumnLayout
{
anchors.fill: parent
// Breadcrumb
SEE BELOW
// View
StackView
{
id: stackView
Layout.fillHeight: true
Layout.fillWidth: true
initialItem: TreeSlide {}
}
}
}
TreeSlide
The view itself is pretty simple. I didn't used anything fancy here, and it displays only one role, but you could extend it without trouble. Note that the view's model is NOT your treeModel, but instead just the rowCount for the rootIndex.
ListView
{
Layout.fillHeight: true
Layout.fillWidth: true
model: treeModel.rowCount(rootIndex)
clip: true
snapMode: ListView.SnapToItem
property var rootIndex
// I used a QFileSytemModel in my example, so I had to manually
// fetch data when the rootIndex changed. You may not need this though.
onRootIndexChanged: {
if(treeModel.canFetchMore(rootIndex))
treeModel.fetchMore(rootIndex)
}
Connections {
target: treeModel
onRowsInserted: {
rootIndexChanged()
}
}
delegate: ItemDelegate {
property var modelIndex: treeModel.index(index,0, rootIndex)
property bool hasChildren: treeModel.hasChildren(modelIndex)
width: parent.width
text: treeModel.data(modelIndex)
onClicked: {
if(hasChildren)
{
// Recursively add another TreeSlide, with a new rootIndex
stackView.push("TreeSlide.qml", {rootIndex: modelIndex})
}
}
}
}
Breadcrumb
To navigate the model, instead of a simple back button, I used a kind of dynamic breadcrumb
// Breadcrumb
RowLayout
{
Repeater
{
id: repeat
model: {
var res = []
var temp = stackView.currentItem.rootIndex
while(treeModel.data(temp) != undefined)
{
res.unshift(treeModel.data(temp))
temp = temp.parent
}
res.unshift('.')
return res
}
ItemDelegate
{
text : modelData
onClicked: {
goUp(repeat.count - index-1)
}
}
}
}
the goUp function simply goes up the stack by poping items
function goUp(n)
{
for(var i=0; i<n; i++)
stackView.pop()
}
To to do it completely by guides we should use DelegateModel and DelegateModel.rootIndex
DelegateModel {
id: delegateSupportPropConfigModel
model: supportModel
delegate: SupportPropConfigListItem {
id: currentItem
width: scrollRect2.width - 60
fieldName: model.fieldName
fieldValue: model.value
onClick:{
delegateSupportPropConfigModel.rootIndex = supportPropConfigModel.index(0, 0, supportPropConfigModel)
}
}
}
Column {
id: columnSettings
spacing: 2
Repeater {
model: delegateSupportPropConfigModel
}
}

Qt/QML: Putting WebEngineView in a ListView and calling loadHtml()

If I have a WebEngineView in a ListView delegate, how can I call loadHtml when the delegate is loaded? For example:
ListView
{
model: myModel
delegate: Component
{
Item:
{
WebEngineView
{
id: myWebView
text: myWebView.loadHtml(model.modelData.htmlText)
}
}
}
}
The above shows the idea of what I would like to do. Is there a signal I could hook into for each delegate Item where I could call myWebView.loadHtml()?
I do not know about a text-property of the WebEngineView but I have never used it sofar.
I think, what you want is the Component.onCompleted-handler like this:
ListView {
model: myModel
delegate: Component {
Item {
WebEngineView {
id: myWebView
Component.onCompleted: loadHtml(model.modelData.htmlText, baseURL)
}
}
}
}
I don't know about your usecase, but the loadHtml-method has a second argument for the baseURL to look for ressources such as CSS or Images, which might be needed by you.

QAbstractListModel to Combobox , How to get value a When select value b

I have made very simple QAbstractListModel example.This example also presented on this link : http://doc.qt.io/qt-5/qtquick-models-abstractitemmodel-example.html . An program result is like this:
program result
I have made a little change on it on view.qml like this
ComboBox {
id:mycombo
width: 200; height: 250
model: myModel
textrole:"type"
onCurrentTextChanged: {
console.log(mycombo.model.get(currentIndex).size);
}
}
on here , Combobox shows animals type.But I want to write 'size' value when onCurrentTextChanged event. But I can't get type value.
I think there is a failure in your cpp model implementation.
Because when i use a qml model it works as it should:
ListModel {
id: myModel
ListElement{
type: "mouse"
size: "small"
}
ListElement{
type: "cat"
size:"medium"
}
ListElement{
type: "elephant"
size:"large"
}
}
ComboBox {
id:mycombo
width: 200; height: 250
model: myModel
textRole:"type"
onCurrentTextChanged: {
console.log(mycombo.model.get(currentIndex).size);
}
}
Output:
qml: small
qml: medium
qml: large

QML ListView inside Repeater

How can I assign different models to a ListView, which is inside a Repeater?
I did a sketch (my actual project is much larger):
Column {
Repeater {
model: ["Line1","Line2","Line3","Line4"]
Rectangle {
ListView {
model: ???
delegate: Rectangle {
Text {
text: somemodelproperty
}
}
}
}
}
}
Currently, I am solving the idea by copy-pasting 10 Rectangles, each containing a ListView. In C++, I have implemented 10 QList<QObject*>, and each list is "bounded" to a ListView by doing
QQmlContext * example = engine.rootContext();
example->setContextProperty(modelname,QVariant::fromValue(listobject));
I am pretty sure there is a more intelligent way to do this, but I have just started working with QML a few days ago, and can't figure out a solution.
There is the catch, you can't use an id as a value for a list element, nor can you nest a list model inside a list element, at least not directly.
You can however populate it like this:
Item {
id: models
ListModel {/*...*/}
ListModel {/*...*/}
//...
}
ListModel {
id: outerModel
}
function set() {
for (var i = 0; i < models.data.length; ++i) {
outerModel.append({"model": models.data[i] /* and other stuff */})
}
}
Or if you prefer using your C++ data, you can add a QObject* "model" property to each of the elements in the list and set it with a function, either as in the example above or with the ids for the inner models you specify.
On a second thought, you might want to use another name instead of "model" because I can't imagine QML will be happy about something like model: model
Update: You could try doing something like this (assuming your 10 models are exposed as m1 - m10 in QML)
property var subModels: [m1, m2, ... m10]
Then for the ListView inside the repeater delegate you can:
ListView {
model: subModels[index]
// ...
}
Then assuming you have 10 elements in the repeater, the model for each list view will be selected from the array with the appropriate element index.
Declare model for repeater instead of using array of strings. You can use ListModel for that purposes. You can also add to ListModels element, ListElement, any properties you want. Declare something like following:
ListModel {
id: repeaterModel
ListElement { title: "Line1"; model: modelForFirstElement; }
ListElement { title: "Line2"; model: modelForSecondElement; }
// ...
}
Then assign it to your Repeater's model property
Repeater {
id: repeater
model: repeaterModel
// ...
Then you will be able to access to model for your ListView by just calling model's model property, like this (assuming that you have assigned id "repeater" for your Repeater element):
ListView {
model: repeater.model.model
// ...
For the model, consider an array that has a sub-array nested within. To differentiate since both the outer Repeater and the inner ListView have a different instance of modelData consider copying the outer modelData to a property named outerModelData.
In the following example, I refactor out Column-Repeater-ListView and replaced it with ListView-ListView. Both patterns achieve the same thing but the latter does it shorter.
import QtQuick
import QtQuick.Controls
Page {
ListView {
anchors.fill: parent
model: [
{la:"English",v:["one","two","three"]},
{la:"Mandarin",v:["yi","er","san"]},
{la:"French",v:["un","duex","trois"]},
{la:"German",v:["eins","zwei","drei"]},
{la:"Indonesian",v:["satu","dua","tiga"]},
]
delegate: ListView {
property var outerModelData: modelData
width: 120
height: childrenRect.height
header: Text { text: outerModelData.la }
model: outerModelData.v
delegate: Frame {
width: 100
Text { text: modelData }
}
}
}
}
You can Try it Online!