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.
Related
i have an application which sets properties of Qml from C++, so far i was able to set property of any type. I am able to create QQmlComponent in C++ and set it to property.
But first i need to initialize component with its properties. I can do that with create/beginCreate and set properties. But i cant change created QObject back to QQmlComponent, therefor i cant set it to givent Component type property.
Is there some way how to conver QObject to QQmlComponent ? Or is there any other way to initialize QQmlComponent properties ?
Example, of what i want to achiev in C++, how it looks in qml
import QtQuick 2.3
Loader {
// property of type Component
sourceComponent: Text {
text: "property i want to intialize, this component can be in file"
}
}
Edit: I will clarify what im after. Suppose i have something like JSON given by user.
e.g:
{
"type":"Loader",
"sourceComponent":{
"type":"Text",
"text":"some property of property of type QQmlComponentProperty"
}
}
Then from C++ i create given items, problem is how to create QQmlComponent with user given properties.
Are there any signals I can bind to that allow me to execute functions directly before and directly after a property changes?
For example something like this:
Canvas {
id: canvas
anchors.fill: parent
property real lastX
property real lastY
property var lastContext
function cacheImage(){
lastContext = canvas.context
}
function loadImageFromCache(){
canvas.context = lastContext
canvas.requestPaint()
}
}
I want to call the cacheImage() function right before the canvas's size changes and the loadImageFromCache() right after the resize is completed.
If I understand correctly the onWidthChanged and onHeightChanged do the latter, but I need to save the image before it does that. Is there a simple way to do this?
But cacheImage() doesn't have to be called right before the size changes! It needs to be called any time after the most recent canvas contents change, and before the new canvas size takes effect. The window to cache the canvas is very long and extends between changes. The event "before n-th property change" is simply "after (n-1)-th property change" :)
This kind of functionality is not part of the "core design intent" of QML, so you are not getting it out of the box.
It is quite easy to do for C++ objects when you get to implement your own setter function.
But even in QML you can simply use an auxiliary setter function rather than using the property directly:
function setSomeProp(newValue) {
if (newValue === oldValue) return
stuffBeforeChange()
oldValue = newValue
stuffAfterChange() // optional, you can use the default provided signal too
}
If you insist on using a property based syntax, you can use an additional auxiliary property:
property type auxProp
onAuxPropChanged: setSomeProp(auxProp)
Which will allow you to use the = operator as well as do bindings.
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 have a component that should be created dynamically.
Component {
id: myComponent
Rectangle {
id: letItBeRect
}
}
I want to create it dynamically (when some button clicked) in a function like this:
function loadComponent() {
myComponent.createObject(root); //root is some root component, doesn't matter
}
I have state for root component that depends on some letItBeRect property:
state: letItBeRect.visible ? "visible" : "hidden"
So the question is how do I check if letItBeRect has been created, so I could assign a proper value to "state" property of root component?
I get "ReferenceError: letItBeRect is not defined" so far, which is expected from this code snippet.
P.S. This is not the real code I have, because I don't want to put commercial code here. Thank you
what is the best way to pass any model (for example a ListModel in main.qml) through loader into an other .qml file?
If the model only needs to be set once, then you can simply do that in the onLoaded signal handler
Loader {
id: loader
source: "myelement.qml"
onLoaded: item.modelProperty = yourModelId;
}
If it needs to be a binding, e.g. if the model is stored in a property which will change during runtime to a different model instance, then a Binding element should work
Binding {
target: loader.item
property: "modelProperty"
value: theModelStoreProperty
}
There is really only one way to use a loader. Pass the url of the qml file to the source property.
From http://qt-project.org/doc/qt-5.0/qtquick/qml-qtquick2-loader.html#details
Loader {
id: myLoader
source: "MyItem.qml"
}
It sounds like this is not quite what you are asking though. If you can provide more details on what you are doing we may be able to provide more help.