Let's say I have a QtObject loaded by a Loader which is embedded in a Repeater (which itself is attached to some arbitrary ListModel).
How would I access the properties and functions of the QtObject?
Repeater {
id: repeater
model: listModel
Loader {
sourceComponent: QtObject {
property int width: 100
property int height: 100
function foo() {console.log("bar")}
}
}
}
}
First you access the Loader using itemAt() function and then the QtObject using item property:
var loader = repeater.itemAt(index)
var qt_object = loader.item
console.log(qt_object)
Related
I'm making a user control which has a property, which I modify with functions. The property is binded to another property in Main.qml file. In a real-life project, it is binded to a property of a ViewModel. I need to have a two-way binding allowing me to change the ViewModel's property from qml view and the view's property from the view model.
Main.qml:
property int myProperty: 0
Column {
MyControl {
value: myProperty
}
MouseArea {
width: 200; height: 100
onClicked: myProperty = 0
}
}
MyControl.qml:
Item {
id: root
property int value: 0
Row {
MouseArea {
width: 100; height: 100
onClicked: root.value++
}
MouseArea {
width: 100; height: 100
onClicked: root.value--
}
}
}
But in fact, when I change MyControl's value property with root.value++, it brokes the binding and the changes stop passing between the properties.
Is there a way to implement it in a declarative way, without cross-setting it by ...Changed signals?
I am trying to make a change to all items of a GridView.
I have tried to iterate through either the model or the grid, I looked at similar examples on the web, but everything I try ends with Cannot read property 'buttonText' of undefined.
It seems to me that the problem is that the interpreter can't figure out that the item from the grid or model is a Button. But I don't know how to cast it.
If I change the log to only display the item, not any property, (see code snippet), it seems that it knows it is an item... see my experiments below.
The only thing I can make work is set a property (or call a signal, or a function) from the delegate. But that only affects one grid item, not all.
How can I set a property on every item of the grid ? Alternatively, how can I send a signal, or call a function, on every item?
My experiments are in function changeEverythingFunction()
file: Button.qml
Item
{
id: itemButton
signal changeEverything
property int buttonIndex
property string buttonText
...
}
file: Model.qml
Item
{
id: modelItem
ListModel
{
id: listModel
}
property int buttonCount: listModel.count
function changeEverythingFunction()
{
// for (var i = 0; i < buttonCount; i++)
// listModel.setProperty(i, buttonText, "abc")
for(var childIndex in gridItems.contentItem.children)
{
console.log(listModel.get(childIndex).buttonText) // Cannot read property 'buttonText' of undefined
console.log(gridItems.contentItem.children[childIndex].buttonText) // Cannot read property 'buttonText' of undefined
console.log(gridItems.contentItem.children[childIndex]["buttonText"]) // undefined (I saw this in a SO example)
var item = gridItems.contentItem.children[childIndex]
console.log(item) // qml: QQuickItem(0xe496370)
}
}
MouseArea
{
....
Rectangle
{
...
GridView
{
id: gridItems
anchors.fill: parent
clip: true
model: listModel
delegate: Item
{
id: buttonDelegate
Button
{
buttonIndex: gridId
buttonText: itemText
onChangeEverything:
{
changeEverythingFunction();
}
}
}
}
}
}
}
Your approach is in the opposite direction: Your approach is to obtain the item of the view and modify it, but the approach that Qt points out is that the view reflects the information of the model and modifies it when necessary.
The following is a simple example where every time you press on the button with "change me" text increasing the number it shows, but if you press the button with "change all" text it will change all the numbers. As it is observed everything is done through the model, not through the view that are only used to display information or receive user interaction.
import QtQuick 2.14
import QtQuick.Window 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
Window {
visible: true
width: 640
height: 480
ListModel{
id: listmodel
}
function changeAll(){
for(var i=0; i< listmodel.count; ++i){
listmodel.setProperty(i, "number", listmodel.get(i).number + 1)
}
}
GridView{
id: grid
anchors.fill: parent
clip: true
model: listmodel
cellHeight: 120
cellWidth: 120
delegate: Item {
width: grid.cellWidth; height: grid.cellHeight
Column {
anchors.fill: parent
Text { text: model.number; anchors.horizontalCenter: parent.horizontalCenter }
Button{text: "change me"; onClicked: model.number +=1}
Button{text: "change all"; onClicked: changeAll()}
}
}
}
Component.onCompleted: {
for(var i=0; i < 10; ++i){
listmodel.append({"number": 0});
}
}
}
I have a QML application where I'm creating lists of elements received from JavaScript. Using the details from this answer I'm populating the model as a JS array, and it works great. However, I'd like it so that when properties of the JavaScript objects change that ListView items driven from them update live.
Here's a simple test app showing the problem. The ListView is properly populated with MyRow instances showing the correct id/title, but when the rand property is changed by the timer, the List rows are unchanged (they show 0 for the last item).
MyRow.qml
import QtQuick 2.0
import QtQuick.Layouts 1.3
Rectangle {
property var obj
color:'#eeeeff'; height:20
RowLayout {
anchors.fill:parent
Text { text:obj.id }
Text { text:obj.title; Layout.fillWidth:true }
Text { text:obj.rand }
}
}
main.qml
import QtQuick 2.7
import QtQuick.Window 2.2
Window {
id:app; visible:true; width:200; height:100
property var database: ({"17":"World","42":"Hello"})
property var objById: ({})
function getObj(id){
if (!objById[id]) objById[id] = { id:id, title:database[id], rand:0 };
return objById[id];
}
ListView {
id:mylist
anchors.fill:parent
model: [42,17] // object ids
delegate: MyRow {
width:parent.width
obj:getObj(mylist.model[index])
}
}
Timer { // Update every object's rand value every second
interval:1000; running:true; repeat:true
onTriggered: {
Object.keys(objById).forEach(function(id){
objById[id].rand = Math.random()*100<<0;
})
}
}
}
How can I get the delegate's Text items to update their text when properties of the objects change?
The easiest (only?) way to get property bindings to work properly is to create real Qt objects to hook the values to. If you don't want to use a ListModel (because you want to quickly populate a model with items from a master library), then you can use createObject() to generate objects and pass them to your delegate.
Here's an updated main.qml that works as desired:
Window {
// ...same as above...
Component { // Creates real Qt objects with bindable properties
id:objFactory
QtObject {
property int id
property string title
property int rand:0
}
}
function getObj(id){
if (!objById[id])
objById[id] = objFactory.createObject( app, {id:id,title:database[id]} );
return objById[id];
}
// ...same as above...
}
Additionally, you may wish to change the property var obj in MyRow.qml to a more specific property QtObject obj (or a more specific object type, depending on what you pass in).
Finally, note that it's slightly cleaner/simpler to use modelData instead of mylist.model[index]:
ListView {
anchors.fill:parent
model: [42,17] // object ids
delegate: MyRow {
width:parent.width
obj:getObj(modelData)
}
}
Currently some students and I are programming a little application with QtQuick.
We have the following TableView:
TableView {
model: ListModel {
id: orderListModel
Component.onCompleted: {
var tOrderList = orderController.getOrderList();
for(var i = 0; i < tTicketList.length; ++i){
orderListModel.append(tOrderList[i]);
}
}
}
TableViewColumn {
role: "orderId"
title: "Auftragsnummer"
width: 100
}
TableViewColumn {
role: "customer.name"
title: "Kunde"
width: 100
}
}
getOrderList returns a QList<Object*> with all the orders.
The Order class has a property customer
Q_PROPERTY(Customer* customer READ getCustomer NOTIFY customerChanged)
which in turns has a property called name.
We would like to show the latter property inside the TableView but unfortunately only the orderId property of Order does works.
What value should have the second role? How can we access data of the nested object?
According to the documentation you can use QList<QObject*> as a model for a view directly, without manually copying the data as you did. In particular:
The QObject* is available as the modelData property. As a convenience, the properties of the object are also made available directly in the delegate's context.
Considering the example linked in the documentation we have that a property of the QObject can be used as role in the following way:
ListView {
width: 100; height: 100
model: myModel // injected QList<QObject*> context property
delegate: Rectangle {
height: 25
width: 100
color: model.modelData.color // without "modelData" it would not work
Text { text: name }
}
}
The same applies for nested properties. If our QObject-derived object has a QObject property, like in your case, we can retrieve it via modelData and then access it's properties. So, for instance with the customer property, we would have something like this in the delegate:
Text { text: model.modelData.customer.name }
That's true for ListView et similia whereas I'm not sure it could be done directly with TableView. One possible workaround would be to combine the usage of the QObject-derived role with styleData.value. You can define the usage of the role in the role property and access the inner property inside the styleData.value. A solution for your case would look like the following (assuming myModel is a context property as the example above):
TableView {
width: 100; height: 100
model: myModel
TableViewColumn {
role: "customer"
title: "Kunde"
width: 100
delegate: Text {
text: styleData.value.name // accessing the property
}
}
itemDelegate: Item { } // should be set to empty to avoid warnings
}
A downside of this approach is that you should implement a delegate for each column.
I have the following C++ model structure:
Manager // QAbstractListModel
↪ Slot // QAbstractListModel
↪ Processor // QAbstractListModel
↪ name // Q_PROPERTY
I pass only the Manager reference to QML on instantiation. I need to fill the ComboBox with Processor names but I don't know how to fill up with this nested structure.
Here is the code I plan to have (but is not working now):
ComboBox {
model: Repeater {
model: manager
delegate: Repeater {
model: slot
delegate:Repeater {
model: processor
delegate: ListModel {
ListElement {text: name}
}
}
}
}
}
I know that delegates are for specifying how to display data (and that's why ComboBox doesn't have this one), but I'm out of ideas how to implement this correctly.
So my question is: how to fill up a ListModel recursively?
I came up with the following solution to recursively fill a ComboBox:
ComboBox {
id: comboBox
model: ListModel {}
textRole: "processorName"
Repeater {
model: manager
delegate: Repeater {
model: slot
delegate: Repeater {
model: processor
Component.onCompleted:
comboBox.model.append(
{"processorName": model.Processor.Name}
);
}
}
}
}
Add to your QAbstractListModel role that returns another QAbstractListModel.