So I have created a nested list in QML:
ListModel {
id: players
ListElement {
name: "Player 1"
counters: [
ListElement {
name: "Life"
count: 20
edit: false
} ,
ListElement {
name: "Poison"
count: 0
edit: false
}
]
}
//etc...
}
And, I have two views that read data from this: a ListView that is given the first-level ListModel, and a GridView that reads from the counters second-level model. I have given the GridView the counters list through the attached property in the delegate, like so:
GridView {
model: counters
}
Initially, it reads the data just fine. But my problem is that when the second-level model data is updated from outside the delegate, the GridView does not update with the new data. How do I get it to update?
When looking around, I have seen other people just create two separate models, but the problem is that I need the information in the nested list to be associated with the ListElement that contains it, so that really is not an option.
It turns out that you have to catch the signal and change the property manually. It's a little brute-force (because it will run given any change in the model, not just changes from outside the delegate), but it works.
In order to do this, you have to add a Connections element to the view delegate to catch the signal.
GridView {
id: view
model: counters
delegate: Item {
property int count: count
Connections {
target: view
onDataChanged: Item.count = count
}
}
}
It is important that the Connections element is a child of the delegate, because the view creates/removes delegates at its whim, and as such it is difficult to access the delegates created by the view.
Related
I am trying to create ListModels dynamically, and insert these ListModels into a Container.
I get a variable number of ListModels, each with variable content, from a backend system. Based on the user's interaction with the GUI, one of those ListModels needs to be loaded into a known ListView. The client wants to avoid Qt/C++, so I am looking to solve this via QML.
How do I get a dynamically created object (in my case, a ListModel) into a Container? This code does not work:
property string strID;
property string strName;
Container {
id: functionListContainer;
}
// create ListModel
var newObject = Qt.createQmlObject("import QtQml.Models 2.14; ListModel { }", mainWindow);
...
// dynamically append elements into the ListModel
newObject.append({ deviceID: strID, deviceName: strName })
...
// add ListModel to Container
functionListContainer.addItem(newObject);
createQmlObject and append seem to work as intended. I get this error when running addItem code above:
"Could not convert argument 0 at"
...
"Passing incompatible arguments to C++ functions from JavaScript is dangerous and deprecated."
"This will throw a JavaScript TypeError in future releases of Qt!"
Any idea regarding how to get this to work? I know that addItem is expecting an Item, not an Object, but I do not know how to get this to work. I have tried replacing var with property Item newItem and property QtObject newObject [combined with addItem(newObject.item)], and all give (seemingly identical) errors. Is it a simple issue of casting the object into an Item? If so, what is the syntax that needs to be used?
Lastly, assuming I do have a Container with N ListModels, is it possible to refer to the container directly in the ListView? i.e.:
property int idx;
Container {
id: functionListContainer;
}
// add ListModels to container...
// use ListModel inside container
ListView {
...
model: functionListContainer.itemAt(idx);
...
}
I know that ListView is expecting a ListModel or something equivalent. But I am not sure how to connect the ListView with a Container containing ListModels, or if it is even possible.
If I were to summarize my problem, I am trying to have a ListView display different ListModels based on context, and I am trying to do this within a pure-QML framework as much as possible. Both of my questions above related to this. It would be helpful even only to clarify that this is not an option and that it is necessary to use another method like clearing and populating the ListModel (suggestions welcome).
You are trying to add a QObject (since ListModel is inherited from QAbstractListModel, which is in turn inherited from QObject) as a visual item using the addItem function. However, in QML a QObject (or QtObject) is regarded as an "storage element".
What you want to do it add the contentData of the Container:
Container {
id: functionListContainer
}
// create ListModel
var newObject = Qt.createQmlObject("import QtQml.Models 2.14; ListModel { }", mainWindow);
...
// dynamically append elements into the ListModel
newObject.append({ deviceID: strID, deviceName: strName })
...
// add ListModel to Container
functionListContainer.contentData.push(newObject)
The contentData property is the place where all children reside in the Container; QObject and QQuickItem (note that QQuickItem is inherited from QObject).
About referencing the lists, this also becomes easy when using the contentData property:
// create ListModel
var newObject = Qt.createQmlObject("import QtQml.Models 2.14; ListModel { objectName: \"the_list\" }", mainWindow);
...
// add ListModel to Container
functionListContainer.contentData.push(newObject)
console.log(functionListContainer.contentData[0])
will yield:
qml: QQmlListModel(0x55bcb7119720, "the_list")
Note that this is almost the same as using a Javascript array, the documentation of QQmlListProperty (which is what contentData is) states it is transparent.
I have an item of some type that consists of several QML editing controls:
Column {
id: inputItem
SpinBox {
}
TextInput {
}
ComboBox {
}
Button {
id: enableMeButton // this needs to be enabled if
text: "Apply" // anything was changed above
enabled: false
}
}
This item [Column as example] can be potentially inserted to some list view as "polymorphic" editing item so that we don't know beforehand what edit fields to handle (say we would want to move Apply outside). Or we want to use a common type with Apply to develop different editing delegates (which is the story here).
How can we detect that any of the data handled in this form was changed? Is there a generic way to do so or there is some trick to accomplish it?
ListView {
id: listView
model: someModel {}
delegate: Loader {
id: delegateLoader
source: {
var where;
if(model.type === 1) {
where = "RadioQuestion.qml";
}
else
where = "TextQuestion.qml";
if(delegateLoader.status == Loader.Ready) {
delegateLoader.item.question= model.question;
}
return Qt.resolvedUrl(where);
}
}
I show some questions to user by using ListView. But I cannot access members of delegate loaded by Loader.
RadioQuestion.qml has Radio Buttons and Text is just text field. I just want to get all answers after pressing a submit button but I could not figure out how I can traverse among delegates.
Maybe my approach is wrong about building this structure. Therefore I am open for better solutions.
Your question is already exposed through the model, so your delegates should directly bind to it, so assuming that question is a property of the model:
// from inside the delegate
question: delegateRootId.ListView.view.model.question
or assuming that question is a list element role:
// from inside the delegate
question: model.question
And if you were careful enough not to name the property inside the delegate question thus shadowing the model role, you could simply:
// from inside the delegate
questionSource: question
Not to mention that if your model "scheme" is known and it is a given that you will have a question role and that role will be displayed in the delegate, you don't even need any additional property in the delegate to begin with, you can bind the actual item directly to the question, for example:
// from inside the delegate
Text { text: question }
And that's all that's really needed.
Alternatively you can use a Binding or a Connections element or a simple signal handler to delay the operation until the loader has actually completed the loading. For example:
delegate: Loader {
source: Qt.resolvedUrl(model.type === 1 ? "RadioQuestion.qml" : "TextQuestion.qml")
onStatusChanged: if (status === Loader.Ready) if (item) item.question= model.question
}
Button {
text: "Submit answers"
onClicked: {
for(var child in listView.contentItem.children) {
var c = listView.contentItem.children[child].item;
if(typeof c !== "undefined")
console.log(c.answer)
}
}
}
You can get properties of loader delegate like this. We use "undefined" check, because one object in children of contentItem is undefined. That was second object in the list, I do not know why.
I'm accessing a XML page (online) via a XmlListModel and displaying the data using a ListView delegate (Row).
Once the Listview display the data (Xml file always only contain one "record"), i want a separate label to display one of the node's data (selected row)? Any guidance on this matter is appreciated.
My Listview:
ListView {
id: viweID
model: modelID
delegate: Row {
id:rowID
spacing: 10
Text {
id: fnameId
text: FName
}
Text {
id:lnameID
text: LName
}
}
Thank you
The ListView can be accessed either in terms of its items or by visiting its underlying model (see the documentation for further details).
It looks to me that you want to visit the former, for it already contains all the data that have to be shown on your separated panel.
To do that you can rely on the currentItem property and the signals onCurrentItemChanged/onCurrentIndexChanged.
It's a matter of exposing the desired properties from the delegate object to be able to read them and set them on the other panel.
See here for further details on how to expose those properties.
If I want to build out a TableView dynamically by loading data via AJAX call rather than by specifying list elements manually (like in the code below), how can that be done? I haven't found any examples anywhere yet.
ListModel {
id: dataModel
ListElement { title:"Image title"; credit:"some author"; source:"http:/..." }
ListElement { title:"Another title"; credit:"some author"; source:"http:/..." }
}
You need to fetch the data and then append it to your model.
The docs on ListModel are pretty straight forward and clear on how to add data to a model dynamically.
As to making the web request, here's an example on how to fetch data in QML using XMLHttpRequest.