Search code examples
qtqmlqt5qt5.3

How to change the transient parent of a QML Dialog/Window?


I'm working on a Qt (5.3) desktop application (C++ core with a QML ui) whose main window is an ApplicationWindow and that at some point launches Dialogs. Since there are differences in the use of dialog modality between Windows and Mac OS X (e. g. An about dialog is rarely modal on Mac OS X but is almost always modal on Windows) and also in the way of presenting some dialogs’ content, I’ve changed the design to allow the implementation of platform specific versions of the dialogs.

For so I created the following DialogLoader:

Loader {
    id: dialogFactory
    property string dialogName
    function platformFolder() {
        if (Qt.platform.os === "osx")
            return "osx"
        return "win"
    }
    onDialogNameChanged: { source = platformFolder() + "/" + dialogName + ".qml" }
    onStatusChanged: {
        if (dialogFactory.status === Loader.Error)
            console.log("DialogFactory: failed to load file: " + source);
        else if (dialogFactory.status === Loader.Ready)
            console.log("DialogFactory: file \"" + source + "\" loaded")
    }
}

Which I use as follows:

ApplicationWindow {
    // …
    property alias aboutDialog: aboutDialogLoader.item
    // …
    DialogLoader { id: aboutDialogLoader; dialogName: "AboutDialog" }
    // …
    Action { text: qsTr("About..."); onTriggered: aboutDialog.show() }
    // …
}

That approach is working fine and it suits my needs except for one thing: Modal dialogs on Windows don’t behave as they do when I declare them in the ApplicationWindow directly. If the app looses focus when a modal windows is opened and the focus is granted again, the modal window appears behind the main window which causes the app to be inoperative.

After some research I’ve realized that the cause of the problem is that with the loader approach the ApplicationWindow is not acting as the transient parent of the Dialog.

I've found a work-around for this by manually bringing the Dialog to front when that happens:

MainWindow {
    // ...
    onActiveChanged: {
        if (Qt.platform.os === "windows") {
            if (active) {
                // ...
                if (aboutDialog.visible) {
                    aboutDialog.requestActivate()
                }
                // ...
            }
        }
    }
    // ...
}

but I think it would be better to avoid such work-arounds, if possible. After digging into the Qt documentation without any luck, I decided to post this question, which can be resumed as follows: Is it possible to change the transient parent of a QML Window? If so, please would you point out how?

Suggestions about a different approach that could avoid the reparent stuff are welcome.


Solution

  • I guess it is impossible to do that. At least in Qt 5.4.

    From documentation (default property "data" of QML type "Window")

    data : list The data property allows you to freely mix visual children, resources and other Windows in a Window.

    If you assign another Window to the data list, the nested window will become "transient for" the outer Window.

    If you assign an Item to the data list, it becomes a child of the Window's contentItem, so that it appears inside the window. The item's parent will be the window's contentItem, which is the root of the Item ownership tree within that Window.

    If you assign any other object type, it is added as a resource.

    It should not generally be necessary to refer to the data property, as it is the default

    property for Window and thus all child items are automatically assigned to this property.

    It seems that all dynamic creation of "Window" has wrong transient parent, because of QML Binding limitation. All QML list elements is not modifiable. Thus, it is not recommended to use lists with property bindings. You'll never get notify signal if it changes. So, I guess, QML Engine never knows that new QML element is "Window" type and it necessary to add it to Windows hierarchy.

    From documentation (https://www.ics.com/files/qtdocs/qml-list.html):

    A list property cannot be modified in any other way. Items cannot be dynamically added to or removed from the list through JavaScript operations; any push() operations on the list only modify a copy of the list and not the actual list. (These current limitations are due to restrictions on Property Binding where lists are involved.)

    Maybe this is a bug, maybe feature. Anyway, for now it is impossible to load "window" type dynamically with proper transient parent.

    The best workaround I've found - use modality: Qt.ApplicationModal for dialog.