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?
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'm following this tutorial (without the flickable content in each entry) for Qt 4.8 while using Qt 5.7 with QtQuick 2.0. The way the ListView there works is as follows:
User clicks on item in list
Alternative (detailed) view of item is displayed
User has to click on Close button in detailed view to reset the state of entry to its default compact view.
This leads to a clutter where at some point if the user clicks on all items in which case all will be shown in their full view. Having the user click on the Close button every time he/she opens a detailed view also is (omho) not that handy.
I've altered the entry to close when the user clicks on the view. I'm also trying to prevent this clutter and achieve a more (omho) flowing behaviour:
User clicks on item in list
Alternative view of item is displayed
User clicks on detailed view to reset state of entry to its default compact view OR
User clicks on another entry and all currently in detailed view entries are reset to their compact view
Currently I'm looping through my ListView's contentItem.children[loop_index] and setting the state to "" ("Details" = show detailed view | "" = show compact view). Due to the way ListView works (loading/unloading delegates on demand) this is quite unreliable and I often get an undefined reference when I try to access the state of other delegates. The following MouseArea, which I'm using to do all that, is part of every delegate:
// state is a QML `State` that is bound to the delegate (see below for the details on it)
MouseArea {
anchors.fill: background
onClicked: {
// Iterate through all other entries and close them
for (var entry = 0; entry < listView.count; ++entry) {
if(listView.contentItem.children[entry] !== gestureEntry) {
console.log("Hide other element");
listView.contentItem.children[entry].state = ""; // IT FAILS HERE (SOMETIMES)
}
}
// Change view of current entry
if(gestureEntry.state === "Details") {
gestureEntry.state = "";
console.log("Hiding details")
}
else {
gestureEntry.state = "Details";
console.log("Showing details");
}
}
}
with state being a delegate's state:
states: State {
name: "Details"
PropertyChanges { target: background; color: "white" }
PropertyChanges { target: gestureImage; width: 130; height: 130 } // Make picture bigger
PropertyChanges { target: gestureEntry; detailsOpacity: 1; x: 0; y: 0 } // Make details visible
PropertyChanges { target: gestureEntry; height: listView.height } // Fill the entire list area with the detailed view
}
I'm thinking that the state information can be stored inside the ListModel itself making it possible to iterate through the model's contents (which are always there unlike the contents of the delegates) however I don't know how to automatically update my list (and the currently visible/invisible delegates) when an entry changes in the model. From what I've found so far it seems not possible to do that since the ListView doesn't actively monitor its ListModel.
Is this indeed the case? If yes, then is it possible to go around this problem in a different way?
Why don't you use the currentIndex property of your ListView?
Just modify your delegate like this:
Item {
id: gestureEntry
...
state: ListView.isCurrentItem?"Details":""
...
MouseArea {
anchors.fill: background
onClicked: {
if(listView.currentIndex == index)
listView.currentIndex = -1
else
listView.currentIndex = index
}
}
}
EDIT:
The only issue with the solution above is that - upon loading - an entry in the ListView is preselected which automatically triggers the detailed view of that entry. In order to avoid that the following needs to be added to listView:
Component.onCompleted: {
listView.currentIndex = -1;
}
This ensures that no entry will be preselected.
guess it is an issue because you stored a state in your delegate. You should not do this as described in the delegate-property (Link), because the delegates get reused when they get out of view.
At least you should use a when: ListView.isCurrentItem in the State and depend on a value of the ListView. So only your current delegate is maximized. Then in the MouseArea only set `ListView.view.currentIndex = index'. Don't change the state manually in the function!
I ran in the same trouble, removed the states completely and just used the attached property ListView.isCurrentItem. But binding the state to a Value from the ListView should also work, because it's not stored in the delegate.
Minimal example:
import QtQuick 2.0
Item {
width: 800
height: 600
ListView {
id: view
anchors.fill: parent
model: 3
spacing: 5
currentIndex: -1
delegate: Rectangle {
id: delegate
color: ListView.isCurrentItem ? "lightblue" : "green" // directly change properties depending on isCurrentItem
height: 100
width: 100
states: State {
name: "maximized"
when: delegate.ListView.isCurrentItem // bind to isCurrentItem to set the state
PropertyChanges {
target: delegate
height: 200
}
}
MouseArea {
anchors.fill: parent
//onClicked: delegate.ListView.view.currentIndex = model.index // if only selection is wanted
onClicked: {
//console.debug("click");
if (delegate.ListView.isCurrentItem)
{
delegate.ListView.view.currentIndex = -1;
}
else
{
delegate.ListView.view.currentIndex = model.index;
}
}
}
Text {
anchors.centerIn: parent
text: index
}
}
Text {
text: "CurrentIndex: " + parent.currentIndex
}
}
}
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.
{New to Qml (quick 2.0 using Qt 5.1 beta) and learning}.
I wanted to know if such an idiom would be possible in Qml:
Below I have objLeftColumn which expects its children to expose a boolean m_bIsSelected and a MouseArea alias m_mouseProperty and uses them to make the collection of such children mutually exclusive, ie., only one of them can be in selected state. The followin works fine but I need to repeat it every time I want and specially if I wanted it for Row etc.
Column {
id: objLeftColumn
property int m_iLastButtonClicked: -1
property int m_iCurrentButtonClicked: -1
onM_iCurrentButtonClickedChanged: {
if(m_iLastButtonClicked != -1) {
objLeftColumn.children[m_iLastButtonClicked].m_bIsSelected = false
}
m_iLastButtonClicked = m_iCurrentButtonClicked
}
Repeater {
id: objLeftColumnRepeater
model: 5
delegate: ABCD {
id: objABCD
m_mouseProperty.onClicked: {
if(m_bIsSelected) {
objLeftColumn.m_iCurrentButtonClicked = index
}
else {
objLeftColumn.m_iLastButtonClicked = -1
objLeftColumn.m_iCurrentButtonClicked = -1
}
}
}
}
}
Can I write a generic objLeftColumn (in a separate qml file) that could arrange the given Items in Column while aslo dealing with exclusivity of their selection?
The idea is instead of giving the component to the delegate right there an then, I'll give it later and for each instantiation of the component (depending on numeric value of model above and below) the delegate: in Repeater should behave similarly.
eg., in psedo code:
in Exclusive.qml:
Column {
id: objLeftColumn
property int m_iLastButtonClicked: -1
property int m_iCurrentButtonClicked: -1
property alias m_delegate: objLeftColumnRepeater.delegate
onM_iCurrentButtonClickedChanged: {
if(m_iLastButtonClicked != -1) {
objLeftColumn.children[m_iLastButtonClicked].m_bIsSelected = false
}
m_iLastButtonClicked = m_iCurrentButtonClicked
}
Repeater {
id: objLeftColumnRepeater
model: 5
onItemAdded: {
//state of item can be manipulated but want to
//add behaviour to the item eg:
/*item {
m_mouseProperty.onClicked: {
//do something
}
}*/
}
}
}
in SomeOther.qml:
Exclusive {
model: 5
delegate: ABCD
}
Exclusive {
model: 9
delegate: DEFG
}
etc..So this way Column in Exclusive is more generic and can be called with any Item assigned to its delegate and will behave similarly. Is this possible in qml
This needs a bit of trickery to be solved, I can think of two ways:
Use the JS connect() function to manually create the connections. Something like this:
Repeater {
id: objLeftColumnRepeater
model: 5
onItemAdded: {
item.m_mouseProperty.onClicked.connect(function() {
console.log("Clicked!");
});
}
}
Wrap the delegate into an Item by using a Loader, and use aConnections element for the connection. Something like this:
property Component delegate
Repeater {
id: objLeftColumnRepeater
model: 5
delegate: Item {
Loader {
id: loader
sourceComponent: delegate
}
Connections {
target: loader.item.m_mouseProperty
onClicked: console.log("Clicked")
}
}
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.