How to access subcomponentes in QML - qt

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).

Related

Are QML var properties passed by reference or by copy?

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)

Qml: How to set a property to all GridView items

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});
}
}
}

read string from Qt application by reverse engineering

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

QML Access object property by property name string

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]

QML iterating over custom elements and calling function

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.

Resources