How to access dynamically/randomly loaded Repeater items in QML? - qt

The answer provided by #TheBootroo here: link
provides a way to load and change between QML files/screens/views. But when doing it like this how can one use signal and slots?
One can access the items created by the repeater by using the Repeater::itemAt(index) method, but since I don't know in what order the items are loaded I don't know what index screen2, screen3, screen4 etc. is at.
Is there any way to solve this or do one have to instantiate all the screens in memory at start up?
My code below:
main.qml:
//List of screens
property variant screenList: [
"main",
"screen2",
"screen3",
...
]
//Set this to change screen
property string currentScreen: "main"
Repeater {
id: screens
model: screenList
delegate: Loader {
active: false;
asynchronous: true
anchors.fill: parent
source: "%1.qml".arg(modelData)
visible: (currentScreen === modelData)
onVisibleChanged: {
loadIfNotLoaded()
}
Component.onCompleted: {
loadIfNotLoaded()
}
function loadIfNotLoaded () {
//To load start screen
if(visible && !active) {
active = true;
}
}
}
}
Connections {
target: screens.itemAt(indexHere)
//screen is here the string passed with the signal from screen2.
onChangeScreen: currentScreen = screen
}
Button {
id: button1
text: "Go To Screen 2"
onClicked: {
currentScreen = "screen2"
}
}
And in screen2.qml:
signal changeScreen(string screen)
Button {
text: "Go To Main"
onClicked: {
changeScreen("main")
}
}

One can access the items created by the repeater by using the Repeater::itemAt(index) method, but since I don't know in what order the items are loaded I don't know what index screen2, screen3, screen4 etc. is at.
The order of the items the Repeater instantiates is actually defined - the items will be instantiated in the order of the model, so in your case "main" will be the first, then "screen2" and so on. Now, each item inside of the Repeater is a Loader - the Repeater will create 3 Loaders in a defined order. The Loaders themselves load their source item on demand.
I think the only thing missing in your code is that the Connections refers to the Loader, but you need it to refer to the item the Loader creates. So change the Connections to
Connections {
target: screens.itemAt(indexHere).item
onChangeScreen: currentScreen = screen
}
Notice the additional .item.
After fixing this, there is one additional problem though: The Repeater hasn't yet instantiated its items when the Connections element is created, so screens.itemAt(indexHere) will return null. One way to fix this would be to use a currentIndex property instead of a currentScreen property and use that in the binding for target, and set currentIndex after the Repeater has instantiated its items:
property int currentIndex: -1
...
Connections {
target: screens.itemAt(currentIndex).item
...
}
Component.onCompleted: currentIndex = 0
Even easier would probably be to put the Connections element inside the Repeater's delegate, i.e. have 3 Connections instead of one (assuming you have 3 screens).

Related

pass data from one window to another (inside same QML file)

i got two Windows inside the same .qml file.
Window1 has a textinput1 and a button, and when I press the button, i'd like to send the string value from that textinput to the Window2 textInput2.
I'm new to Qt and QML, been reading a lot on signals, Loaders, properties and can't materialize this kind of transfer. Can anyone do a simple 10-line example of such transfer please?
Window {
id:window
TextInput {
id:text1
text: "This value is needed in the second Window!"
}
Button {
onClicked: window2.open()
}
Window {
id.window2
function open(){
visible = true
}
Text {
text: text1.text
}
}
}
If I do this it gives me ReferenceError: text1 is not defined, how can I reference the text1 from the first Window?
I would prefer to use signals in such case:
Window {
id: window1
title: "window 1"
visible: true
width: 600
height: 600
signal someSignal()
Button {
anchors.centerIn: parent
text: "Open window"
onClicked: window1.someSignal()
}
Window {
id: window2
title: "window 2"
width: 400
height: 400
// you can use this instead of Connections
//Component.onCompleted: {
// window1.someSignal.connect(show);
//}
}
Connections {
target: window1
onSomeSignal: {
window2.show();
}
}
}
I think this is more ... how do you say? ... more imperative -)
i got two Windows inside the same .qml file.
If you did then your code will work. Since it doesn't work, I will assume each window is defined in its own qml file, and you only instantiate the two windows in the same qml file.
If I do this it gives me ReferenceError: text1 is not defined, how can
I reference the text1 from the first Window?
You will have to be able to reference the window first, and it should provide an interface to reference the text.
Keep in mind that ideally ids should only be used to reference stuff in the same source. On rare occasions you could go further, and reference ids down the object tree, but only parents, and none of their out-of-line children, it will however work for in-line children that are given ids in that same source. Meaning that if window2 is created inside window then you will be able to reference window from inside window2. But if both windows are siblings in another object, the id won't resolve.
Obj1
Obj2
Obj4
Obj3
In this example object tree, Obj1 will resolve from any of the objects. However, Obj3 will not be able to resolve Obj2 if the id is given inside Obj2, but will resolve if the id for Obj2 is given inside Obj1. But there is no way to resolve Obj4 from Obj3. because the id doesn't act like a property, you can't do someId.someOtherId, that's only possible for properties. You cannot do somePropertyObject.someId neither. You can only begin with either an id or a property, and continue only with sub-properties.
When the id is not applicable, can expose objects or properties either as properties or property aliases. The first is useful when you want to expose a whole object, the second when you want to expose a particular property of an object:
Item {
property Item innerItem: inner // gives you access to the "entire" inner object
property alias innerWidth: inner.width // gives you access to a property of inner
Item {
id: inner
}
}
You can also have aliases to aliases.

How GUI screen transition works in qml

I'm a C++ developer, now studying about GUI development using QML in QtQuick.
In GUI creation, only one screen is visible to the user.
And based on user interaction, the screens are switched.
But what actually happens behind?
There are lot of info only on how to design a single screen, but very less resource for how to manage the transitions of their states.
Are all the screens and components loaded when starting the application and change the layer order to display once screen,
OR
after an user action, the new screen is built, loaded and old is destroyed ( only one screen is in memory at a time)
What is the term for this type of handling.
It would be so helpful to point to where i can find such information.
If you can't understand my question,please let me know. I will rewrite again!!
There is a convenient ready-made solution available: StackView. It provides built-in transitions for pages that slide/fade in and out.
StackView {
id: stack
initialItem: Page {
Button {
text: "Push"
anchors.centerIn: parent
onClicked: stack.push(Qt.resolvedUrl("OtherPage.qml"))
}
}
}
StackView allows you to push items, URLs and components. When pushing either of the latter two, StackView automatically creates and destroys the instance when appropriate. For example, if you push multiple URLs or components, it will only instantiate the top-most one that becomes the current item on the stack. Once you pop items off the stack, it creates an instance of the item underneath on demand once it becomes the current top-most item on the stack. StackView also allows you to replace one or more items in the stack. When popping or replacing dynamically created items off the stack, it automatically destroys the instances after the respective transitions are finished.
One of the possible options to switch between different screens using states:
ColumnLayout {
id: controls
states: [
State {
id: state1
name: "STATE1"
property list<Item> content: [
Loader {
...
},
MyItem {
...
}
]
PropertyChanges {
target: controls
children: state1.content
}
},
State {
id: state2
name: "STATE2"
property list<Item> content: [
MyHud {
...
}
]
PropertyChanges {
target: controls
children: state2.content
}
}
]
}
You can use Loader to load different qml-files or qml-components.
Example:
import QtQuick 2.0
Item {
width: 200; height: 200
Loader { id: pageLoader }
MouseArea {
anchors.fill: parent
onClicked: pageLoader.source = "Page1.qml"
}
}

Crash in QQuickItem destructor / changeListeners when closing application (Qt 5.6)

We have a fairly big QtQuick application, with a lot of modal dialogs. All of these modals share a consistent look and behaviour, and have leftButtons, rightButtons, a content and additional warning widgets. We use the following base class (PFDialog.qml):
Window {
property alias content: contentLayout.children
ColumnLayout {
id: contentLayout
}
}
and declare dialogs in the following way (main.qml):
Window {
visible: true
property var window: PFDialog {
content: Text { text: "Foobar" }
}
}
The problem is that when the application is closed, a segfault happens in the QQuickItem destructor. This segfault is hard to reproduce, but here is a surefire way of making it happen: with visual studio in debug mode, freed memory is filled with 0xDDDDDDD with triggers the segfault every time.
Full example application can be found here: https://github.com/wesen/testWindowCrash
The crash happens in QQuickItem::~QQuickItem:
for (int ii = 0; ii < d->changeListeners.count(); ++ii) {
QQuickAnchorsPrivate *anchor = d->changeListeners.at(ii).listener->anchorPrivate();
if (anchor)
anchor->clearItem(this);
}
The reason for this is that the content of our dialog (the Text item in the example above) is a QObject child of the main Window, but a visual child of the dialog window. When closing the application, the dialog window is destroyed first, and at the time the Text item is deleted, the dialog window (still registered as a changeListener) is stale.
Now my question is:
is this a QtQuick bug? Should the dialog deregister itself as a changeListener for its children when it is destroyed (I think it should)
is our property alias content: layout.children pattern correct, or is there a better way to do this? This also happens when declaring a default property alias.
For the sake of completeness, here is how we hotfix it in our application. When content changes, we reparent all the items to the layout item. A of elegance, as you will all agree.
function reparentTo(objects, newParent) {
for (var i = 0; i < objects.length; i++) {
qmlHelpers.qml_SetQObjectParent(objects[i], newParent)
}
}
onContentChanged: reparentTo(content, contentLayout)
I have had this problem lots of times, I don't think it is a bug, more like a design limitation. The more implicit behavior you get, the less control you have, leading to inappropriate orders of object destruction and access to dangling references.
There are numerous situations where this can happen "on its own" as you exceed the bounds of a trivial "by the book" qml application, but in your case it is you who's doing it.
If you want proper ownership, don't use this:
property var window: PFDialog {
content: Text { text: "Foobar" }
}
Instead use this:
property Window window: dlg // if you need to access it externally
PFDialog {
id: dlg
content: Text { text: "Foobar" }
}
Here is a good reason why:
property var item : Item {
Item {
Component.onCompleted: console.log(parent) // qml: QQuickItem(0x4ed720) - OK
}
}
// vs
property var item : Item {
property var i: Item {
Component.onCompleted: console.log(parent) // qml: null - BAD
}
}
A child is not the same as a property. Properties are still collected but they are not parented.
As for achieving the "dynamic content" thingie, I've had good results with ObjectModel:
Window {
property ObjectModel layout
ListView {
width: contentItem.childrenRect.width // expand to content size
height: contentItem.childrenRect.height
model: layout
interactive: false // don't flick
orientation: ListView.Vertical
}
}
Then:
PFDialog {
layout: ObjectModel {
Text { text: "Foobar" }
// other stuff
}
}
Lastly, for the sake of doing explicit cleanups before closing the application, on your main QML file you can implement a handler:
onClosing: {
if (!canExit) doCleanup()
close.accepted = true
}
This ensures the window will not be destroyed without doing the cleanup first.
Finally:
is our property alias content: layout.children pattern correct, or is
there a better way to do this? This also happens when declaring a
default property alias.
It wasn't last time I looked into it, but it was at least couple of years back. It would certainly be nice to have objects declared as children actually becoming children of some other object, but at the time this was not possible, and still may not be. Thus the need for the slightly more verbose solution involving the object model and the list view. If you investigate the matter and find something different, leave a comment to let me know.
I believe that you cannot declare a Window Object in a var. In my tests the SubWindow never open and sometimes broken on startup.
A Window can be declared inside an Item or inside another Window; in that case the inner Window will automatically become "transient for" the outer Window
See: http://doc.qt.io/qt-5/qml-qtquick-window-window.html
Modify your code to this:
Window {
visible: true
PFDialog {
content: Text { text: "Foobar" }
}
}

Qt 5 QML app with lots of Windows or complex UIs

In QtQuick 2 using the QtQuick Controls you can create complex desktop apps. However it seems to me that the entire UI must be declared and create all at once at the start of the app. Any parts that you don't want to use yet (for example the File->Open dialog) must still be created but they are hidden, like this:
ApplicationWindow {
FileDialog {
id: fileOpenDialog
visible: false
// ...
}
FileDialog {
id: fileSaveDialog
visible: false
// ...
}
// And so on for every window in your app and every piece of UI.
Now, this may be fine for simple apps, but for complex ones or apps with many dialogs surely this is a crazy thing to do? In the traditional QtWidgets model you would dynamically create your dialog when needed.
I know there are some workarounds for this, e.g. you can use a Loader or even create QML objects dynamically directly in javascript, but they are very ugly and you lose all the benefits of the nice QML syntax. Also you can't really "unload" the components. Well Loader claims you can but I tried it and my app crashed.
Is there an elegant solution to this problem? Or do I simply have to bite the bullet and create all the potential UI for my app at once and then hide most of it?
Note: this page has information about using Loaders to get around this, but as you can see it is not a very nice solution.
Edit 1 - Why is Loader suboptimal?
Ok, to show you why Loader is not really that pleasant, consider this example which starts some complex task and waits for a result. Suppose that - unlike all the trivial examples people usually give - the task has many inputs and several outputs.
This is the Loader solution:
Window {
Loader {
id: task
source: "ComplexTask.qml"
active: false
}
TextField {
id: input1
}
TextField {
id: output1
}
Button {
text: "Begin complex task"
onClicked: {
// Show the task.
if (task.active === false)
{
task.active = true;
// Connect completed signal if it hasn't been already.
task.item.taskCompleted.connect(onTaskCompleted)
}
view.item.input1 = input1.text;
// And several more lines of that...
}
}
}
function onTaskCompleted()
{
output1.text = view.item.output1
// And several more lines...
// This actually causes a crash in my code:
// view.active = false;
}
}
If I was doing it without Loader, I could have something like this:
Window {
ComplexTask {
id: task
taskInput1: input1.text
componentLoaded: false
onCompleted: componentLoaded = false
}
TextField {
id: input1
}
TextField {
id: output1
text: task.taskOutput1
}
Button {
text: "Begin complex task"
onClicked: task.componentLoaded = true
}
}
That is obviously way simpler. What I clearly want is some way for the ComplexTask to be loaded and have all its declarative relationships activated when componentLoaded is set to true, and then have the relationships disconnected and unload the component when componentLoaded is set to false. I'm pretty sure there is no way to make something like this in Qt currently.
Creating QML components from JS dynamically is just as ugly as creating widgets from C++ dynamically (if not less so, as it is actually more flexible). There is nothing ugly about it, you can implement your QML components in separate files, use every assistance Creator provides in their creation, and instantiate those components wherever you need them as much as you need them. It is far uglier to have everything hidden from the get go, it is also a lot heavier and it could not possibly anticipate everything that might happen as well dynamic component instantiation can.
Here is a minimalistic self-contained example, it doesn't even use a loader, since the dialog is locally available QML file.
Dialog.qml
Rectangle {
id: dialog
anchors.fill: parent
color: "lightblue"
property var target : null
Column {
TextField {
id: name
text: "new name"
}
Button {
text: "OK"
onClicked: {
if (target) target.text = name.text
dialog.destroy()
}
}
Button {
text: "Cancel"
onClicked: dialog.destroy()
}
}
}
main.qml
ApplicationWindow {
visible: true
width: 200
height: 200
Button {
id: button
text: "rename me"
width: 200
onClicked: {
var component = Qt.createComponent("Dialog.qml")
var obj = component.createObject(overlay)
obj.target = button
}
}
Item {
id: overlay
anchors.fill: parent
}
}
Also, the above example is very barebone and just for the sake of illustration, consider using a stack view, either your own implementation or the available since 5.1 stock StackView.
Here's a slight alternative to ddriver's answer that doesn't call Qt.createComponent() every time you create an instance of that component (which will be quite slow):
// Message dialog box component.
Component {
id: messageBoxFactory
MessageDialog {
}
}
// Create and show a new message box.
function showMessage(text, title, modal)
{
if (typeof modal === 'undefined')
modal = true;
// mainWindow is the parent. We can also specify initial property values.
var messageDialog = messageBoxFactory.createObject(mainWindow, {
text: text,
title: title,
visible: true,
modality: modal ? Qt.ApplicationModal : Qt.NonModal
} );
messageDialog.accepted.connect(messageDialog.destroy);
messageDialog.rejected.connect(messageDialog.destroy);
}
I think loading and unloading elements is not actual any more because every user have more than 2GB RAM.
And do you think your app can take more than even 512 MB ram? I doubt it.
You should load qml elements and don't unload them, no crashes will happens, just store all pointers and manipulate qml frames.
If you just keep all your QML elements in RAM and store their states, it will works faster and looks better.
Example is my project that developed in that way: https://youtube.com/watch?v=UTMOd2s9Vkk
I have made base frame that inherited by all windows. This frame does have methods hide/show and resetState. Base window does contains all child frames, so via signal/slots other frames show/hide next required frame.

Get index of the delegate currently displayed - QML ListView

I created a ListView, which displays a couple of pages of content defined by the user (plain text). The page displayed is a delegate. Only one page is visible at a time. I decided to use it to get snapping to one item, in the same way the iOS' launcher works. The user simply flicks between the pages. (this is to be used on touch screens)
I need to have the index of the currently displayed page for some operation. currentIndex of the ListView always stays == 0. How can I get it?
For those who prefer code:
ListView
{
onCurrentIndexChanged: console.log(currentIndex) // this gets called only once - at startup
delegate: Column
{
// The page displayed, only one page at a time
}
}
Thanks
There are many ways to get the index of current item that is displayed in the screen. If you can get the x-y coordinate of current page, you can use indexAt method in ListView.
And in each delegate, you can find the index using index role within the scope of the delegate. The index is like a role you declared in your model, and is automatically assigned by ListView. For example,
ListView
{
delegate: Column
{
property int indexOfThisDelegate: index
//...
}
}
The index role is introduced here:
A special index role containing the index of the item in the model is also available to the delegate. Note this index is set to -1 if the item is removed from the model...
Another way is to explicitly assign value to the currentItem property in ListView, so the view can scroll by itself. Here is an simple example in Qt documentation, which is similar to your application.
I know this is quite old but I had the same problem and spend some time trying to find a way to get currentIndex that would work for me. In my case sometimes I need to change the width of my ListView so I have to recalculte currentIndex manualy every time I resize it.
But I found a highlightRangeMode property. When it's set to ListView.StrictlyEnforceRange then currentIndex is always updated automaticly and contains correct index of the currently visible item.
ListView {
highlightRangeMode: ListView.StrictlyEnforceRange
// ...
}
You can do like that:
QModelIndex index =this->indexAt(event->pos());
this ->setCurrentIndex(index);
You can use attached properties of ListView class (ListView). They are attached to each instance of the delegate.
See ListView.isCurrentItem or ListView.view example:
ListView {
width: 180; height: 200
Component {
id: contactsDelegate
Rectangle {
id: wrapper
width: 180
height: contactInfo.height
color: ListView.isCurrentItem ? "black" : "red"
Text {
id: contactInfo
text: name + ": " + number
color: wrapper.ListView.isCurrentItem ? "red" : "black"
}
}
}
model: ContactModel {}
delegate: contactsDelegate
focus: true
}

Resources