ListView in subwindow triggers immediate close, or whilst scrolling - qt

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.

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)

How can I get property in gridview which is Repeater's child

how can i get gridView.itemAtIndex(index).color?
what is i have tried:
contentrepeater.itemAt(5).gridView.model.color;
contentrepeater.itemAt(5).gridView.itemAtIndex(5).color;
But it doesn't work
Rectangle {
anchors.top: bar.bottom
Layout.fillHeight: true
Layout.fillWidth: true
Repeater {
id: contentrepeater
model: 11
Rectangle {
anchors.fill: parent
color: 'red'
visible: false
GridView {
id: gridView
anchors.fill: parent
anchors.topMargin: 10
anchors.leftMargin: 10
cellWidth: 150
cellHeight: 170
clip: true
model: 11
delegate: Rectangle{
height: 160
width: 140
color: '#333333'
}
}
}
}
}
Ultimately you probably don't want to do it that way. It will be hackish and error-prone. For example GridView only provides item access based on position coordinates, not indexes. So you'd need to dive into its children which are going to be created dynamically... it's possible but very messy and not really supported API.
You are better off defining your item models first, then using the GridView (or whatever) to display them. That way you can manipulate objects in the models and changes will be reflected in the view (instead of the other way around like you're trying now).
This example (based on your posted code) creates 4 layouts with 11 squares each and animates the color in each square using a timed script. Note that we need separate instances of the models for each of the GridViews within contentrepeater (otherwise it is only shown in the last view). So the example is a bit more convoluted since the item models are being created dynamically.
I should add that in a "real" application I'd use a different method of tracking the created item models instead of looking them up in the display hierarchy like I have it here. The main point this is trying to demonstrate is to manipulate the displayed items (delegates) via changes to the model data.
import QtQuick 2.9
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.3
import QtQml.Models 2.3
Pane {
id: root
width: 400
height: 650
padding: 9
// rectangle items to create per model
property int itemsPerModel: 11
// prototype object model
property Component itemModel: ObjectModel {}
// prototype model item
property Component delegate: Rectangle {
height: 30
width: 30
color: '#333333'
}
// Creates a new ObjectModel with some Rectangle items as children
function newItemModel() {
var model = itemModel.createObject(root);
for (var i=0; i < itemsPerModel; ++i)
model.append(delegate.createObject(root));
return model;
}
SequentialAnimation {
id: animate
running: true
loops: Animation.Infinite
ScriptAction {
property string nextColor: "blue"
property int nextSet: 0
property int nextItem: 0
script: {
contentrepeater.itemAt(nextSet) // the Rectangle within the GridLayout
.children[0] // the gridView within the Rectangle
.model.get(nextItem) // the model's delegate item (a Rectangle)
.color = nextColor; // set the new color on it.
// advance to next item or set of items.
nextItem = (nextItem+1) % root.itemsPerModel;
if (!nextItem)
nextSet = (nextSet+1) % contentrepeater.count;
nextColor = (nextColor === "blue" ? "orange" : nextColor === "orange" ? "white" : "blue");
}
}
PauseAnimation { duration: 100 }
}
GridLayout {
columns: 2
anchors.fill: parent
Repeater {
id: contentrepeater
model: 4
Rectangle {
color: 'red'
width: 150
height: 170
GridView {
id: gridView
anchors.fill: parent
anchors.topMargin: 10
anchors.leftMargin: 10
cellWidth: 40
cellHeight: 40
clip: true
// here we need a unique instance of the ObjectModel
model: root.newItemModel()
}
}
}
}
}

Qt: checkbox single click not working

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.

QML - How to know if a child has keyboard focus

I think I know how to use FocusScopes and how to handle keyboard focus.
But I can't find a clever way to figure out if one of my child items
or theirs or anyone below me has keyboard focus.
The documentation for FocusScope says:
When a focus scope receives active focus, the contained element with
focus set (if any) also gets the active focus. If this element is also
a FocusScope, the proxying behavior continues. Both the focus scope
and the sub-focused item will have activeFocus property set.
A FocusScope therefore will have activeFocus set to false
when the focus was given to a contained FocusScope. Is there a way to figure out if that was the case? How can I know if at least a contained FocusScope received the focus?
Focus is a chain in QtQuick.
That means all the ancestor FocusScope down to the current active child get active focus.
FocusScope is used to make some more simple focus abstraction : tell to a custom component that when the root object gets active focus, it has to forward it to a given child.
In the following example :
import QtQuick 2.0;
Rectangle {
width: 400;
height: 200;
focus: true;
FocusScope {
id: scope1;
anchors {
top: parent.top;
left: parent.left;
right: parent.right;
bottom: parent.verticalCenter;
}
Rectangle {
id: rect1;
color: (scope1.activeFocus ? "yellow" : "gray");
border.width: 1;
anchors.fill: parent;
MouseArea {
anchors.fill: parent;
onClicked: { scope1.forceActiveFocus (); }
}
TextInput {
id: input1;
focus: true;
anchors.centerIn: parent;
}
}
}
FocusScope {
id: scope2;
anchors {
top: parent.verticalCenter;
left: parent.left;
right: parent.right;
bottom: parent.bottom;
}
Rectangle {
id: rect2;
color: (scope2.activeFocus ? "yellow" : "gray");
border.width: 1;
anchors.fill: parent;
MouseArea {
anchors.fill: parent;
onClicked: { scope2.forceActiveFocus (); }
}
TextInput {
id: input2;
focus: true;
anchors.centerIn: parent;
}
}
}
}
... we want two big areas that can have focus and we don't necessarly want to focus explicitely the inner TextInput (cause they ideally would be inside a custom component, so not accessible from outside).
So when an area gets clicked, we give the active focus to the parent scope, and the scope automatically proxies it to the child that has the focus:true flag (means that it wants the focus, not that it HAS it, that's why we have one flag in each TextInput).
The items that need to know if the inner input has active focus will instead simply request if the scope has it. They don't have to care was is inside.
If the scope contains another scope with focus:true, the focus is forwarded again until it reaches the latest item that wants focus.

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