Stackview Push and Pop destroys and creates a new page - qt

I have many pages and when a button is clicked I make transitions between these pages with a StackView, push and pop. And in these pages when corresponding buttons are clicked I make these buttons red. However, when I pop and re-opened the same page with a push, button is no longer red. So that makes me think that pop and push destroys and creates a new page which is the opposite what is written in docs:
This means that any item pushed onto a StackView will never be
destroyed by the StackView; only items that StackView creates from
Components or URLs are destroyed by the StackView.
Here is the code for stackview:
Window{
id:main
property bool isAbsOffRoad: false
StackView{
id:contentFrame
initialItem:Qt.resolvedUrl("qrc:/MainPage.qml")
Connections{
target:contentFrame.currentItem
onBackButtonPressed:{
contentFrame.pop() }
}}
And then here is how I push:
Item {
id:backgroundItem
LeftButtons{
id:buttonSettings
MultiPointTouchArea{
onPressed{
contentFrame.push("qrc:/SettingsPage.qml")}
}}
I cannot see a reason why the page doesn't preserves it's state when popped and pushed back. What might be the reason?
Another question is: I get a
QML Connections: Cannot assign to non-existent property
"onBackButtonPressed".
However, back buttons work. Why I get that error?

The documentation you quote gives you the answer.
This means that any item pushed onto a StackView will never be destroyed by the StackView; only items that StackView creates from Components ->or URLs<- are destroyed by the StackView.
If a StackView creates an item from a URL, it will have ownership of it, and therefore feel free to destroy it.
Your code shows this:
initialItem:Qt.resolvedUrl("qrc:/MainPage.qml")
So you're giving the StackView a URL to your QML. If you don't want it to do that, try doing something like this instead:
initialItem: MainPage {}
That way, the StackView will be given a fully constructed item, and it won't try to destroy it.
For your second question, I'm guessing that your MainPage.qml does not define that signal. You could create that signal in MainPage just to remove the warning, or you can try adding the ignoreUnknownSignals property to your Connections object.
UPDATE:
You can still use push and pop. You just have to provide a created instance of your item, not just the item type. You could try something like this, for example:
component SomePage: Rectangle {
signal clicked()
MouseArea {
anchors.fill: parent
onClicked: parent.clicked()
}
Component.onCompleted: {
console.log("Created: " + color);
}
Component.onDestruction: {
console.log("Destroyed: " + color);
}
}
SomePage {
id: bluePage
color: "blue"
visible: false
onClicked: contentFrame.push(redPage)
}
SomePage {
id: redPage
color: "red"
visible: false
onClicked: contentFrame.pop();
}
StackView {
id: contentFrame
anchors.fill: parent
initialItem: bluePage
}

Related

How to access containing QML StackView?

I am new to QML, I have a Component which is pushed on a StackView. I'd like from this component to access the StackView containing it.
Here is a code that works
import QtQuick 2.11
import QtQuick.Controls.2.2
ApplicationWindow {
id: window
StackView {
id: stackView
initialItem: test1
anchors.fill: parent
}
Component {
id: test1
Button {
text: "Go to test2"
onClicked: stackView.push(test2)
}
}
Component {
id: test2
Button {
text: "Back to test1"
onClicked: stackView.pop()
}
}
}
However, I'd like to avoid accessing stackView by its id
Stack.view seems to be what I'm looking for, but I have no idea how to use it. I tried all of the following (replacing Buttons' onClicked) :
Stack.view.push(test2)
view.push(test2)
test1.Stack.view.push(test2)
test1.view.push(test2)
None of these work.
Am I misunderstanding something ? How am I supposed to use Stack.view ?
Edit : this question looks really close to mine : Access QML StackView from a control
I could indeed use a property to keep a reference to my StackView, but I would still like to avoid that if possible.
The accepted answer says that root.StackView.view.pop() is the correct method. I assume root is the Page's id from this post, so I tried test1.StackView.view.push(test2), but this still doesn't work. (Also tried with root, but it's not better)
Be aware that this Q/A is about the QtQuick.Controls 2.x
I also think it is good style to first use the attached property, rather than doubling this functionality by adding own, custom properties.
The problem is, that you are using the attached property in the wrong place - it is attached to the Item that is pushed onto the StackView and not to any of its children. So to use the attached property, you need to specify an identifier of this Item first.
In the example, that has been linked, this is root. But it is not the id of the Component-object. It has to be the root-node of the content of the Component-object.
You should have something like this:
Component {
id: myComponent
Item { // The Page. To this the attached property will be attached.
id: myComponentRoot // Use this to identify the root node of the pushed item.
Item { // Some more layers ...
...
Button { // Here you now want to access it perhaps?
onClicked: myComponentRoot.StackView.view.pop() // Reference the root node of the component to access it's attached properties.
}
}
}
}
A simple way of convenient using StackView.view is by assigning it to a property. In the following, I deliberately did not give the parent StackView an id. I can create a property in the sub-pages to grant access to the StackView as follows:
property StackView stackView: StackView.view
Here's a fully working example:
import QtQuick
import QtQuick.Controls
Page {
StackView {
anchors.fill: parent
initialItem: "Test1.qml"
}
}
//Test1.qml
import QtQuick
import QtQuick.Controls
Page {
property StackView stackView: StackView.view
Button {
anchors.centerIn: parent
text: "Go to test2"
onClicked: stackView.push("Test2.qml")
}
}
//Test2.qml
import QtQuick
import QtQuick.Controls
Page {
property StackView stackView: StackView.view
Button {
anchors.centerIn: parent
text: "Back to test1"
onClicked: stackView.pop()
}
}
You can Try it Online!

Stackview calling slots for previous screen

I am implementing stackview in my application.
SwipeView {
id: swipeView
anchors.fill: parent
currentIndex: showfooter.currentIndex
DashboardListView{
id:dashboard
}
Settings{
id:setting
}
Cart{
id:cart
}
}
StackView {
id: stackView
initialItem: Pane {
id: pane
}
}
When i am loading some other screen(like SightDescription.qml) from DashboardListView using push method and cliking somewhere on that screen its calling slots for DashboardListView. DashboardListView Screen controls are getting onclick signal. Is there any setting related to stack view that I need to do, I read stackview's documentation but did not find anything to restrict this behavior.
It seems like the Pane is usually intercepting the mouse events, so the lower Items cannot receive them.
When you push the new item on the StackView the Pane becomes visible: false and therefore does not care for input anymore. If the new Item does not handle the mouse events, they will propagate to the lower Item.
To prevent that, you have various options:
Make sure that all Items pushed on the StackView will handle mouse events, e.g. by making a Pane or a MouseArea the root item.
Place a MouseArea directly below the StackView that is only enabled when there are Items on the StackView
Some more... e.g. installing EventFilters in C++ e.t.c. but I think 1 and 2 should be suffice and be easy to implement.

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"
}
}

Catching mouse events from QML

I want to create a QML item which disappears when the mouse moves outside of it. Here is my code:
Item {
id: disappearing_element
ListView { ... }
MouseArea {
id: collapser
anchors.fill: parent
propagateComposedEvents: true
hoverEnabled: true
onExited: {
disappearing_element.visible = false
}
}
}
It works well, but MouseArea propagates events like onClicked() onDoubleClicked() only (as said in Qt docs).
Is there a way to notify disappearing_element's childrens about mouse enter and mouse exit events (without using a Popup element)?
I think this is one of the common needs when developing QtQuick apps. One solution we currently use quite often is to add MouseArea in each of the children that need check mouse containment, and emit signals (and catch these signals in your main item) when the mouse enters or exits.
Things go a bit complicated when the children items also need such mechanism to manage their children. However, for common usage, this approach is enough for us right now.

QML ListView method positionViewAtEnd() does exactly the opposite

I'm going crazy. I have a ListView inside a ScrollView, hooked up to a model that inherits QAbstractListModel. When objects are added to the model, the ListView shows them using a delegate. So far, so good.
But I really want the view to stay scrolled to the bottom (like a chat window), and I'm having a very difficult time making that happen. Here is the relevant QML code:
Rectangle {
ScrollView {
[anchor stuff]
ListView {
id: messageList
model: textMessageFiltered
delegate: messageDelegate
}
}
TextField {
id: messageEditor
[anchor stuff]
onAccepted: {
controller.sendTextMessage(text)
text = ""
/* This works. */
//messageList.positionViewAtEnd();
}
}
Component {
id: messageDelegate
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
color: "white"
height: nameText.height + 4
Text {
id: nameText
wrapMode: Text.Wrap
text: "<b>" + authorName + " (" + authorId + ")</b> " + message
[anchor stuff]
}
ListView.onAdd: {
console.log("This prints just fine!")
messageList.positionViewAtEnd()
}
}
}
}
The really strange thing, is that messageList.positionViewAtEnd() (at the end of the file) actually jumps it to the beginning. Without the call, the view stays where it is, even as new entries appear in the list. And indeed, if you look at the Qt documentation for the ListView.positionViewAtEnd(), it says:
Positions the view at the beginning or end, taking into account ...
Is that a silly error in the documentation, or what? I've tried everything I can think of to make this work, particularly the positionViewAtIndex() method and using highlighters to force the scroll to happen. But nothing works. Note the /* This works. */ comment in the source code above. When that is enabled, it works totally fine! (except of course, it jumps to the ListView.count()-2 index, instead of the end of the list)
Does anyone have any idea what might be wrong here? Any examples I could try to prove that there's a terrible, terrible bug in QML?
I'm using Qt 5.3.1 with QtQuick 2.0 (or 2.1 or 2.2 fail too). I've tried many, many other configurations and code as well, so please ask if you need more info. I've completely exhausted my google-fu.
Thanks!
Edit 1
While the accepted answer does solve the above problem, it involves adding the Component.onCompleted to the delegate. This seems to cause problems when you scroll the list, because (I believe) the delegates are added to the view when you scroll up, causing the onCompleted trigger to be called even if the model item isn't new. This is highly undesirable. In fact, the application is freezing when I try to scroll up and then add new elements to the list.
It seems like I need a model.onAdd() signal instead of using the existence of a delegate instance to trigger the scroll. Any ideas?
Edit 2
And how does this NOT work?
ListView {
id: messageList
model: textMessageFiltered
delegate: messageDelegate
onCountChanged: {
console.log("This prints properly.")
messageList.positionViewAtEnd()
}
}
The text "This prints properly" prints, so why doesn't it position? In fact, it appears to reset the position to the top. So I tried positionViewAtBeginning(), but that did the same thing.
I'm totally stumped. It feels like a bug.
You need to set the currentIndex as well.
testme.qml
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Window 2.0
ApplicationWindow {
title: qsTr("Hello World")
width: 300
height: 240
ScrollView {
anchors.fill: parent
ListView {
anchors.fill: parent
id: messageList
model: messageModel
delegate: Text { text: mytextrole }
highlight: Rectangle { color: "red" }
highlightMoveDuration: 0
onCountChanged: {
var newIndex = count - 1 // last index
positionViewAtEnd()
currentIndex = newIndex
}
}
}
ListModel {
id: messageModel
ListElement { mytextrole: "Dog"; }
ListElement { mytextrole: "Cat"; }
}
Timer {
property int counter: 0
running: true
interval: 500
repeat: true
onTriggered: {
messageModel.append({"mytextrole": "Line" + (counter++)})
}
}
}
There is still some jumping to the first element and jumping back down for a fraction of a second.
There is a note in documentation:
Note: methods should only be called after the Component has completed. To position the view at startup, this method should be called by Component.onCompleted.
Change your ListView.onAdd: to
Component.onCompleted: {
console.log("This prints just fine!")
messageList.positionViewAtEnd()
}
And it works well.
In your case, the ListView emits add signal before the new delegate is created and completed. The ListView is still working on something behind the scene, so positionViewAtEnd cannot work as expected. And /* This works. */ because it is called after the new delegate is completed. However, don't assume this always works. Simply follow the note, call positionViewAtEnd in Component.onCompleted, in documentation.

Resources