I'm playing around with QtQuick in Qt 5.9 and I encountered a strange issue.
When I created two Tumblers and a CheckBox in QML everything was working fine.
But when I created an event handler for a id: secondTumbler which manipulates testCheckBox.checked status the CheckBox started to act in a strange way.
When I launch the app and firstly scroll around any tumbler and then click the CheckBox it will not check. The second click will eventually check it but that is a strange behavior.
The only thing I wrote is the below code in the main.qml:
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Tumbler {
id: firstTumbler
model: 10
anchors.left: parent.left
}
Tumbler {
id: secondTumbler
model: 10
anchors.right: parent.right
onCurrentIndexChanged: {
testCheckBox.checked = false
}
}
CheckBox {
id: testCheckBox
anchors.left: firstTumbler.right
onCheckedChanged: {
if(testCheckBox.checked == true)
{
secondTumbler.currentIndex = firstTumbler.currentIndex
}
}
}
}
What am I missing?
The issue is that javascript runs asynchronously. So, signals and slots dont work as they do in C++. They fire along with the other code, not sequentially. This makes them an unrealiable Intermediary for logical processing since the order that events can happen can vary.
Instead,
Use property bindings for this one by setting a property such as currentIndex to another property created using property var <my property> and currentIndex: <my property>
Then you can change the value of currentIndex by setting <my property> without disrupting the flow of things.
Tumbler {
id: firstTumbler
model: 10
anchors.left: parent.left
}
/* Edit in response to comment #1
*/
property bool followFirst: testCheckbox.checked
/* end of Edit in response to comment #1 */
Tumbler {
id: secondTumbler
model: 10
/* modify the property of currentIndex by changing this variable which will be bound to the currentIndex property */
property var m_index: 0
anchors.right: parent.right
/* conditional assignment for currentIndex -- do not set currentIndex directly or this will disappear..
instead set secondTumbler.m_index */
currentIndex: testCheckBox.checked === true ? firstTumbler.currentIndex : m_index
/* ensures that changing the currentIndex does not change the actual property, but instead changes m_index which will be bound to that property */
onCurrentIndexChanged: {
m_index = currentIndex;
/* Edit in response to comment #1
*/
if (followFirst) { testCheckBox.checked = false }
/* end of Edit in response to comment #1 */
}
}
this will allow the chckbox to change state along with the tumbler without running into state conflicts arising from the current index changing.
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 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 want to enable TextField focus when QML file is loaded. But, it is not working. After loading TestUi.qml file I put some button and its onClick() method I did _recipientView.focus = true_, it works fine. The problem is that default focus is not enabled when view is loaded first time.
TestUi.qml
import QtQuick 2.0
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.0
Page {
function init() {
recipientView.focus = true;
}
TextField {
id: recipientView
Layout.fillWidth: true
font.pixelSize: 18
inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhEmailCharactersOnly
focus: true
placeholderText: "Email"
}
}
main.qml
onComposeBtnClicked: {
rootStackView.push(test)
test.init()
}
TestUi {
id: test
visible: false
}
Edit
The Page component already acts as a FocusScope, so only forcing the active focus is necessary. Thanks for the comments.
StackView {
id: stackView
initialItem: firstPage
// Ensures the focus changes to your page whenever
// you show a different page
onCurrentItemChanged: {
currentItem.forceActiveFocus()
}
}
Page {
id: firstPage
visible: false
TextField {
// Explicitly set the focus where needed
focus: true
}
}
Original answer
This is simply because you are pushing TestUi into your stack with rootStackView.push(test). When you do so, the focus is reset. This is typically handled with a QFocusScope, which role is to remember the focused Item, and give the focus back to it when the QFocusScope regains focus.
In your case, adding a QFocusScope to your base page would enable restoring the focus correctly when the page is shown:
StackView {
id: stackView
initialItem: firstPage
onCurrentItemChanged: {
currentItem.forceActiveFocus()
}
}
Page {
id: firstPage
visible: false
onFocusChanged: {
scope.focus = true
}
FocusScope {
id: scope
TextField {
focus: true
// ...
}
}
}
You can then use your page handler onVisibleChanged if you want to reset to focus when the user comes back to it (after a pop for instance), instead of memorizing it where the focus is. But in that case the FocusScope might be overkill.
For information, you can also use the StackView property initialItem in order to set the first page.
It is also kind of unrelated, but prefer importing the most recent version of the QtQuick components available. QtQuick version will be 2.12 for Qt 5.12. A bit less trivial for QtQtcuik.Controls version, but they are getting in line with that versioning scheme.
I have rather strange scenario whereby if I launch a subwindow that contains a ListView with a moderately complex delegate and enough items to comfortably exceed the visible area, the entire subwindow will immediately close on launch.
Reducing the complexity of the delegate will allow the window to open, but then rapidly scrolling the ListView will forcibly close it.
This SSCCE triggers the effect on my laptop, but on a more powerful machine it may only do it whilst scrolling (or perhaps the delegate may need to be more complex):
import QtQuick 2.3
import QtQuick.Window 2.0
Window {
width: 300
height: 200
Component.onCompleted: {
win.createObject( null );
}
Component {
id: win
Window {
width: 600
height: 400
visible: true
ListView {
id: view
anchors.fill: parent
model: 100
boundsBehavior: Flickable.StopAtBounds
clip: true
delegate: Rectangle {
width: view.width
height: 24
property int debugLevel: index % 3
property int timestamp: index * 1000
property int message: index
color: "darkgray"
Row {
anchors.fill: parent
Repeater {
id: delegateRepeater
property list< QtObject > roleModel: [
QtObject {
property string label: timestamp
property int itemWidth: 100
},
QtObject {
property string label: debugLevel
property int itemWidth: 100
},
QtObject {
property string label: message
property int itemWidth: view.width - 100 - 100
}
]
model: roleModel
Item {
width: itemWidth
anchors {
top: parent.top
bottom: parent.bottom
}
Text {
anchors {
fill: parent
leftMargin: 4
}
verticalAlignment: Text.AlignVCenter
text: label
elide: Text.ElideRight
}
Rectangle {
anchors {
top: parent.top
bottom: parent.bottom
right: parent.right
}
width: 1
visible: index != ( delegateRepeater.count - 1 )
color: "white";
}
}
}
}
}
}
}
}
}
There doesn't seem to be any particular part of the code that is causing the problem, removing any of the objects in the delegate reduces the probability of the subwindow closing.
I've added the debugging tag because my main problem is that this effect produces no debug output. If I add a breakpoint into the subwindow's destruction handler (Component.onDestruction) then there is a single stack entry pointing at the model: roleModel statement - but removing the entire Repeater and replacing with a copy-and-pasted equivalent yields the same results minus the stack entry.
So I would be grateful is anyone knows of a way of getting more information from this pure QML example.
As noted by #BaCaRoZzo the changing of behaviour by modifying the delegate code seems to be an unrelated side-issue.
The real cause is because it turns out you cannot create new root contexts (i.e. top-level windows) from QML. This was hinted at being resolved when Qt Quick Components were released, but the blog post boasting of Window doesn't explicitly state this. Creating a new Window and passing null for the parent technically works but the result seems to be very unstable.
Thankfully in my circumstance I'm creating a QML/C++ application so I've solved the issue by creating new root contexts from Q_INVOKABLE methods on the C++ side. But if you're developing a pure QML application, it seems that you are out of luck.
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.