In QtQuick 2 using the QtQuick Controls you can create complex desktop apps. However it seems to me that the entire UI must be declared and create all at once at the start of the app. Any parts that you don't want to use yet (for example the File->Open dialog) must still be created but they are hidden, like this:
ApplicationWindow {
FileDialog {
id: fileOpenDialog
visible: false
// ...
}
FileDialog {
id: fileSaveDialog
visible: false
// ...
}
// And so on for every window in your app and every piece of UI.
Now, this may be fine for simple apps, but for complex ones or apps with many dialogs surely this is a crazy thing to do? In the traditional QtWidgets model you would dynamically create your dialog when needed.
I know there are some workarounds for this, e.g. you can use a Loader
or even create QML objects dynamically directly in javascript, but they are very ugly and you lose all the benefits of the nice QML syntax. Also you can't really "unload" the components. Well Loader
claims you can but I tried it and my app crashed.
Is there an elegant solution to this problem? Or do I simply have to bite the bullet and create all the potential UI for my app at once and then hide most of it?
Note: this page has information about using Loader
s to get around this, but as you can see it is not a very nice solution.
Ok, to show you why Loader
is not really that pleasant, consider this example which starts some complex task and waits for a result. Suppose that - unlike all the trivial examples people usually give - the task has many inputs and several outputs.
This is the Loader
solution:
Window {
Loader {
id: task
source: "ComplexTask.qml"
active: false
}
TextField {
id: input1
}
TextField {
id: output1
}
Button {
text: "Begin complex task"
onClicked: {
// Show the task.
if (task.active === false)
{
task.active = true;
// Connect completed signal if it hasn't been already.
task.item.taskCompleted.connect(onTaskCompleted)
}
view.item.input1 = input1.text;
// And several more lines of that...
}
}
}
function onTaskCompleted()
{
output1.text = view.item.output1
// And several more lines...
// This actually causes a crash in my code:
// view.active = false;
}
}
If I was doing it without Loader
, I could have something like this:
Window {
ComplexTask {
id: task
taskInput1: input1.text
componentLoaded: false
onCompleted: componentLoaded = false
}
TextField {
id: input1
}
TextField {
id: output1
text: task.taskOutput1
}
Button {
text: "Begin complex task"
onClicked: task.componentLoaded = true
}
}
That is obviously way simpler. What I clearly want is some way for the ComplexTask
to be loaded and have all its declarative relationships activated when componentLoaded
is set to true, and then have the relationships disconnected and unload the component when componentLoaded
is set to false. I'm pretty sure there is no way to make something like this in Qt currently.
Creating QML components from JS dynamically is just as ugly as creating widgets from C++ dynamically (if not less so, as it is actually more flexible). There is nothing ugly about it, you can implement your QML components in separate files, use every assistance Creator provides in their creation, and instantiate those components wherever you need them as much as you need them. It is far uglier to have everything hidden from the get go, it is also a lot heavier and it could not possibly anticipate everything that might happen as well dynamic component instantiation can.
Here is a minimalistic self-contained example, it doesn't even use a loader, since the dialog is locally available QML file.
Dialog.qml
Rectangle {
id: dialog
anchors.fill: parent
color: "lightblue"
property var target : null
Column {
TextField {
id: name
text: "new name"
}
Button {
text: "OK"
onClicked: {
if (target) target.text = name.text
dialog.destroy()
}
}
Button {
text: "Cancel"
onClicked: dialog.destroy()
}
}
}
main.qml
ApplicationWindow {
visible: true
width: 200
height: 200
Button {
id: button
text: "rename me"
width: 200
onClicked: {
var component = Qt.createComponent("Dialog.qml")
var obj = component.createObject(overlay)
obj.target = button
}
}
Item {
id: overlay
anchors.fill: parent
}
}
Also, the above example is very barebone and just for the sake of illustration, consider using a stack view, either your own implementation or the available since 5.1 stock StackView
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With