How to Use QML StackView? - qt

I am a beginner in QMl and have worked more on StackWidget in QT C++.In QML i am confused to use stackView and have written following code:
Window {
visible: true
width: 640
height: 480
title: qsTr("Stack view")
MainForm {
StackView {
id: stackView
x: 0
y: 0
width: 360
height: 360
initialItem: page1
Rectangle {
id: page1
//anchors.fill: parent
color: "lightgreen"
Button {
id: buttonPage1
text: "back to 2"
anchors.centerIn: parent
onClicked: {
stackView.pop() //**Is THIS CORRECT**
stackView.push(page2) //**Is THIS CORRECT**
}
}
TextEdit {
id: te1
width: 105
height: 40
text: "enter"
}
}
Rectangle {
id: page2
//anchors.fill: parent
color: "lightblue"
Button {
id: buttonPage2
text: "back to 1"
anchors.centerIn: parent
onClicked: {
stackView.pop() //**Is THIS CORRECT**
}
}
TextEdit {
id: te2
width: 109
height: 29
text: "enter"
}
}
}
}
}
Below are the questions:
In StackWidget i was using setCurrentIndex to set the desired page and I know that in QML i should use push and pop. In that case how to use push and pop to navigate between page1 and page2 based on some selection. ?
Initially, can I load all the pages to the stackView?
How to save the content in the page when I pop an item from stackView?

I know that I will not exactly answer your question on how to use the StackView, that is because I think you don't want to have a StackView following your description.
The use-case of a StackView is, when you have the pages - as the names suggests - on a stack. If you only want to switch between pages, where it is not determinable, which one is logically below another, the StackView is not what you want, and you might want to consider a SwipeView.
In the SwipeView the pages coexist in a side-by-side manner. Since Qt 5.9 they have a interactive property with which you might disable the swipe behaviour.
Here you can choose the page you want to show by setting the currentIndex.
However, the SwipeView will create its pages as needed, to reduce the memory and CPU load (effectively disabling bindings of unloaded pages). This might result in data loss, if the data is not stored in a model outside the page itself.
If you want to have all the pages loaded at the same time, and you only want to switch the visible one, you might go with a simple custom component:
Item {
property int currentIndex
Page1 { visible: parent.currentIndex === 0 }
Page2 { visible: parent.currentIndex === 1 }
Page3 { visible: parent.currentIndex === 2 }
...
}
Or you go like:
MyView.qml
Item {
id: root
property int currentIndex: 0
default property Item newContent
onNewContentChanged: {
newContent.parent = root
newContent.visible = Qt.binding(bindingsClosure(root.children.length - 1))
}
function bindingsClosure(index) { return function() { return root.currentIndex === index } }
}
main.qml
MyView {
Page1 { }
Page2 { }
Page3 { }
}

Related

QML SplitView auto collapse on handlebar mouse release

I have a QML Controls 2 SplitView and a redefined handle, which works well, but I want detect mouse release event on the handler, so I could collapse the SplitView under a certain threshold of width. Adding a MouseArea on top of the existing handle will absorb drag events, so I'm unable to move the handlebar. Any idea how could I gather the mouse release event, or any other solution which solves this problem?
Alright, I have created an example application. As you can see in this example, my MouseArea is marked with yellow and collapses the right view programmatically when double clicked, which is nice, but I also want to drag the handlebar and upon mouse release under a certain width threshold I want to collapse the view as well. The black part of the handlebar where my MouseArea is not covering the handlebar, responds to drag, but since there is no signal I can gather from it, the width threshold already set shouldCollapse boolean property, so the view won't update. Probably I could solve this issue with a timer, but I need a more sophisticated solution.
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
width: 800
height: 400
visible: true
SplitView {
id: splitView
anchors.fill: parent
orientation: Qt.Horizontal
function toggleCollapse() { collapsibleRect.shouldCollapse = !collapsibleRect.shouldCollapse }
handle: Rectangle {
implicitWidth: 20
implicitHeight: 20
color: "black"
MouseArea {
anchors.centerIn: parent
width: parent.width
height: parent.height / 2
onDoubleClicked: splitView.toggleCollapse()
Rectangle {
anchors.fill: parent
color: "yellow"
Text {
anchors.centerIn: parent
text: "Double click to collapse"
rotation: 90
}
}
}
}
Rectangle {
id: mainRect
color: "green"
SplitView.fillWidth: true
Text {
anchors.centerIn: parent
font.pixelSize: 24
text: "Main scene"
}
}
Rectangle {
id: collapsibleRect
property bool shouldCollapse: false
SplitView.preferredWidth: shouldCollapse ? 0 : 300
color: "purple"
clip: true
onWidthChanged: {
if(width < 200) shouldCollapse = true
else shouldCollapse = false
}
Text {
anchors.centerIn: parent
rotation: parent.shouldCollapse ? 90 : 0
font.pixelSize: 24
text: parent.shouldCollapse ? "SHOULD BE COLLAPSED" : "NOT COLLAPSED"
Behavior on rotation { NumberAnimation { duration: 100 } }
}
}
}
}
I had a similar problem and was able to solve it thanks to the hint of #Ponzifex that the SplitView's resizing property will be set to true as soon as the handle is clicked. Using a Timer I managed to detect whether the handle was quickly pressed twice in a row.
SplitView {
id: view
...
handle: Rectangle {
...
}
//============================================================
// double click behavior
Timer {
id: doubleClickTimer
interval: 300 // number of ms between clicks that should be considered a double click
}
property bool doubleClicked: false
// `resizing` will be set to true even if the handle is just pressed
onResizingChanged: {
if (view.resizing) {
if (!doubleClickTimer.running) {
doubleClickTimer.start();
return;
}
view.doubleClicked = true;
} else {
if (view.doubleClicked) {
// do any manual resizing in here
view.doubleClicked = false;
}
}
}
}
It is important to note, however, that it is only possible to resize the contents of a SplitView when resizing is false. That's why I need to have the doubleClicked helper property.
Add this to MouseArea:
onPressed: {
mouse.accepted = (mouse.flags & Qt.MouseEventCreatedDoubleClick);
}
propagateComposedEvents: true
cursorShape: Qt.SplitHCursor

Open and close additional window (QML)

Currently I have a window openning in the following way:
property variant win
Button {
id: testButton
MouseArea {
onClicked: {
var component = Qt.createComponent("test.qml");
win = component.createObject(testButton);
win.show();
}
}
}
Is it ok to create a window like this or there is a better way to do it (from QML, not from C++)?
When I close this additional window (just by clicking "x" button), I want to connect it to another event (for example, changing color of the button). How to do it?
Thanks.
It is usually nicer to have it more declarative. If you want your button to only open one window, the usage of a Loader might be right for you.
I think this is what you want, as you store it in one variable, and if you click the button multiple times, you would lose access to your instance. If you need a larger amount of Windows created by the same Button, you might use a ListModel and a Instantiator to create the instances.
With the Loader this might look like this:
Button {
id: ldbutton
onClicked: winld.active = true
Rectangle {
id: ldindic
anchors {
left: parent.left
top: parent.top
bottom: parent.bottom
}
width: height
color: winld.active ? 'green' : 'red'
}
Loader {
id: winld
active: false
sourceComponent: Window {
width: 100
height: 100
color: 'green'
visible: true
onClosing: winld.active = false
}
}
}
In this code is also already the answer to your second question: The signal you are looking for is called closing - connect to it to do what ever is necessary.
In the case of the Loader it is necessary to unload the window, so it can be loaded again later, maybe. If you have the window created by a Instantiator, you need to remove the corresponding index from the Instantiator's ListModel.
This might look like this:
Button {
id: rpbutton
onClicked: rpmodel.append({})
text: 'Open Windows ' + rpmodel.count
ListModel {
id: rpmodel
}
Instantiator { // from QtQml 2.0
model: rpmodel
delegate: Window {
width: 100
height: 100
color: 'blue'
visible: true
onClosing: rpmodel.remove(index)
}
}
}
In your code you could connect to it, either by using a Connection-object, that connects to your property win, or by changing the JS onClicked like so:
onClicked: {
var component = Qt.createComponent("test.qml");
win = component.createObject(testButton);
win.closing.connect(function() { console.log('do something') })
win.show();
}

Page Navigation in QML

I'm trying to implement the following GUI in QML and having trouble understanding how to properly navigate through different pages of the application.
There are 3 buttons in the main menu. When the user clicks on the 'actor' button the UI switches to 'actor view' where the user can toggle between Thumbnail view and List View. When the user clicks on one of the actors the UI switches to Actor Detail view: A view that has a movie view 'nested in it' which lists all the actors movies.
I'm trying to implement this using StackView.
So my StackView lives in the main menu screen (main.qml) when the user clicks one of the buttons the onClicked event pushes the correct view on to the stack.
ActorsView.qml consists of an internal StackView (Most likely a bad idea) and 2 buttons that switch between Thumb and Detail view. This is done by pushing either Thumb or Detail view onto the local stack.
DetailView.qml and ThumbView.qml function exactly the same though look different. Here is where I ran into trouble. I want the main view to be notified when a click event occurs in either Detail or Thumb view. So that it could (based on the event passed information) know what view push onto the main stack. For example when the user clicks on Actor1, the main menu could push 'actor detail view for actor 1' onto the stack.
Sadly I don't know how to 'catch' events that are firing in nested components in the parent element.
I've started playing around with QML and QT just a few weeks ago, and would be happy to hear that my approach is all wrong and that there is a much better way to achieve what I want. Sadly this is the only viable option I found this far.
main.qml:
ApplicationWindow {
title: qsTr("Hello World")
width: 1280
height: 720
visible: true
id: mainWindow
Component{
id: homeScreen
Rectangle{
height: 500
width: 500
color:"blue"
anchors.centerIn: mainWindow
Text {
anchors.centerIn: parent
text: qsTr("Home")
font.pixelSize: 40
}
}
}
Component{
id: actorsView
ActorsView{
view: stack
}
}
Component{
id: moviesView
MoviesView{
view: stack
}
}
ColumnLayout{
RowLayout{
Layout.fillWidth: true
Button{
text: "Back"
onClicked: stack.pop()
}
Button{
text: "actor view"
onClicked: stack.push(actorView)
}
Button{
text: "movie view"
onClicked: stack.push(moviesView)
}
}
StackView {
id: stack
initialItem: homeScreen
Layout.fillHeight: true
Layout.fillWidth: true
}
}
}
ActorsView.qml:
Item {
property StackView view
Component {
id: actorDetailView
DetailView {
name: "actorDetailView"
text: "Actor"
}
}
Component {
id: actorThumbView
ThumbView {
name: "actorThumbView"
text: "Actor"
}
}
ColumnLayout {
RowLayout {
Text {
text: "Actor view"
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
}
Button {
text: "Detail"
onClicked: internalStack.push(actorDetailView)
}
Button {
text: "Thumb"
onClicked: internalStack.push(actorThumbView)
}
Button {
text: "back"
onClicked: internalStack.pop()
}
Button {
text: "depth: " + internalStack.depth
}
}
StackView {
id: internalStack
initialItem: {
console.log(internalStack.depth)
internalStack.initialItem = actorThumbView
}
Layout.fillHeight: true
Layout.fillWidth: true
}
}
}
ThumbView.qml:
Item {
property string name: "thumbView"
property string text
property int counter: 0
id:thumbView
signal thumbPressed (string pressedName)
GridLayout {
columnSpacing: 10
rowSpacing: 10
width: parent.width
Repeater {
model: 16
Rectangle {
width: 200
height: 300
color: "grey"
Text {
id: lable
text: text
anchors.centerIn: parent
}
MouseArea {
anchors.fill: parent
onClicked: {
var tag = lable.text
console.log("You have clicked " + tag)
thumbView.thumbPressed(tag)
}
}
Component.onCompleted: {
counter = counter + 1
lable.text = text + " " + counter
}
}
}
}
}
That's actually a common approach to structure a QML application, so no it's not all bad ! Nested StackViews are a powerful way to manage sub-content of a page, but surely add a level in your app structure. It's made easier by creating your own Page item, redefining the navigation and interaction as you wish.
There's different ways to handle signal in nested components. The easiest: call an identified item up in hierarchy. Local and parent elements in QML are accessible from their id directly, even if those are not in the same QML file. Which allowThis of course has the drawback of inducing coupling between your pages or components and the rest of your application.
ApplicationWindow {
id: mainWindow
function pushPage(page) {
stack.push(page)
}
function showActor(id) {
...
}
// ...
}
In your page simply...
MouseArea {
onClicked: {
mainWindow.showActor(index)
}
}
To achieve something more modular, you can rely StackView currentItem, signals, Connections and Binding elements to name a few, or implement an interface in QML and/or C++ to manage your navigation.
There's definitely a lot of possibilities depending on your goal architecture, trying & learning makes it perfect !

Unable to access QML variable / id globally

I have QtQuick 1.0
I use the following code:
Rectangle {
Component {
id: appDelegate
MouseArea{
id:myMouseArea
hoverEnabled: true
onClicked:{
onClicked: load.source = page;
}
}
Loader {
id: load
}
}
GridView {
id: view
// I am unable to access myMouseArea here.
highlight: myMouseArea.containsMouse ? appHighlight : !appHighlight
delegate: appDelegate
}
}
It gives me the following error:
ReferenceError: Can't find variable: myMouseArea
/usr/lib/i386-linux-gnu/qt4/bin/qmlviewer exited with code 0
I don't know if the details I provided are sufficient, please let me know if theres anything else I am missing.
I am using this code as an example:
http://docs.knobbits.org/qt4/declarative-modelviews-gridview-qml-gridview-example-gridview-example-qml.html
You cannot access myMouseArea because it's created inside delegate context. You cannot access delegate other then currentItem. But you can freely access view inside the context of delegate, to set currentIndex to attached property index.
This is a corrected code:
Rectangle {
width: 360
height: 360
Component { // It must be a component, if we want use it as delegate
id: appDelegate
// its not possible to have more than one element inside component
Rectangle
{
// need to set size of item, anchors wont work here
// could use view.cellWidth and view.cellHeight to keep it DRY
width: 96
height: 66
color: "green" // color only to see the place of MouseArea
MouseArea {
id:myMouseArea
anchors.fill: parent // this setup the size to whole rectangle
// it this item have the size 0,0 it will simple do not work
hoverEnabled: true
onEntered: {
// we know the mouse is inside this region
// setting this value will show the highlight rectangle
view.currentIndex = index;
}
onClicked:{
onClicked: load.source = page;
}
}
Loader {
// this is not needed but it's wise to not keep zero size
anchors.fill: parent
id: load
}
}
}
GridView {
id: view
// the size of GridView must be set,
// as otherwise no delegate will not show
anchors.fill: parent
anchors.margins: 5
cellWidth: 100
cellHeight: 70
// Rectangle will act as a border.
// Size and position is set by GridView
// to the size and position of currentItem.
// This is no a item, this makes a Component
// as highlight property needs one.
// You can create a Component like appDelegate.
highlight : Rectangle {
border.width: 2
border.color: "blue"
}
// some ListModel to setup the page variable inside delegate context
model: ListModel {
ListElement { page: "test1.qml"; }
ListElement { page: "test2.qml"; }
ListElement { page: "test3.qml"; }
}
delegate: appDelegate
}
}

How can I switch the focus for the pop-up window?

I encounter a problem which is that the pop-up window cannot get the focus when it is shown. I tried to use the activefocus function in main window, but it doesn't work. It is supposed that if I press the enter key, the pop-window will be closed. How can I get the focus for the pop-up window? Thanks.
...
GridView {
id:grid_main
anchors.fill: parent
focus: true
currentIndex: 0
model: FileModel{
id: myModel
folder: "c:\\folder"
nameFilters: ["*.mp4","*.jpg"]
}
highlight: Rectangle { width: 80; height: 80; color: "lightsteelblue" }
delegate: Item {
width: 100; height: 100
Text {
anchors { top: myIcon.bottom; horizontalCenter: parent.horizontalCenter }
text: fileName
}
MouseArea {
anchors.fill: parent
onClicked: {
parent.GridView.view.currentIndex = index
}
}
}
Keys.onPressed: { //pop up window
if (event.key == 16777220) {//enter
subWindow.show();
subWindow.forceActiveFocus();
event.accepted = true;
grid_main.focus = false;
}
}
}
Window {
id: subWindow
Keys.onPressed: {
if (event.key == 16777220) {//press enter
subWindow.close();
}
}
}
...
Let's start with some basics:
Keys.onPressed: { //pop up window
if (event.key == 16777220) {//enter
subWindow.show()
...
event.accepted = true
}
}
Not to mention how error-prone it is, just for the sake of readability, please don't hard-code enum values like 16777220. Qt provides Qt.Key_Return and Qt.Key_Enter (typically located on the keypad) and more conveniently, Keys.returnPressed and Keys.enterPressed signal handlers. These convenience handlers even automatically set event.accepted = true, so you can replace the signal handler with a lot simpler version:
Keys.onReturnPressed: {
subWindow.show()
...
}
Now, the next thing is to find the correct methods to call. First of all, the QML Window type does not have such method as forceActiveFocus(). If you pay some attention to the application output, you should see:
TypeError: Property 'forceActiveFocus' of object QQuickWindowQmlImpl(0x1a6253d9c50) is not a function
The documentation contains a list of available methods: Window QML type. You might want to try a combination of show() and requestActivate().
Keys.onReturnPressed: {
subWindow.show()
subWindow.requestActivate()
}
Then, you want to handle keys in the sub-window. Currently, you're trying to attach QML Keys to the Window. Again, if you pay attention to the application output, you should see:
Could not attach Keys property to: QQuickWindowQmlImpl(0x1ddb75d7fe0) is not an Item
Maybe it's just the simplified test-case, but you need to get these things right when you give a testcase, to avoid people focusing on wrong errors. Anyway, what you want to do is to create an item, request focus, and handle keys on it:
Window {
id: subWindow
Item {
focus: true
Keys.onReturnPressed: subWindow.close()
}
}
Finally, to put the pieces together, a working minimal testcase would look something like:
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
id: window
width: 300
height: 300
visible: true
GridView {
focus: true
anchors.fill: parent
// ...
Keys.onReturnPressed: {
subWindow.show()
subWindow.requestActivate()
}
}
Window {
id: subWindow
Item {
focus: true
anchors.fill: parent
Keys.onReturnPressed: subWindow.close()
}
}
}
PS. Key events rely on focus being in where you expect it to be. This may not always be true, if the user tab-navigates focus elsewhere, for example. Consider using the Shortcut QML type for a more reliable way to close the popup.

Resources