Wrong filling order when loading ListView elements with Loader - qt

I'm using a Loader in a ListView delegate to increase the performance when loading expensive delegate models.
My code looks like this:
delegate: Component {
Loader {
asynchronous: true
sourceComponent: MyDelegateComponent {}
}
}
The model comes from C++ (QAbstractListModel implementation).
The problem is that the filling order of the list is wrong. It comes from bottom to top, creating a strange view for the user, who needs to wait to see the first list element.
Is it possible to change this filling order to load top element first? The model in the C++ data is well ordered.

Related

Binding not updating when there is frame dropping in QML

I have a UI element, which appears while I do some calculation and disappears when the calculation finished. The appearing and disappearing is animated. The animation animates the item's implicitHeight from 0 to X and vice-versa. These items live in a ListView as delegates. The ListView is wrapped in an Item as the root element of this component. The implicitHeight of this root element depends on, aka is binded to the contentHeight of the ListView. The width of this root element is set where the component is used. Here you can see my explanation in code:
Item {
id: _root
implicitHeight: _listView.contentHeight
ListView {
id: _listView
width: _root.width
height: contentHeight
delegates: AnimatingItem {
// ...
}
}
}
The AnimatingItems in the code have predefined height (not necessarily the same for all). I wrote a small debug code piece which basically writes the ListView's contentHeight, childrenRect.height, height and the root item's implicitHeight to the console on a button press.
In most cases when ever I press this button to print out those values, like during a calculation when the item is present (= X) or when there is no calculation therefore the item is hidden (= 0), they all match.
In certain scenarios when I do some really heavy calculation though, when even the UI freezes/drops frames and the calculation finishes and I press the debug button all the ListView's values match (= 0), but the items's implicitHeight differs (= X). The weird thing is that the AnimatingItem's or in other words the ListView disappears thus there is no visible item, but if I anchor a Rectangle to the top of this item then it will float in the air instead of moving down as the ListView disappears.
Is it possible that if I have a really heavy calculation one or more bindings can "forget" to update due to dropping frames?
On some platforms animations don't run in a dedicated thread, but I don't think that's the case of Mac OS. So dropping frames due to lack of graphics performance should not cause skips in binding evaluations. Now if you have a CPU hotspot as a cause of the dropped frame, that's a different story. The animation is synced to the event loop rate as well as the scenegraph rate, so if your event loop is stalling, then it is simply not making the value change, which is why you don't get reevaluations.
As a rule of thumb, you shold never ever do heavy calculations in the main/gui thread. If it causes the GUI to freeze for more than 10 msec you need to offload it to a dedicated thread, and update the results asynchronously. Don't stall or block the gui thread!
Also, binding to contentHeight hasn't work as expected for me in many of the cases. What has proven to work is binding to contentItem.childrenRect.height. Also, the root item is completely redundant if it is going to be just an empty item, but even if it will have direct children, you can do that with the list view which is also an Item.
If you scale the view to the full height, I'd recommend to use a Column with a Repeater instead. A simple repeater will take advantage of all the model features and efficiencies too, so no worries there.

What is best way to load either Rectangle(drawn by code) or Image in Qml?

I have a Qml file with one 'Rectangle' and an 'Image'. I want to load either one based on the property set in my.cpp file.
Please help me to find a best way to do this.
Actually I could think two possible ways to do the same:
1) First approach is to have both the element (the image and rectangle), defined in the respective QML, and to control their visibility from my.cpp file. I can have a property, this property can control the visibility of either of the two. Drawback in this approach is that even though only one element has to be displayed, two will be created.
2) Second approach is that we can have two components and load either one using "Loader" depending on the property set from the my.cpp.
Ex:
'
Component
{
id:img
Image
{
id: myImage
source:currentdir + "/img_production/Separator/myImage.png"
width: 10
height: 79
}
}
Component
{
id:rect
Rectangle
{
id:re
height: 82
width: 10
color: "#FFFFFF"
}
}
Loader
{
id: itemDisplay
sourceComponent: style.flag? rect : img
anchors.fill: parent.fill
}
'
Looking for some expert suggestions.
PS: style.flag is property set by my.cpp to Qml.
In this case, where both items are simple base types, I would go for the visibility change.
Having both elements instantiated directly makes it easier to refer to them in bindings or bind to their properties.
It also means their allocation only happens once, reducing the chance of memory fragmentation
If you are worried about the image consuming too much memory while the rectangle is shown you could still make the image`s source property depend in the visiblity value, i.e. unload the image when not showing the Image element.

ListView positionViewAtIndex() not working

I have two ListViews in a QML project that are both running off of the same model. I am trying to get them to start out at different indices (the model starts with 2 ListElements in it). In order to do this, I call positionViewAtIndex when the component completes:
ListView {
model: mymodel
Component.onCompleted: positionViewAtIndex(1,ListView.Beginning)
//...
}
However, neither ListView actually is positioned at the desired index. Is there something I'm not doing? The only solution that I have seen for this problem is to ensure that you're not calling the method before the ListView completes, but I am doing that.
I am using Qt 5.2/QtQuick 2.0.
Edit: After playing around with the other positioner functions, I have found that none of them work. I have also found that changing currentIndex does not work either. Furthermore, I have found that currentIndex is not being changed with the view -- onCurrentIndexChanged is never being fired.
So, I figured it out. It turns out that a ListView instantiates its delegates before it worries about its own properties...so the delegate was only reading off of the ListView's width before the ListView set its own dimensions. When a delegate has a width/height property in the orientation of the view equal to zero, the view will not know where to scroll to when positionViewAtIndex() is called. So, in order to fix this, you have to use a conditional binding:
Component {
id: myDelegate
Item {
width: ListView.view.width == 0 ? 480 /*or some preset*/ : ListView.view.width
}
}
This will give the delegate a nonzero width and cause the positionViewAtIndex() function to work.
Of course, if your ListView is vertical, then you need to set the height property and not the width property.
Alternatively you can set currentIndex to 1

Why is a dynamically by C++ generated QML Item not aware of other pre-existing Items?

I have a QQuickView that displays a QML file which itself consists of several QML Items (in separate files). I'd like to add Items dynamically using C++ code. The dynamically added item should resize with the parent one, i.e. width and height properties reference parent.
For example, my target Item in QML looks like this:
// TargetContainer.qml
Grid {
id: fillMeWithItemsContainer
objectName: "fillMeWithItemsContainer"
}
The Item I want to add dynamically (maybe multiple times) looks like this:
// DynamicItem.qml
Rectangle {
color: "white"
height: fillMeWithItemsContainer.height
width: height * 4/3
}
Note that the rectangle references the container it is intended to reside in regarding height.
quickView is populated with TargetContainer:
QQuickView *quickView = new QQuickView();
quickView->setSource(QUrl("qrc:/foo/bar/TargetContainer.qml"));
So I load a component
QQmlComponent dynamicallyLoadedComponent(
quickView->engine(),
QUrl("qrc:/foo/bar/DynamicItem.qml")
);
And I create an Object out of it.
QObject *dynamicallyLoadedObject = dynamicallyLoadedComponent.create();
Here I get an error (in application output view):
DynamicItem.qml:4: ReferenceError: fillMeWithItemsContainer is not defined
quickView should be aware of the existence of fillMeWithItemsContainer, because it has been created before. However, fillMeWithItemsContainer is not a parent of dynamicallyLoadedObject (yet) and this could be the problem.
So I find the target Item by
QQuickItem *targetItem = quickView->rootObject()->findChild<QQuickItem*>("fillMeWithItemsContainer");
And reparent the previously created object
dynamicallyLoadedObject->setProperty("parent", QVariant::fromValue<QObject*>(targetItem ));
Note: I tried dynamicallyLoadedObject->setParent() before, but this seems to be a different kind of parent (QObject vs. parent property).
However, the width and height properties of dynamicallyLoadedObject are set to 0 (because of the reference error, I assume) and won't change. Even if I set them again programatically
dynamicallyLoadedObject->setProperty("height", "fillMeWithItemsContainer.height;");
dynamicallyLoadedObject->setProperty("width", "height * 4/3");
nothing changes.
If I define DynamicItem directly in QML it works:
Grid {
id: fillMeWithItemsContainer
objectName: "fillMeWithItemsContainer"
DynamicItem {}
}
How do I make sure that dynamically added items can access Items that have been in the QML view before? Alternatively: What am I doing wrong?
dynamicallyLoadedObject->setProperty("height", "fillMeWithItemsContainer.height;");
dynamicallyLoadedObject->setProperty("width", "height * 4/3");
This will not actually set a JavaScript binding on the properties. Instead, it will try to assign e.g. the string "fillMeWithItemsContainer.height;" to the property, which will fail since the property is of type int, not of type QString.
Assigning bindings to properties is actually not possible from within C++ (with some exceptions like QQmlBinding).
dynamicallyLoadedObject->setProperty("parent", QVariant::fromValue<QObject*>(targetItem ));
As Sergei mentioned, you need to call QQuickItem::setParentItem instead of setting the parent property. That is also a bit more typesafe than the general string-based setProperty API. Without a parent item, a QQuickItem will not be visible.
Reparenting will only change the parent item, which will affect layouting and a few other things. It will not change the context of the object. A context defines which objects/IDs are in the scope. The context can not be changed after an item has been created. Even if changing the parent would change the context, it is too late - the object has been created, and IDs/object are only looked up in the creation phase.
The solution is to pass the correct context to QQmlComponent::create(), which actually has an optional argument. You need to create your item in the context of fillMeWithItemsContainer, so you need to get a pointer to it (you did that already with findChild) and then retrieve its context, which is possible with QQmlEngine::contextForObject(). That should give you enough to figure out how to make it work.
I agree with Sergei though, you should prefer to dynamically create objects in JavaScript instead. Changing QML from within C++ is a layering violation, you should never access QML from C++, only the other way around, to have a nicer separation between UI and program logic.
I believe, QML engine treats DynamicItem instance as a non-graphical item since it is casted to QObject*. Thus it is not rendered. It has to be at least QQuickItem* to be rendered.
I believe you experience same problem with setParent() since parent property refers to parent QQuickItem and might no be the same as QObject parent.
Two questions:
Why wouldn't you create dynamic objects in JS?
Is it possible to use relative parent instead of absolute fillMeWithItemsContainer?
p.s. I assume you understand this is rather irregular way of using QML and have strong reasons for such hacky approach.

QML : If condition in delegate?

I'm designing a spinner list control, which displays 3 items at a time.
Its working fine as required behaviour the only issue am facing is I need the central element appearance little bigger.
The approach which I can think as of now is to have an if condition in the delegate, which on the basis of current index increases the font size.
Is the above approach is possible? Any suggestions to achieve the particular behaviour
Below is the code snippet
SpinnerData {
id: spinner
focus: true
model: 20
delegate: Text { font.pixelSize: spinner.height/4.5; text: index; height: spinner.height }
}
I don't know the details of your component but here you can see implementation of the same control in Qt Quick Components.

Resources