Where do I destroy a Dialog if I create it dynamically? - qt

Assume I have the following Dialog in QML:
import QtQuick
import QtQuick.Controls
Dialog
{
title: "Bot Settings"
modal: true
standardButtons: Dialog.Ok | Dialog.Cancel
property var bot1
onAccepted: {}
onRejected: {}
Component.onCompleted: {}
Component.onDestruction: {}
}
and I create it with a code like this:
function openBotDialog()
{
var dynamicComp = Qt.createComponent("BotDialog.qml")
var dynamicDlg = dynamicComp.createObject(mainWindow, {"bot1" : bot});
dynamicDlg.open()
}
and I need to destroy the dialog when it closes.
What code do I use and where do I add it?
Should I destroy dynamicComp?
See the full source code of the app with my dialog on GitHub.
EDIT1
When I press the following buttons in my app the dialog is not destroyed by GC:
But when I press "Clear Dynamic Dlg" button that explicitly calls destroy() method the Dialog is destroyed and its Component.onDestruction handler is called.
Button
{
text: "Clear Dynamic Dlg"
onClicked:
{
dynamicComp.destroy()
dynamicComp = null
dynamicDlg.destroy()
dynamicDlg = null
}
}

How about:
onAccepted: Qt.callLater(destroy)
onRejected: Qt.callLater(destroy)
This means you are scheduling a destroy after the accepted/rejected signals are received. Qt.callLater adds enough of a delay so that the destruction isn't being done during the signal handler but immediately after.
import QtQuick
import QtQuick.Controls
Page {
id: thisPage
Button {
text: qsTr("Bot Dialog")
onClicked: openBotDialog()
}
function openBotDialog()
{
var dynamicComp = Qt.createComponent("BotDialog.qml")
var bot = new Date();
var dynamicDlg = dynamicComp.createObject(thisPage, {"bot1" : bot});
dynamicDlg.open()
}
}
//BotDialog.qml
import QtQuick
import QtQuick.Controls
Dialog
{
title: "Bot Settings"
modal: true
standardButtons: Dialog.Ok | Dialog.Cancel
property var bot1
onAccepted: Qt.callLater(destroy)
onRejected: Qt.callLater(destroy)
}
You can Try it Online!

Related

qt qml errors with QtQuick Dialogs 1

I"m using Qt 5.15.2. Below is the qml snippet that is failing for me.
Upon launching everything is ok. However when i try to open new windows, 1 window will be ok, but second (and subsequent) one will start showing something like this in the console.
bin/archdatadir/qml/QtQuick/Dialogs/DefaultFileDialog.qml:99: TypeError: Value is null and could not be converted to an object
bin/archdatadir/qml/QtQuick/Controls/SplitView.qml:630: TypeError: Property 'terminateItemConnections' of object QObject_QML_93(0x600003397f20) is not a function
bin/archdatadir/qml/QtQuick/Controls/SplitView.qml:630: TypeError: Property 'terminateItemConnections' of object QObject_QML_93(0x600003394500) is not a function
bin/archdatadir/qml/QtQuick/Dialogs/DefaultFileDialog.qml:436: TypeError: Cannot read property 'bottom' of undefined
....
If I remove the FileDialog definition completely, which is actually unused here, then these errors disappear completely. Would there be any suggestions on what I am doing incorrectly?
thanks!
import QtQuick 2.15
import QtQuick.Controls 2.4
import QtQuick.Dialogs 1.2
ApplicationWindow {
id: root
onClosing: {
console.log("closing window")
}
menuBar: MenuBar {
Menu {
title: qsTr("&File")
MenuItem {
text: qsTr("&New")
icon.name: "document-new"
onTriggered: newDocument()
}
}
}
FileDialog {
id: openDialog
title: "Open"
folder: shortcuts.home
onAccepted: {
console.log("open file dialog")
}
}
function newWindow() {
console.log("new window")
var component = Qt.createComponent("reproducer.qml")
var window = component.createObject()
return window
}
function newDocument() {
console.log("new document")
var window = newWindow()
window.show()
}
}
VK

How to implement single connection between objects in QML?

In my Qt app I have many windows, and sometimes they need a "Back" button. This button is placed on ToolBar component in the header of the ApplicationWindow .
What I want to achieve, is that this Back button, would have only single connection to other objects , i.e. the connection to the last object that called connect method. Right now with every connect I am getting a new connection and when the signal is emitted, it is called multiple times. Unfortunately Qt doesn'thave disconnectAll method, if it would , that would have solve my problem , I would just call disconnectAll before and then connect and that would implement single connection.
So , how are you doing this functionality in Qt , with a simple method?
Here is a minimal reproducible example, click on the tabs many times, then press 'Back' button and you will see lots of console.log messages. And what I need is this message to correspond to the last object that is connected to the Back button.
import QtQuick 2.11
import QtQuick.Controls 2.4
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Tabs")
signal back_btn_clicked()
SwipeView {
id: swipeView
anchors.fill: parent
currentIndex: tabBar.currentIndex
Page1Form {
id: page1
function page1_callback() {
console.log("page 1 back button triggered")
}
function install_button() {
enable_back_button(page1_callback)
}
}
Page2Form {
id: page2
function page2_callback() {
console.log("page 2 back button triggered")
}
function install_button() {
enable_back_button(page2_callback)
}
}
function install_back_button(idx) {
if (idx===0) {
page1.install_button()
}
if (idx===1) {
page2.install_button()
}
}
}
Button {
id: btn_back
visible: false
text: "Back Button"
onClicked: back_btn_clicked()
}
footer: TabBar {
id: tabBar
currentIndex: swipeView.currentIndex
TabButton {
text: qsTr("Page 1")
onClicked: swipeView.install_back_button(0)
}
TabButton {
text: qsTr("Page 2")
onClicked: swipeView.install_back_button(1)
}
}
function enable_back_button(func_name) {
btn_back.visible=true
back_btn_clicked.connect(func_name)
}
}
PageForm.ui is defined like this
import QtQuick 2.11
import QtQuick.Controls 2.4
Page {
width: 600
height: 400
header: Label {
text: qsTr("Page 1")
font.pixelSize: Qt.application.font.pixelSize * 2
padding: 10
}
Label {
text: qsTr("You are on Page 1.")
anchors.centerIn: parent
}
}
The simplest hack, I think, would be to store the callback in a property, then in enable_back_button(), reference that property in your disconnect() function, and update the property accordingly with the new callback passed as a function argument. (The rationale for this argument being that the disconnect() function must take in an argument: the slot to disconnect. So we'll need to keep track of it some way or another.)
ApplicationWindow {
visible: true
// ... omitted for brevity
property var prevCallback: null
// ... ofb
function enable_back_button(func_name) {
btn_back.visible=true
if (prevCallback)
back_btn_clicked.disconnect(prevCallback) // disconnect previous callback
back_btn_clicked.connect(func_name) // connect new callback
prevCallback = func_name // update property with new callback
}
}
And this could work on multiple connections as well, by simply changing the storage into an array, then iterating through that.

How to keep a single instance of the window?

I have the next QML:
import Qt.labs.platform 1.0
SystemTrayIcon {
visible: true
iconSource: "qrc:/icons/ic_tray.png"
menu: Menu {
MenuItem {
text: qsTr("Settings")
onTriggered: {
// Don't create a new object if it exists, just show
var settings = Qt.createComponent("main.qml")
var form = settings.createObject(this)
form.show()
}
}
MenuItem {
text: qsTr("Quit")
onTriggered: Qt.quit() // Just hide an existing
}
}
}
How to create main.qml one time only and after just show/hide?
P.S. I'm learning Qt including QtQuick 2 only
Depending on how your application is structured, the best way could be to pass in the window that the tray icon shall control as a property from "further up" your user interface structure.
First, extend your tray icon component and add a "window" property to it:
import QtQuick 2.9
import QtQuick.Window 2.2
import Qt.labs.platform 1.0
SystemTrayIcon {
id: trayIcon
// this property holds the window the tray icon controls:
property Window window
visible: true
iconSource: "qrc:/icons/ic_tray.png"
menu: Menu {
MenuItem {
text: qsTr("Settings")
onTriggered: {
trayIcon.window.show();
}
}
MenuItem {
text: qsTr("Quit")
onTriggered: Qt.quit() // Just hide an existing
}
}
}
Now, you could instantiate your tray icon e.g. in your main window like this:
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
id: mainWindow
width: 800
height: 600
TrayIcon { window: mainWindow }
}
In this case, the tray icon would control the main window itself; however, you can easily create a single instance of a settings window within the main window and pass that one to the tray icon.
You can create the component in the onCompleted.
SystemTrayIcon {
visible: true
iconSource: "qrc:/icons/ic_tray.png"
menu: Menu {
MenuItem {
text: qsTr("Settings")
property var form
onTriggered: {
form.show()
}
Component.onCompleted: {
// Don't create a new object if it exists, just show
var settings = Qt.createComponent("Test.qml")
form = settings.createObject(this)
}
}
MenuItem {
text: qsTr("Quit")
onTriggered: Qt.quit() // Just hide an existing
}
}
}

Can't close the window with button: QML

I've created an item, that contains a button. I'm trying to close parent window of item with this button, but I'm getting this message, when click the item:
TypeError: Property 'close' of object QQuickRootItem(0x1d8efed8) is not
a function
Can you help me with this?
Code of item:
import QtQuick 2.4
Item {
id: backButton
ItemForButton{
id: baseButton
text: "Back"
onClicked: {
backButton.parent.close()
}
}
}
Code for window:
Window {
id: window
visible: true
BackButton {
}
x: 30
y: 30
}
That seems a bit messy. If I were you, I'd add a clicked signal to the custom button type. For example:
Item:
import QtQuick 2.4
Item {
id: backButton
// Add a clicked signal here
signal clicked()
ItemForButton{
id: baseButton
text: "Back"
onClicked: {
// Emit the new clicked signal here:
backButton.clicked();
}
}
}
Window:
Window {
id: window
visible: true
BackButton {
// Respond to the signal here.
onClicked: window.close();
}
}
This provides the flexibility of using your custom BackButton type in other ways in the future.

How to access fileUrl of element "FileDialog" when using it as component in qt5?

I have made a qml file named fileDialog.qml and which use element FileDialog{} available from qt5
http://qt-project.org/doc/qt-5.1/qtquickdialogs/qml-qtquick-dialogs1-filedialog.html.
Whenever i need the location of resource i want to use fileDialog.qml as component and set all the properties like title, filter etc. These are working fine but when i tried to use id.fileUrl then no response. details are given below.
The file fileDialog.qml is
import QtQuick 2.1
import QtQuick.Dialogs 1.0
FileDialog {
id: fileDialog
objectName: "fileBrowser"
title: "Add New Layer"
visible: false
property alias selectedFilename: fileDialog.fileUrls
onAccepted: {
console.log("You chose: " + fileDialog.fileUrls)//-------- (1)
}
onRejected: {
console.log("Canceled")
}
//Component.onCompleted: visible = true
}
Now using this as component when Browse(an item in QML to be used like button) button is clicked then i am performing following steps.
onClicked: {
//Default Values fileDialog.{selectExisting = true, selectFolder = false}
fileDialog.title = "Add New Image"
//fileDialog1.selectMultiple = true
fileDialog.nameFilters = ["Image File (*.png *.jpg *.bmp)"]
//fileDialog.fileUrls
//string path
fileDialog.visible = true
console.log(" Image chosen: " + fileDialog.fileUrl + " in image")//--- (2)
}
The line (1) is working fine but but (2) is not working. The output of (2) line in console is just Image chosen: in image.
I don't understand what am I doing wrong here, because when I am setting other(like title, filer) property of component fileDialog its working but not for the fileUrl or fileUrls.
Please somebody suggest how to get the fileUrl when using it as component.
Thanks,
I think what you're trying to do is creating a special FileDialog component that fits your needs.
First, let's call this component "MyFileFialog" saved in MyFileDialog.qml.
// MyFileDialog.qml
import QtQuick 2.0
import QtQuick.Dialogs 1.0
Item {
id: root
property alias title: qmlFileDialog.title
property alias fileUrl: qmlFileDialog.fileUrl
property alias fileUrls: qmlFileDialog.fileUrls
signal accepted()
signal rejected()
function open() { qmlFileDialog.open() }
function close() { qmlFileDialog.close() }
FileDialog {
id: qmlFileDialog
modality: Qt.WindowModal
nameFilters: ["Image File (*.png *.jpg *.bmp)"]
onAccepted: root.accepted()
onRejected: root.rejected()
}
}
There are now a bunch of connections between MyFileDialog and FileFialog:
modality and nameFilters are fixed
title, fileUrl, fileUrls are passed between both components
when the FileDialog is accepted or rejected, the MyFileDialog will be as well
when you open() or close() your MyFileDialog, the inner FleDialog will open or close
Now that you have your very own MyFileDialog, you can use it:
Button {
text: "open"
MyFileDialog {
id: saveFileDialog
title: qsTr("Save to ...")
onRejected: {
console.log("Canceled")
}
onAccepted: {
console.log("File selected: " + fileUrl)
}
}
onClicked: {
saveFileDialog.open()
}
}
Just a try. From documentation, you can see that fileUrl property is only set if you make a single file selection. So you are right to expect it to be setted by your FileDialog
The problem is that you try to display fileUrl before the FileDialog is closed I think.
Showing a modal dialog probably don't block your function execution.
As you do in your base component fileDialog.qml, you can put a handler on onAccepted. When your handler will be called, fileUrl property will be available.
edit:
Browse {
id: browser
signal fileChosen
FileDialog {
id: fileDialog
//configure your fileDialog here
//...
//emit parent signal when done
onAccepted: browser.fileChosen();
}
onClicked: {
fileDialog.open();
}
onFileChosen: {
//fileUrl should be available here
console.log(fileDialog.fileUrl);
}
}
I got a solution after reading the answer of #jbh whose answer helps in accessing fileUrl outside FileDialog{} element in same qml file but that's not an answer to my question.
Element FileDialog{} have an signal accepted so we use this signal to connect to a method and then access the fileUrl or Urls. This how fileBrowser.qml look like.
import QtQuick 2.1
import QtQuick.Dialogs 1.0
FileDialog {
id: fileDialog
objectName: "fileBrowser"
title: "Add New Layer"
visible: false
//property alias selectedFilename: fileDialog.fileUrls
// signal fileChosen
// onAccepted: {
// console.log("You chose: " + fileDialog.fileUrls)
// fileChosen();
// }
onRejected: {
console.log("Canceled")
}
//Component.onCompleted: visible = true
}
onAccepted slot above is commented and we use accepted signal to access fileUrl.
This is how a qml file will look when using fileBrowser.qml as component whenever file dialog is required.
Item{
id: popup
Rectangle{
id: browse button
// properties setting for construction a button such as width, color, mouse area, states, etc..
// the method where we can use the URLS
function dialogAccepted(){
fileDialog.accepted.disconnect(dialogAccepted)
filePath.text = Qt.resolvedUrl( fileDialog.fileUrl ).toString()// to set the text in text field
console.log("You Chose in elev: " + fileDialog.fileUrl)
//browseButtonClicked(checkBox.checked)
}
onClicked: {
//Default Values fileDialog.{selectExisting = true, selectFolder = false}
fileDialog.title = "Add New Image"
//fileDialog1.selectMultiple = true
fileDialog.nameFilters = ["Image File (*.png *.jpg *.bmp)"]
fileDialog.visible = true
fileDialog.accepted.connect(dialogAccepted)
}
}
}
well, this worked for me but i am still facing problem with how to resolve Url when multiple file is selected and how to send it to c++ file so that it is accepted.

Resources