5

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 Loaders to get around this, but as you can see it is not a very nice solution.

Edit 1 - Why is Loader suboptimal?

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.

Timmmm
  • 88,195
  • 71
  • 364
  • 509
  • Why is the Loader solution not nice? Why is it ugly? If your app crashes when setting active to false, it's either a bug in your app or Qt code, and isn't an argument against using Loader. Why is setting the visibility of things to false in a large application crazy? – Mitch Nov 06 '14 at 12:19
  • That sounds a bit like I'm interrogating you, but it's just a series of genuinely curious questions. :) – Mitch Nov 06 '14 at 12:20
  • See updates. And setting the visibility to false in large apps is crazy because the component is still loaded and uses resources. It's like those "one page websites" that preload everything. They're ok if you've only got a few pages, but you can't use that technique if you're making an eBay or Amazon. – Timmmm Nov 06 '14 at 16:10

3 Answers3

6

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.

dtech
  • 47,916
  • 17
  • 112
  • 190
  • Ok that example is nicer than using `Loader`. Why does the fact that the QML file is local make a difference btw? – Timmmm Nov 06 '14 at 16:13
  • @Timmmm - cuz if you load over the internet it may take a while. You will have to split it up and `component.statusChanged.connect(finishCreation)` so you call `createObject()` when the component finally is `Component.Ready` (if it doesn't time out or fail for some other reason) ... or use a loader – dtech Nov 06 '14 at 16:18
  • The difference between using that approach and loaders is you do not pollute your QML files with loaders, both approaches do effectively the same thing. Also the loader is defined and "more uniform" whereas you can use manual dynamic instantiation in various ways. – dtech Nov 06 '14 at 16:24
0

Here's a slight alternative to ddriver's answer that doesn't call Qt.createComponent() every time you create an instance of that component (which will be quite slow):

// Message dialog box component.
Component {
    id: messageBoxFactory
    MessageDialog {
    }
}
// Create and show a new message box.
function showMessage(text, title, modal)
{
    if (typeof modal === 'undefined')
        modal = true;

    // mainWindow is the parent. We can also specify initial property values.
    var messageDialog = messageBoxFactory.createObject(mainWindow, {
                                                           text: text,
                                                           title: title,
                                                           visible: true,
                                                           modality: modal ? Qt.ApplicationModal : Qt.NonModal
                                                       } );

    messageDialog.accepted.connect(messageDialog.destroy);
    messageDialog.rejected.connect(messageDialog.destroy);
}
Timmmm
  • 88,195
  • 71
  • 364
  • 509
-2

I think loading and unloading elements is not actual any more because every user have more than 2GB RAM.

And do you think your app can take more than even 512 MB ram? I doubt it.

You should load qml elements and don't unload them, no crashes will happens, just store all pointers and manipulate qml frames.

If you just keep all your QML elements in RAM and store their states, it will works faster and looks better.

Example is my project that developed in that way: https://youtube.com/watch?v=UTMOd2s9Vkk

I have made base frame that inherited by all windows. This frame does have methods hide/show and resetState. Base window does contains all child frames, so via signal/slots other frames show/hide next required frame.

IGHOR
  • 691
  • 6
  • 22
  • 2
    Now this is a perfect example of a bad answer. It is not only "link only", but the link doesn't even contain any information whatsoever, only a ready product with no information on its implementation. –  Nov 06 '14 at 13:17
  • @user3735658 ok, please remove your comment and I'll delete my answer. – IGHOR Nov 06 '14 at 13:22
  • I'd prefer you improve on it by providing some useful information in it, then I will be able to remove the downvote. Otherwise it is not an answer but just you showing your product off. –  Nov 06 '14 at 13:25
  • @user3735658 looks better? – IGHOR Nov 06 '14 at 13:37
  • I meant information ON THE SUBJECT, not random vaguely related stuff. You've done it, how about share some of your code with it, you've done a good job with your product, show us how. –  Nov 06 '14 at 13:40