I want to create a QML binding in a repeated component. I want to bind the value of one of the elements to a property from a known object. My problem is that the name of said property that I want to bind to will be provided as a string in the component. How can I resolve the property name to an actual property that can be used as the value in a binding?
PS. If possible I guess I could pass the property directly to the repeater but then I would like to be able to convert the property to a string because I need both and don't want to pass both.
EDIT:
Here's what I want:
ListModel {
id: settingsModel
ListElement { title: "Bed Width"; setting: "bedWidth"; }
ListElement { title: "Bed Length"; setting: "bedLength"; }
}
Component {
id: settingsDelegate
Item {
width: parent.width
height: childrenRect.height
Label {
id: setLabel
text: title + ":"
width: parent.width
}
TextBox {
id: setTBox
anchors.top: setLabel.bottom
anchors.topMargin: 5
width: parent.width
Binding on text {
when: !setTBox.isActive
value: settings.setting
}
Binding {
target: settings
property: setting
value: setTBox.text
}
}
}
}
Column {
id: settingsColumn
spacing: 10
anchors.left: parent.left
anchors.right: parent.right
anchors.top: txtSave.bottom
anchors.topMargin: 15
Repeater {
model: settingsModel
delegate: settingsDelegate
}
}
My problem is that the name of said property that I want to bind to
will be provided as a string in the component. How can I resolve the
property name to an actual property that can be used as the value in a
binding?
If you look at the documentation for Binding you will discover that the property property expects a string - property : string
So you don't have anything to resolve, that happens internally.
my problem is the "value: settings.setting" line
You could try something like settings[setting]
Related
I have two QML files as below:
//Page.qml
SelectionPage {
model: localizationPageProxy.vehicleTypes //QObject* class exposed by property
currentKey: localizationPageProxy.vehicleTypes.currentDataKey //QVariant property
}
//SelectionPage.qml
Item {
property var model
property var currentKey
id: page
ColumnLayout {
height: parent.height
width: parent.width * 0.9
anchors.horizontalCenter: parent.horizontalCenter
ListView {
id: listView
anchors.fill: parent
ScrollBar.vertical: ScrollBar {}
clip: true
model: page.model.data
spacing: Number.EPSILON // I don't know why the data loading is faster with that
delegate: Item {
height: listView.height * 0.12
width: listView.width
RadioButtonItem {
height: parent.height * 0.85
width: parent.width
anchors.centerIn: parent
text: modelData.value
checked: modelData.key === page.currentKey
onClicked: page.currentKey = modelData.key //here the c++ property is changed
}
}
}
}
}
So, is currentKey property of SelectionPage.qml passed by reference?
If that was a copy I should not see the c++ model change.
Thank you for your help
Rather than discussing copy vs reference, we should really be talking about bindings. When you do this:
currentKey: localizationPageProxy.vehicleTypes.currentDataKey
You're creating a binding. Whenever currentDataKey's value changes, currentKey will be updated too. But it is not a two-way binding. So changing currentKey does not update currentDataKey. Two-way bindings are pretty difficult to achieve, but there are posts on SO about them.
To actually solve what you're trying to achieve, I recommend adding a Q_INVOKABLE function to your QObject called updateCurrentKey or something. Then in your onClicked handler, do something like this:
onClicked: page.model.updateCurrentKey(modelData.key)
I had a problem with the item in QML. I wanna get children of an item but it seems working in the first element.
The detail code is below:
I have a gridview with a list custom component AAA_Styles
GridView{
id: grdViewDeviceControl
clip: true
interactive: true
ScrollIndicator.vertical: ScrollIndicator{}
cellWidth: 200
cellHeight: 300
model: ListModel{}
delegate: Item {
width: grdViewDeviceControl.cellWidth
height: grdViewDeviceControl.cellHeight
AAA_Styles{
id: deviceControl
objectName: "deviceControl"
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
name: Names
subname: Subnames
}
}
My custom AAA_RTS is a QML component have some functions such as:
- function_a()
- function_b()
I added 2 items into model using
grdViewDeviceControl.model.append()
I ensure the model have data that I added because It appeared in my design and the count of gridview is 2 elements
console.log(grdViewDeviceControl.count) //the result is 2
After that, I tried to get each element to access functions that they are available using a method in signal onRelease of a button:
onReleased: {
console.log("number of item: " + grdViewDeviceControl.count)
var myRTS = grdViewDeviceControl.contentItem.children[0]
console.log(myRTS)
console.log(myRTS.children[0])
myRTS = grdViewDeviceControl.contentItem.children[1]
console.log(myRTS)
console.log(myRTS.children[1])
}
The result on console:
qml: number of item: 2
qml: QQuickItem(0x9828f60)
qml: AAA_Styles_QMLTYPE_0_QML_112(0x9829070, "deviceControl")
qml: QQuickItem(0x5554140)
qml: undefined
With the first element grdViewDeviceControl.contentItem.children[0], I access function_a or function_b successful but when I using the second the error appeared
TypeError: Cannot call method 'function_a' of undefined
So can anyone tell me why I wrong and how to fix it?
Many thanks for any help!
Do not try to access directly to the child items. Use delegate IDs, signals and slots instead:
Give a "delegate ID" to all your delegates through the model of your GridView.
In your GridView, add signals that will be used to broadcast to all the delegates the following things:
The "delegate ID" of the delegate that you want it to execute the function.
The arguments for the AAA_Styles function.
In your delegate, add one slot per AAA_Styles function. Each slot will execute the AAA_Styles function only if the broadcasted delegate ID is the delegate's: if (broadcastedID === delegateID) { function_ab() }.
When you want to execute function_a() or function_b() in a delegate, broadcast the delegate ID and the function arguments through the corresponding GridView signal (in onReleased, for example).
The following piece of code sums up what I have just described to you. If it does not work put the delegate in a separated QML file. This should work for good:
// Your grid
GridView {
id: grdViewDeviceControl
clip: true
interactive: true
ScrollIndicator.vertical: ScrollIndicator {}
cellWidth: 200
cellHeight: 300
model: ListModel {
/*
Example of list element:
ListElement { m_uuid: "{element-uuid}" }
*/
}
delegate: Item {
width: grdViewDeviceControl.cellWidth
height: grdViewDeviceControl.cellHeight
AAA_Styles {
id: deviceControl
objectName: "deviceControl"
anchors.centerIn: parent
name: Names
subname: Subnames
}
// The delegate ID
property string delegate_id: m_uuid
// Broadcast receivers
function delfunc_a(uuid, argA0) {
if (uuid === this.delegate_id) {
deviceControl.function_a(argA0)
}
}
function delfunc_b(uuid, argB0, argB1) {
if (uuid === this.delegate_id) {
deviceControl.function_b(argB0, argB1)
}
}
// Connecting broadcasters to receivers
Component.onCompleted: {
grdViewDeviceControl.exec_a.connect(this.delfunc_a)
grdViewDeviceControl.exec_b.connect(this.delfunc_b)
}
Component.onDestruction: {
grdViewDeviceControl.exec_a.disconnect(this.delfunc_a)
grdViewDeviceControl.exec_b.disconnect(this.delfunc_b)
}
}
// Your broadcasters
signal exec_a(string uuid, int argA0)
signal exec_b(string uuid, bool argB0, string argB1)
}
// Somewhere else in your code:
onReleased: {
/*
* Only the delegate whose ID is "{a-given-uuid}"
* will execute deviceControl.function_a(3):
*/
grdViewDeviceControl.exec_a("{a-given-uuid}", 3)
/*
* Only the delegate whose ID is "{another-given-uuid}"
* will execute deviceControl.function_b(true, "U got style!"):
*/
grdViewDeviceControl.exec_b("{another-given-uuid}", true, "U got style!")
}
I want to create my custom combo box. So I went and desinged a simple item that is a rectancle with a mouse area and a text. My idea is to draw multiple of these according to the values in an string array. Here is my code so far for the item:
Item {
signal itemSelected(int id);
property int vmItemIndex: 0;
property alias itemBody: ibody;
Rectangle{
id: ibody;
property alias itemMouseArea: imouseArea;
property alias itemText: itext;
Text{
id: itext
anchors.centerIn: parent
}
MouseArea {
id: imouseArea
anchors.fill: parent
onClicked: itemSelected(vmItemIndex);
}
}
}
the acutual combo box
Item {
property int vmWidth: 300;
property int vmHeight: 50;
property int vmCurrentIndex: 0;
property var vmItemTexts: ["Item 1", "Item 2", "Item 3"];
property string vmBackColor: "#ff0000";
VMComboBoxItem {
id: main;
itemBody.itemText.text: vmItemTexts[vmCurrentIndex];
itemBody.width: vmWidth
itemBody.height: vmHeight
itemBody.color: vmBackColor
}
}
Hoewever the engine load fails telling me it cannot access itemText. So what am I doing wrong?
I think the problem is that in your VMComboBoxItem component, you expose the inner Rectangle as a property. There's some hint about this in the Qt documentation.
What you can do instead is to expose selected properties directly in your "root" item in VMComboBoxItem:
import QtQuick 2.0
Item {
signal itemSelected(int id);
property int vmItemIndex: 0;
property alias itemBody: ibody;
// Expose the "text" property directly:
property alias text: itext.text
Rectangle{
id: ibody;
property alias itemMouseArea: imouseArea;
property alias itemText: itext;
Text{
id: itext
anchors.centerIn: parent
}
MouseArea {
id: imouseArea
anchors.fill: parent
onClicked: itemSelected(vmItemIndex);
}
}
}
And then set this property in QML code using your component:
import QtQuick 2.0
Item {
property int vmWidth: 300;
property int vmHeight: 50;
property int vmCurrentIndex: 0;
property var vmItemTexts: ["Item 1", "Item 2", "Item 3"];
property string vmBackColor: "#ff0000";
VMComboBoxItem {
id: main;
itemBody.width: vmWidth
itemBody.height: vmHeight
itemBody.color: vmBackColor
// Set Text property directly:
text: vmItemTexts[vmCurrentIndex];
}
}
It might generally be a good idea to expose only selected properties in your components, as this narrows the interface your component exposes (which in turn makes it much easier should you ever need to refactor your code).
I want to programmatically read a string generated in runtime by a basic Qt GUI application.
The string appears on the screen but I don't have the source and I want to pass this string to another script.
Here's the relevant .qml file:
import QtQuick 1.0
Rectangle {
id: tagCloud
SystemPalette { id: palette } //we get the system default colors from this
//public API
property variant model
property color baseColor: palette.base
property color textColor: palette.text
property int textFontSize: 16
color: baseColor
Flow {
id: flow
width: parent.width
spacing: 15
anchors.margins: 4
anchors.verticalCenter: parent.verticalCenter
//property int maxHeight:0
Repeater {
id: repeater
model: tagCloud.model
Text {
id: textBlock
text: category
font.pointSize: tagCloud.textFontSize;
}
}
}
}
Is there an easy way to get the "category" string as it's being generated?
Edit: This is the link to the application,
http://cybertron.cg.tu-berlin.de/eitz/projects/classifysketch/sketchpad_win.zip
I have a custom element called MenuButton:
import QtQuick 1.1
import VPlay 1.0
Image {
property alias text: buttontext.text
property alias mouseArea: area
property alias fontBold: buttontext.font.bold
property alias textSize: buttontext.font.pixelSize
id: button
source: "img/cloudButton.png"
opacity: 1
Text {
id: buttontext
color: "black"
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 50
font.bold: true
}
MouseArea {
id: area
anchors.fill: parent
onPressed: button.opacity = 0.7
onReleased: button.opacity = 1.0
onCanceled: button.opacity = 1.0
}
function doStuff {
// do something here
}
width: 200
height: 60
}
Now, in my main View, I have a Column with 5 of those MenuButtons. And I want to iterate over them and call the function doStuff(). How do I do that?
I tried with column.childAt(i) and stuff like that, nothing worked.
MainView.qml
Rectangle {
width: 480; height: 320
// HERE IS MY PROBLEM, how do I iterate over all my elements in the column?
function update() {
for(var i = 0; i < 5; i++) {
column.childAt(i).doStuff(); // THIS IS WHAT I WANT TO DO
}
}
Column {
id: column
spacing: 5
anchors.centerIn: parent
Repeater {
id: repeater
model: 5
MenuButton {
id: levelbutton
text: "Level " + (modelData+1);
source: "img/cloud4.png"
}
}
}
}
The problem is in my update function in the MainView.qml
I dont know how to iterate over the elements and call the doStuff() function.
You may use Component.onCompleted attached signal, like this:
import QtQuick 1.0
Rectangle {
height: 600
width: 600
Repeater {
model: 5
Item {
Component.onCompleted: console.log('Component ' + index + ' completed!')
}
}
}
However, please consider, that this imperative operation is not good, because it will be called all time after model update. Probably you have problem X and asking how to get Y, that (you think) will solve your X?
From what I've seen in the source code in the QDeclarativePositioners class you cannot access the children elements!
But you could turn the way you call your doStuff() method: when do you want it to be called? After some time has passed (then add a Timer element to your MenuButton), or when a signal occurs? In the latter case, you could use the Connections element and listen to the signal beign emitted in your calling qml file where you use the Column and Repeater.
Cheers, Chris
You can access the children of an element via the children property that exists on all QtObjects. It contains an array of child elements and can be freely acccessed in javascript.
eg. element.children[0].doStuff()
Generally speaking, you should avoid actions that require you to manually iterate over the children. However, if you are attempting to write code that generically calls something on each of the children you do not always have a choice.