Closing qml dialog properly - qt

I've been playing around with dialogs and there is something that bothers me.
I have the following code:
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Button {
id: click
x: 285
y: 189
text: qsTr("Click")
onClicked: dlgTest.open()
}
Dialog{
id:dlgTest
visible:false
contentItem: Rectangle{
width: 300
height: 300
TextField{
id: tfText
anchors.top: parent.top
}
Button{
anchors.top: tfText.bottom
onClicked: dlgTest.close()
text: "Close"
}
}
}
}
When I open it the first time I add some text to the TextField and then I close it. However, If I open it again, the text will still be there. What I want is to "reset" the dialog to it's original state when I opened it the first time (with an empty TextField). It seems that calling the method "close" is exactly the same as changing visible to false.
Is there a way of doing this "reset"?
I have an other dialog with a lot of controls and it's annoying having to restore everything manually.

In your code you create the Dialog once, as a child of the ApplicationWindow.
To 'reset' it, you have two options:
Have a reset-function, that you call, and restores everything. You can use this to set it up in the first place as well
Create a new Object with everything set in place.
For the latter you can either use JavaScript Dynamic Object Creation or a Loader.
JavaScript Dynamic Object Creation:
Button {
id: click
x: 285
y: 189
text: qsTr("Click")
onClicked: {
var d = diaComp.createObject(null)
d.open()
}
}
Component {
id: diaComp
Dialog{
id:dlgTest
visible:false
contentItem: Rectangle{
width: 300
height: 300
TextField{
id: tfText
anchors.top: parent.top
}
Button{
anchors.top: tfText.bottom
onClicked: {
dlgTest.close()
dlgTest.destroy()
}
text: "Close"
}
}
}
}
However, as you destroyed the Object, the contents of your properties are lost, and you can't access them anymore. So you need to make sure, to copy them (not bind them) to some property that is not destroyed, first.
With the Loader you have the posibility to unload the Dialog right before you load it again, which basically resets it. But until you unloaded it, you can still access it's values, as you can see in the Buttons onClicked-handler.
Button {
id: click
x: 285
y: 189
text: qsTr("Click")
onClicked: {
console.log((dlgLoad.status === Loader.Ready ? dlgLoad.item.value : 'was not loaded yet'))
dlgLoad.active = false
dlgLoad.active = true
dlgLoad.item.open()
}
}
Loader {
id: dlgLoad
sourceComponent: diaComp
active: false
}
Component {
id: diaComp
Dialog{
id:dlgTest
visible:false
property alias value: tfText.text
contentItem: Rectangle{
width: 300
height: 300
TextField{
id: tfText
anchors.top: parent.top
}
Button{
anchors.top: tfText.bottom
onClicked: {
dlgTest.close()
}
text: "Close"
}
}
}
}
Of course, you could also copy the values from the Loader's item as well, and then unload it earlier, to possible free the memory.
But if the Dialog is frequently (most of the time) shown, it might be the wisest to avoid the creation and destruction of the objects, by reusing it and resetting it manually.

Related

QML give focus to a Component

I have this code:
//main.qml
Window {
id: root
width: 640
height: 480
visible: true
title: qsTr("Hello World")
objectName: "Window"
onActiveFocusItemChanged: console.log("***** ACTIVE FOCUS:", activeFocusItem, "*****")
StackView {
anchors.fill: parent
initialItem: "qrc:/LoaderPage.qml"
objectName: "StackView"
onCurrentItemChanged: currentItem.forceActiveFocus()
}
}
//LoaderPage.qml
Item {
objectName: "ItemLoaderPage"
// Keys.forwardTo: loader
Loader {
id: loader
anchors.fill: parent
objectName: "Loader"
focus: true
sourceComponent: rect1
}
Component {
id: rect1
Rectangle {
Keys.onReleased: {
if(event.key === Qt.Key_Escape || event.key === Qt.Key_Back)
{
console.log("Esc or back pressed from", objectName)
event.accepted = true
}
}
objectName: "Rectangle"
focus: true
color: "blue"
}
}
}
I am trying to give the focus to the Rectangle in rect1 Component and catch key events, but with this code, the focus is always given to ItemLoaderPage and I am not able to catch key events. How can I solve that?
I find that maintaining keyboard focus is a big weakness in Qt. The docs make it all sound so straightforward, but in practice I am always ending up in situations where I can't even tell where the focus went.
I usually resort to manually calling forceActiveFocus() rather than depending on Qt to do the right thing automatically. It's a fragile solution, but it's at least one that I feel I have control over.
Loader {
sourceComponent: rect1
onLoaded: {
item.forceActiveFocus();
}
}
The Loader and the rectangle has requested the focus by your attribute:
focus: true
You should try to set the focus only once if I get the idea of the focus-attribute right.

Send a signal between two object instances in different QML files

I'm trying to send a signal from an object in one QML file to another object in a different QML file, but can't seem to find any good resources to use as a guide. Most of the examples I have come across show signals and slots being used to communicate between either two objects implemented in the same QML file (i.e. inside the same component), or in two different component files that come together inside a third QML file, which differs from my use case.
I need to send a string value from an object in a QML file (which represents a screen) to another object in a different QML file (representing yet another screen). The way the screens are linked currently is via StackView QML type in the main.qml file.
The closest I have seen the same problem described is here. The problem with the accepted answer in my case is the fact that the objects Rect1 and Rect2 are later defined in the same file. This means that they can be given an id and the signal and slot can be connected together, something I'm unable to do on my side.
Here's some code to demonstrate the problem.
main.qml:
ApplicationWindow {
id: app_container
width: 480
height: 600
visible: true
StackView {
id: screen_stack
anchors.fill: parent
initialItem: Screen1 {
}
}
}
Screen1:
Item {
id: screen_1
width: 480
height: 600
property var input
TextField {
id: user_input
width: parent.width
height: parent.height - 100
anchors.horizontalCenter: parent.horizontalCenter
placeholderText: qsTr("Enter your name")
onEditingFinsihed: {
input = user_input.text
}
}
Button {
width: parent.width
height: 100
anchors.top: user_input.bottom
anchors.horizontalCenter: parent.horizontalCenter
onClicked: {
console.log("Moving to Screen2")
screen_stack.push("qrc:/Screen2.qml")
}
}
}
Screen2:
Item {
id: screen_2
width: 480
height: 600
Rectangle {
anchors.fill: parent
color: "yellow"
Text {
id: txt_rect
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
text: qsTr("")
}
}
}
What I would like to be able to do is send the user input from TextField user_input in Screen1 to Text txt_rect in Screen2. How can I achieve this?
You can push properties:
screen_stack.push("qrc:/Screen2.qml", {"inputText": user_input.text})
Screen2:
Item {
id: screen_2
width: 480
height: 600
property var inputText
Rectangle {
anchors.fill: parent
color: "yellow"
Text {
id: txt_rect
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
text: screen_2.inputText
}
}
}

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

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 !

QML , How to Access Element from Another qml

I want to ask simple question for quick solution. I have ListView and Button in main.qml and Component in closingtaskdelage.qml. Here is file main.qml:
ListView
{
id: grid
delegate:mydelegate
model: mymodel
spacing: 5
orientation:ListView.Vertical
Layout.preferredWidth: width_commmon
Layout.preferredHeight: height_body
Layout.maximumHeight: 800
Layout.alignment: Qt.AlignHCenter
// highlightFollowsCurrentItem: false
focus: true
ClosingTaskModel
{
id:mymodel
}
ClosingTaskDelegate
{
id:mydelegate
}
}
Button
{
id:finishbutton
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: width_commmon
Layout.preferredHeight:height_endbutton
text: "İş Emrini Tamamla"
onClicked:
{
// console.log((grid.children["border1"]).id);
}
}
and file ClosingTaskDelegate.qml:
Component
{
id:component1
Rectangle
{
signal sendMenuValuestoJS
height: 200
width: 200
Text
{
id:text1;
text:header;
height:parent.height/2
// width: parent.width
anchors.horizontalCenter:parent.horizontalCenter;
font.pointSize:20;
color:"red";
// height:parent.height/2
// width: parent.width
}
}
}
Don't care about details. My main problem is: When onClickButton() in main.qml occurs, signal sendMenuValuestoJS() must be triggered. This signal must send string value as parameter (i.e. send Text).
I can't access signal from main.qml. I have defined signal in the Component, but I get following runtime QML error:
Component objects cannot declare new signals.
How do I connect signal to some function?
What about to to use Connections?
Here is the idea,
/* main.qml */
Rectangle {
width: 200
height: 200
MouseArea {
id: mouse
anchors.fill: parent
}
Click {
id: click
targetto: mouse
}
}
/* Click.qml */
Item {
property variant targetto
Connections {
target: targetto
onClicked: console.log("CLICKED!");
}
}
Here is example from official Qt webiste in Signal Handler Attributes chapter.

Resources