在QtQuick 2中,使用QtQuick控件可以创建复杂的桌面应用程序。然而,在我看来,整个UI必须在应用程序开始时一次性声明和创建。任何你还不想使用的部分(例如文件->打开对话框)仍然必须创建,但它们是隐藏的,就像这样:
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
,甚至可以直接在javascript中动态创建QML对象,但它们非常丑陋,您将失去良好QML语法的所有好处。此外,您也无法真正"卸载"组件。Loader
声称你可以,但我试过了,我的应用程序崩溃了。
这个问题有一个优雅的解决方案吗?还是我必须咬紧牙关,一次为我的应用程序创建所有潜在的UI,然后隐藏大部分?
注意:本页提供了有关使用Loader
解决此问题的信息,但正如您所看到的,这不是一个很好的解决方案。
编辑1-为什么Loader不理想
好的,为了向您展示为什么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
}
}
这显然是方式更简单。我显然想要的是,当componentLoaded
设置为true时,加载ComplexTask
并激活其所有声明性关系,然后当componentLoaded
设置为false时,断开关系并卸载组件。我很确定目前没有办法在Qt中制作这样的东西。
从JS动态创建QML组件与从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);
}
我认为加载和卸载元素不再是实际的,因为每个用户都有超过2GB的RAM。
你认为你的应用程序可以占用512 MB以上的内存吗?我对此表示怀疑。
您应该加载qml元素,而不是卸载它们,不会发生崩溃,只需存储所有指针并操作qml帧。
如果你只是把所有QML元素都保存在RAM中并存储它们的状态,它会工作得更快,看起来更好。
我的项目就是这样发展起来的:https://youtube.com/watch?v=UTMOd2s9Vkk
我制作了所有窗口都继承的基本框架。此帧确实具有hide/show和resetState方法。基本窗口确实包含所有子帧,所以通过信号/插槽,其他帧显示/隐藏下一个所需帧。