Приложение 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. Базовое окно содержит все дочерние кадры, поэтому через сигнал / слоты другие кадры показывают / скрывают следующий необходимый кадр.