【问题标题】:Qt 5 QML app with lots of Windows or complex UIs具有大量 Windows 或复杂 UI 的 Qt 5 QML 应用程序
【发布时间】:2014-11-06 11:35:07
【问题描述】:

在使用 QtQuick 控件的 QtQuick 2 中,您可以创建复杂的桌面应用程序。但是在我看来,整个 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,然后隐藏大部分?

注意:this page 提供了有关使用 Loaders 解决此问题的信息,但正如您所见,这不是一个很好的解决方案。

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

这显然方式更简单。我显然想要的是某种方式来加载ComplexTask 并在componentLoaded 设置为true 时激活其所有声明性关系,然后在componentLoaded 设置为false 时断开关系并卸载组件。我很确定目前在 Qt 中没有办法制作这样的东西。

【问题讨论】:

  • 为什么Loader解决方案不好?为什么丑?如果您的应用程序在将 active 设置为 false 时崩溃,这可能是您的应用程序或 Qt 代码中的错误,而不是反对使用 Loader 的论据。为什么在大型应用程序中将事物的可见性设置为 false 很疯狂?
  • 这听起来有点像我在审问你,但这只是一系列真正好奇的问题。 :)
  • 查看更新。在大型应用程序中将可见性设置为 false 很疯狂,因为组件仍在加载并使用资源。这就像那些预加载所有内容的“单页网站”。如果您只有几页,它们没问题,但如果您正在制作 eBay 或亚马逊,则不能使用该技术。

标签: qt qml qt5 qtquick2 qtquickcontrols


【解决方案1】:

从 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

【讨论】:

  • 好吧,这个例子比使用Loader 更好。为什么 QML 文件是本地的这一事实会产生影响?
  • @Timmmm - 因为如果你通过互联网加载可能需要一段时间。您将不得不将其拆分并 component.statusChanged.connect(finishCreation) 因此当组件最终为 Component.Ready 时调用 createObject() (如果它没有超时或由于其他原因失败)......或使用加载器
  • 使用该方法和加载器之间的区别在于您不会使用加载器污染您的 QML 文件,这两种方法都有效地做同样的事情。加载器也被定义并且“更统一”,而您可以通过各种方式使用手动动态实例化。
【解决方案2】:

这是 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);
}

【讨论】:

    【解决方案3】:

    我认为加载和卸载元素不再是实际的,因为每个用户都有超过 2GB 的 RAM。

    您认为您的应用程序甚至可以占用超过 512 MB 的内存吗?我怀疑。

    您应该加载 qml 元素并且不要卸载它们,不会发生崩溃,只需存储所有指针并操作 qml 帧。

    如果您只是将所有 QML 元素保存在 RAM 中并存储它们的状态,它会运行得更快并且看起来更好。

    示例是我以这种方式开发的项目:https://youtube.com/watch?v=UTMOd2s9Vkk

    我已经制作了所有窗口都继承的基本框架。这个框架确实有方法 hide/show 和 resetState。基本窗口确实包含所有子框架,因此通过信号/插槽其他框架显示/隐藏下一个所需的框架。

    【讨论】:

    • 现在这是一个糟糕答案的完美例子。它不仅是“仅链接”,而且链接甚至不包含任何信息,只是一个现成的产品,没有关于其实施的信息。
    • @user3735658 好的,请删除您的评论,我将删除我的答案。
    • 我希望您通过在其中提供一些有用的信息来改进它,然后我将能够删除反对票。否则这不是一个答案,而只是你展示你的产品。
    • @user3735658 看起来更好?
    • 我的意思是关于这个主题的信息,而不是随机的模糊相关的东西。你已经做到了,如何与它分享你的一些代码,你的产品做得很好,向我们展示如何。
    猜你喜欢
    • 1970-01-01
    • 2010-11-01
    • 1970-01-01
    • 2017-09-27
    • 1970-01-01
    • 2010-11-02
    • 2014-04-06
    相关资源
    最近更新 更多