Приложение Qt 5 QML с множеством окон или сложных пользовательских интерфейсов

В QtQuick 2 с помощью элементов управления QtQuick вы можете создавать сложные настольные приложения. Однако мне кажется, что весь интерфейс пользователя должен быть объявлен и создан сразу при запуске приложения. Любые части, которые вы пока не хотите использовать (например, диалоговое окно "Файл" -> "Открыть"), должны быть созданы, но они скрыты, например:

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.

Теперь, это может быть хорошо для простых приложений, но для сложных приложений или приложений с большим количеством диалогов наверняка это сумасшедшая вещь? В традиционной модели QtWidgets вы динамически создаете свой диалог при необходимости.

Я знаю, что есть некоторые обходные пути для этого, например, вы можете использовать Loader или даже динамически создавать объекты QML непосредственно в javascript, но они очень уродливы, и вы теряете все преимущества красивого синтаксиса QML. Также вы не можете "разгрузить" компоненты. Что ж Loader утверждает, что вы можете, но я попробовал, и мое приложение упало.

Есть ли элегантное решение этой проблемы? Или мне просто нужно прикусить пулю и сразу создать весь потенциальный интерфейс для моего приложения, а затем скрыть большую его часть?

Примечание: эта страница содержит информацию об использовании Loader Чтобы обойти это, но, как вы можете видеть, это не очень хорошее решение.

Изменить 1 - Почему загрузчик неоптимальный?

Хорошо, чтобы показать вам, почему Loader не очень приятно, рассмотрим этот пример, который запускает какую-то сложную задачу и ждет результата. Предположим, что - в отличие от всех простых примеров, которые обычно приводят люди, - задача имеет много входов и несколько выходов.

Это Loader решение:

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;
    }
}

Если бы я делал это без Loader Я мог бы иметь что-то вроде этого:

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
    }
}

Это явно проще. Что я явно хочу, так это какой-то способ ComplexTask быть загруженным и активировать все его декларативные отношения, когда componentLoaded устанавливается в true, а затем отключить отношения и выгрузить компонент, когда componentLoaded установлено в false. Я уверен, что в настоящее время в Qt нет способа сделать что-то подобное.

3 ответа

Решение

Динамическое создание компонентов QML из JS так же уродливо, как динамическое создание виджетов из C++ (если не менее, поскольку это на самом деле более гибко). В этом нет ничего уродливого: вы можете реализовать свои компоненты QML в отдельных файлах, использовать каждую помощь, предоставляемую Creator при их создании, и создавать экземпляры этих компонентов там, где они вам нужны, столько, сколько вам нужно. Гораздо уродливее спрятать все с самого начала, оно также намного тяжелее и не может предвидеть все, что может произойти, также как и создание динамического компонента.

Вот минималистичный автономный пример, он даже не использует загрузчик, так как в диалоге есть локально доступный файл QML.

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
    }
}

Кроме того, приведенный выше пример очень прост и просто для иллюстрации рассмотрите возможность использования стекового представления, либо вашей собственной реализации, либо доступной начиная с версии 5.1. StackView,

Вот небольшая альтернатива ответу ddriver, который не вызывает Qt.createComponent() каждый раз, когда вы создаете экземпляр этого компонента (который будет довольно медленным):

// 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);
}

Я думаю, что загрузка и выгрузка элементов больше не актуальны, потому что у каждого пользователя более 2 ГБ ОЗУ.

И как вы думаете, ваше приложение может занять больше, чем даже 512 МБ оперативной памяти? Я сомневаюсь.

Вы должны загрузить элементы qml и не выгружать их, никаких сбоев не произойдет, просто храните все указатели и манипулируйте кадрами qml.

Если вы просто сохраните все свои элементы QML в оперативной памяти и сохраните их состояния, это будет работать быстрее и выглядеть лучше.

Примером является мой проект, который был разработан таким образом: https://youtube.com/watch?v=UTMOd2s9Vkk

Я сделал базовую раму, которая наследуется всеми окнами. Этот кадр имеет методы hide /show и resetState. Базовое окно содержит все дочерние кадры, поэтому через сигнал / слоты другие кадры показывают / скрывают следующий необходимый кадр.

Другие вопросы по тегам