Return value from QML Window to parent - qt

I use QML to build GUI in my app + simple logics.
At some step I open dialog and after closing it I want to get back a result value.
This is sample code:
Button {
id: myButton
onClicked: {
var component = Qt.createComponent("Dialog.qml");
if (component.status === Component.Ready) {
var dialog = component.createObject(parent);
dialog.show();
dialog.onClosing: {} // that not works
}
}
}
Dialog.qml:
import QtQuick 2.2
import QtQuick.Window 2.1
import QtQuick.Controls 1.1
Window {
id: dialogWindow
width: 800
height: 600
flags: Qt.Dialog
Button {
id: closeButton
onClicked: {
dialogWindow.close();
}
}
}
But I have no idea how can I get some return value after the dialog was closed.

My advice would be to not bother with Window and use Qt Quick Dialogs.
If you want to use Window, you have to define your own return value in the form of some Yes/No buttons in the window, for example. When one of these is clicked, set a yes property to true/false. Then, within your Button:
Connections {
target: dialogWindow
onVisibleChanged: // some action depending on value of "yes" property
}
Note that I used onVisibleChanged instead of onClosing, as the latter is only emitted upon the user closing the window:
This signal is emitted when the user tries to close the window.
If we follow the documentation for the CloseEvent argument, we see that it's explained in slightly more detail:
Notification that a window is about to be closed by the windowing system (e.g. the user clicked the titlebar close button).

Related

Qt QML Focus an Item (TextField) when showing a StackView Page

I want to enable TextField focus when QML file is loaded. But, it is not working. After loading TestUi.qml file I put some button and its onClick() method I did _recipientView.focus = true_, it works fine. The problem is that default focus is not enabled when view is loaded first time.
TestUi.qml
import QtQuick 2.0
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.0
Page {
function init() {
recipientView.focus = true;
}
TextField {
id: recipientView
Layout.fillWidth: true
font.pixelSize: 18
inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhEmailCharactersOnly
focus: true
placeholderText: "Email"
}
}
main.qml
onComposeBtnClicked: {
rootStackView.push(test)
test.init()
}
TestUi {
id: test
visible: false
}
Edit
The Page component already acts as a FocusScope, so only forcing the active focus is necessary. Thanks for the comments.
StackView {
id: stackView
initialItem: firstPage
// Ensures the focus changes to your page whenever
// you show a different page
onCurrentItemChanged: {
currentItem.forceActiveFocus()
}
}
Page {
id: firstPage
visible: false
TextField {
// Explicitly set the focus where needed
focus: true
}
}
Original answer
This is simply because you are pushing TestUi into your stack with rootStackView.push(test). When you do so, the focus is reset. This is typically handled with a QFocusScope, which role is to remember the focused Item, and give the focus back to it when the QFocusScope regains focus.
In your case, adding a QFocusScope to your base page would enable restoring the focus correctly when the page is shown:
StackView {
id: stackView
initialItem: firstPage
onCurrentItemChanged: {
currentItem.forceActiveFocus()
}
}
Page {
id: firstPage
visible: false
onFocusChanged: {
scope.focus = true
}
FocusScope {
id: scope
TextField {
focus: true
// ...
}
}
}
You can then use your page handler onVisibleChanged if you want to reset to focus when the user comes back to it (after a pop for instance), instead of memorizing it where the focus is. But in that case the FocusScope might be overkill.
For information, you can also use the StackView property initialItem in order to set the first page.
It is also kind of unrelated, but prefer importing the most recent version of the QtQuick components available. QtQuick version will be 2.12 for Qt 5.12. A bit less trivial for QtQtcuik.Controls version, but they are getting in line with that versioning scheme.

List delegates driven live by JavaScript data model

I have a QML application where I'm creating lists of elements received from JavaScript. Using the details from this answer I'm populating the model as a JS array, and it works great. However, I'd like it so that when properties of the JavaScript objects change that ListView items driven from them update live.
Here's a simple test app showing the problem. The ListView is properly populated with MyRow instances showing the correct id/title, but when the rand property is changed by the timer, the List rows are unchanged (they show 0 for the last item).
MyRow.qml
import QtQuick 2.0
import QtQuick.Layouts 1.3
Rectangle {
property var obj
color:'#eeeeff'; height:20
RowLayout {
anchors.fill:parent
Text { text:obj.id }
Text { text:obj.title; Layout.fillWidth:true }
Text { text:obj.rand }
}
}
main.qml
import QtQuick 2.7
import QtQuick.Window 2.2
Window {
id:app; visible:true; width:200; height:100
property var database: ({"17":"World","42":"Hello"})
property var objById: ({})
function getObj(id){
if (!objById[id]) objById[id] = { id:id, title:database[id], rand:0 };
return objById[id];
}
ListView {
id:mylist
anchors.fill:parent
model: [42,17] // object ids
delegate: MyRow {
width:parent.width
obj:getObj(mylist.model[index])
}
}
Timer { // Update every object's rand value every second
interval:1000; running:true; repeat:true
onTriggered: {
Object.keys(objById).forEach(function(id){
objById[id].rand = Math.random()*100<<0;
})
}
}
}
How can I get the delegate's Text items to update their text when properties of the objects change?
The easiest (only?) way to get property bindings to work properly is to create real Qt objects to hook the values to. If you don't want to use a ListModel (because you want to quickly populate a model with items from a master library), then you can use createObject() to generate objects and pass them to your delegate.
Here's an updated main.qml that works as desired:
Window {
// ...same as above...
Component { // Creates real Qt objects with bindable properties
id:objFactory
QtObject {
property int id
property string title
property int rand:0
}
}
function getObj(id){
if (!objById[id])
objById[id] = objFactory.createObject( app, {id:id,title:database[id]} );
return objById[id];
}
// ...same as above...
}
Additionally, you may wish to change the property var obj in MyRow.qml to a more specific property QtObject obj (or a more specific object type, depending on what you pass in).
Finally, note that it's slightly cleaner/simpler to use modelData instead of mylist.model[index]:
ListView {
anchors.fill:parent
model: [42,17] // object ids
delegate: MyRow {
width:parent.width
obj:getObj(modelData)
}
}

Qt - How to connect signal from loader item, if loader source changes dynamically

I have an application which needs the screen to be switched between multiple available screens. I am checking if this is possible with loader in qml.
The issue i am facing is connecting signals from loaded item.
I use an application example in qt documentation and found CreateConnection in application qml cannot have if condition.
I also tried to make it signal slot connection in a function and call in on source change of loader, but that too did not work.
Application.qml
import QtQuick 2.0
Item {
width: 100; height: 100
Loader {
id: myLoader
source: "MyItem.qml"
}
Connections {
target: myLoader.item
// here i tried using if (myLoader.item == "qrc:MyItemOne.qml") , but can't use if
onChangeToSecond: {
myLoader.source = "MyItemTwo.qml"
}
onChangeToFirst: {
myLoader.source = "MyItemOne.qml"
}
}
}
MyItemOne.qml
import QtQuick 2.0
Rectangle {
id: myItem
signal changeToSecond()
width: 100; height: 100
MouseArea {
anchors.fill: parent
onClicked: myItem.changeToSecond()
}
}
MyItemTwo.qml
import QtQuick 2.0
Rectangle {
id: myItem
signal changeToFirst()
width: 100; height: 100
MouseArea {
anchors.fill: parent
onClicked: myItem.changeToFirst()
}
}
Someone knows any way to use loader for this, or loader should not be used for this?
Your code works fine if I use MyItemOne.qml as the initial value for the myLoader.source (Qt5.6.0). However, it will print out a warning:
QML Connections: Cannot assign to non-existent property "onChangeToFirst"
which happens because MyItemOne does not define the changeToFirst signal. The ignoreUnknownSignals property of Connections element can be used to suppress the warning, or both screens should define the same set of signals.
Loader can be used if it does not matter that the previous view is always fully unloaded when switching.

Reject external files dragged in a DropArea without breaking the DropArea

In my application I'm displaying a list of audio files and the user can drag an external file to add it to the list. I want to be able to refuse the drag if no file in the list is supported by my application.
The issue is that when I call drag.accepted = false; in onEntered of my DropArea then it becomes completely unresponsive to any other event.
Here is some sample code showing the issue. If you drag an MP3 in the window you see that it works. Then if you drag any other file it won't work, as expected. But then dragging an MP3 file back will not work either.
import QtQuick 2.1
import QtQuick.Window 2.0
ApplicationWindow {
title: qsTr("Hello World")
width: 640
height: 480
DropArea {
anchors.fill: parent
onEntered: {
console.log("[Droparea] entered");
// Ensure at least one file is supported before accepted the drag
var validFile = false;
for(var i = 0; i < drag.urls.length; i++) {
if(validateFileExtension(drag.urls[i])) {
validFile = true;
break;
}
}
if(!validFile) {
console.log("No valid files, refusing drag event");
drag.accepted = false;
return false;
}
}
onExited: {
console.log("[Droparea] entered");
}
onDropped: {
console.log("[Droparea] dropped");
}
// Only MP3s
function validateFileExtension(filePath) {
var extension = filePath.split('.').pop();
var valid = false;
if(extension == "mp3") {
valid = true;
}
return valid;
}
}
Text {
id: textDrop
anchors.centerIn: parent
text: "Please drag element"
}
}
Is there a bug in the DropArea or did I misunderstood something? I know I can filter the files in the onDropped but then you loose the visual feedback you get on OSX when dragging file on an area that does not accept them.
It has been a known bug for a long time. A patch has been submitted and after been stalled for several months is now merged into 5.6 branch.
Anyone who wants to use this functionality MUST upgrade to Qt 5.6 or MANULLY integrate the available patch into his/her Qt version.
QQuickDropAreaPrivate, contained in DropArea, updates the containsDrag flag to true when a dragEnterEvent occurs, emitting the entered signal. It updates containsDrag to false when adragLeaveEvent occurs, emitting an exited signal. However, when the drag event is not accepted dragLeaveEvent is never called, leaving the private object in a incosistent state. Each subsequent dragEnterEvent is discarded since containsDrag is still true, i.e. the previous drag event is still considered active and the entered is no more emitted.
Since the issue is related to an interaction between private APIs and usage of the public APIs, the problem does not affect filtering using keys. Unfortunately, this approach does not seem to fit for the presented use case.
A quite partial workaround is to use a MouseArea along with the DropArea. The latter disables itself when a rejection occurs while the former enables back the DropArea for the next drop. This workaround covers the common case in which a wrong item is dropped inside the DropArea, which is the most common and intuitive for an end user. Releasing the wrong item outside the DropArea invalidate the mechanism (until the next drop).
Here's the code:
import QtQuick 2.1
import QtQuick.Controls 1.0
import QtQuick.Window 2.0
ApplicationWindow {
title: qsTr("Hello World")
width: 640
height: 480
visible: true
MouseArea {
anchors.fill: parent
hoverEnabled: true
enabled: !drop.enabled
onContainsMouseChanged: drop.enabled = true
}
DropArea {
id: drop
anchors.fill: parent
onEntered: {
console.log("[Droparea] entered");
// Ensure at least one file is supported before accepted the drag
for(var i = 0; i < drag.urls.length; i++)
if(validateFileExtension(drag.urls[i]))
return
console.log("No valid files, refusing drag event")
drag.accept()
drop.enabled = false
}
onExited: console.log("[Droparea] exited")
onDropped: console.log("[Droparea] dropped")
// Only MP3s
function validateFileExtension(filePath) {
return filePath.split('.').pop() == "mp3"
}
}
Text {
id: textDrop
anchors.centerIn: parent
text: "Please drag element"
}
}
you never put accepteed = true
just add drag.accepted = true after you set the valid as valid
for(var i = 0; i < drag.urls.length; i++) {
if(validateFileExtension(drag.urls[i])) {
validFile = true;
drag.accepted = true;
break;
}
}

Navigating and addressing QMLComponents

I am working on a desktop application the uses a QML GUI with a lot of QML Components.
These are parts of the hierarchy:
main -> toolbar -> searchbar -> editfield
and
main -> resultlist -> header -> button1
I could not find a way to access the text contents of editfield in a signal handler for button1. Is it possible to do that in QML or Javascript?
I know I can access the element in the C++ part by using the objectName property.
Due to QML uses dynamic scoping (→ Doc), child elements can access the properties of all ancestors
and it doesn't matter if they are in different files.
So you could add an editFieldText property to main and bind the
text property of editfield to it. Then you can access editFieldText
from everywhere:
//=== main.qml ===
import QtQuick 1.0
Rectangle {
id: main
property string editFieldText
Toolbar {
// [...]
}
Resultlist {
// [...]
}
}
//=== EditField.qml ===
import QtQuick 1.0
TextInput {
// bind text property to main.editFieldText
Binding {
target: main;
property: "editFieldText";
value: text
}
}
//=== Header.qml ===
import QtQuick 1.0
Rectangle {
Button {
onClick: {
console.log(main.editFieldText);
// or simply
console.log(editFieldText);
}
}
}
You can use alias properties to have the editfield.text as a property of main. This property should be accessible from button1.

Resources