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!
Related
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).
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
}
}
Suppose on C++ side I've created a QList<QObject *> myObjects which contains several custom objects derived from QObject.
And then expose it to QML by setContextProperty( "myModel", QVariant::fromValue( myObjects ) );
The question is, in my QML code, how can I get and use a specific element (by index) in myModel (which is a QList). For instance, I would like to take a random element from the list and show it?
The example is here: http://doc.qt.io/qt-5/qtquick-models-objectlistmodel-example.html, where all elements of the model are shown in a ListView`, while I just want to show one (or several) of them.
its pretty easy...
to get item number i from the model:
myModel[i]
and to access its properties/roles:
myModel[i].propertyName
To get an item from the list you can use the [] operator:
myModel[index]
The elements of a QList are similar to arrays in javascript since QML is based on the latter.
The following example shows getting the names in random form (it only replaces the code in the example).
view.qml
import QtQuick 2.0
import QtQuick.Layouts 1.3
import QtQuick.Controls 1.4
//![0]
ColumnLayout{
ListView {
width: 100; height: 100
model: myModel
delegate: Rectangle {
height: 25
width: 100
color: model.modelData.color
Text {
text: name
}
}
}
Button {
text: "random"
onClicked: {
t.text = myModel[ Math.floor(Math.random()*myModel.length)].name;
}
}
Text{
id: t
text: ""
}
}
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.
I want to use qml with master-detail interface, but i don't know how to pass current item to detail view right way. The ListView in master view uses C++ model (add-on of QSQLTableModel, it's work fine) and I see two ways to pass item:
Create C++ classes with fields with static name like QSqlRecord field names and pass it to qml with w->rootContext()->setContextProperty() (w is QDeclarativeView *), but now i don't use any classes like this and can change my database and qml views without changing c++ code, I would like to save it
Create a lot of properties in any detail qml like
Rectangle {
id: mainRect
property alias myFieldName: txt_nominal.text
Column {
width: parent.width
Text {
id: txt_nominal
font.bold: true
}
}
}
and set this properties from c++ code by setting w->rootContext()->setContextProperty(record.fieldName(i),record.field(i).value()); (record - QSqlRecort at current row)
Is there any easier way to solve my problem?
PS The code I wrote above is not checked for accuracy, and is written to make it more clear what I mean
UPD
Maybe it will be useful for somebody, I discovered 3-rd way, rather, the modification of second - you can wrap fields into QVariantMap and pass only one object to qml. This is exactly what I wanted
in cpp:
QVariantMap testObject;
testObject["testField"]="first string from cpp";
testObject["testField2"]="second string from cpp";
rootContext()->setContextProperty("testObject",testObject);
in qml:
Text {
id: simpleTextt
text: testObject.testField
anchors.centerIn: parent
}
You could use the isCurrentItem property of the delegate to pass the data from ListView delegate to your details qml. That way you could get away without have to add additional c++ code. This is basically your second approach but without c++. You also do not need to add many properties as long as each of your QML elements that you want to change have an id.
If you have a number of different QML for different details views you would also have to use the Loader to load the appropriate details QML.
Just a toy example assuming that you have only one details template for all of your elements in the list (as mentioned above if that is not the case than you can use loader instead of detailsRect):
Rectangle {
width: 300; height: 400
Rectangle {
id: detailsRect
anchors.right: parent.right
width: 100
height: 500
color: "blue"
Text {
id: detailsText
text: ""
}
}
ListView {
id: list
anchors.fill: parent
model: 20
delegate: Rectangle {
color: ListView.isCurrentItem ? "red" : "green"
width: 40
height: 40
Text {
text: index
}
ListView.onIsCurrentItemChanged: {
if(ListView.isCurrentItem)
{
detailsRect.color = "yellow"
detailsText.text = index
}
}
MouseArea {
anchors.fill: parent
onClicked: {
list.currentIndex = index
}
}
}
}
}