将属性注入 QML 组件

Posted

技术标签:

【中文标题】将属性注入 QML 组件【英文标题】:Injecting Properties into QML-Component 【发布时间】:2018-03-21 22:40:59 【问题描述】:

我对 Qt/PyQt 还很陌生,目前正在为一些基本功能而苦苦挣扎。我想要做的是,从 python 动态加载 QML-Views (*.qml) 文件并动态替换特定内容。例如,一个复选框被选中,我当前视图的一部分被另一个 qml 文件替换。首先我想通过 PyQt 提供这个逻辑,但 StackView 似乎是一个更好的主意 (multiple qml files in pyqt)。

但是,在这种情况下,我无法将属性注入到我的 QML 文件中。我只能将一个属性注入到 rootContext 中。然而,这限制了我的 QML 视图的使用,因为我一次只能使用一个视图(相同类型)。我想将属性动态注入到 QML-Views 中,并使它们只对这个特定的视图可见。在这种情况下,我可以在后端使用多个对象多次使用同一个视图来捕获信号。

这是我的 SimpleMainWindow.qml 文件(主视图:

import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
import QtQuick.Controls 1.4

ApplicationWindow 
    id: window
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")
    objectName : "WINDOW"
    property ApplicationWindow appWindow : window


这里是我尝试加载的文件(TestViewButton.qml):

import QtQuick 2.9
import QtQuick.Controls 1.4

Button 
    id: test
    text: "test"
    objectName : "Button"
    onClicked: 
        configurator.sum(1, 2)
        configurator.info("TestOutput")
    

Python 文件加载 QML-View(组件)

from PyQt5.QtCore import QObject, QUrl
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQml import QQmlApplicationEngine, QQmlComponent

if __name__ == "__main__":
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()

    engine.load("qml/SimpleMainWindow.qml")
    engine.quit.connect(app.quit)

    rootWindow = engine.rootObjects()[0].children()[0].parent()

    # create component
    component = QQmlComponent(engine)
    component.loadUrl(QUrl("qml/TestViewButton.qml"))

    configurator = SignalHandler()
    component.setProperty("configurator", configurator)

    itm = component.create()
    itm.setParentItem(rootWindow.children()[0])

    itm.setProperty("configurator", configurator)

    app.exec_()

还有我用来处理来自视图的信号的python对象(SignalHandler.py):

from PyQt5.QtCore import QObject, pyqtSlot

class SignalHandler(QObject):

    @pyqtSlot(int, int)
    def sum(self, arg1, arg2):
        print("Adding two numbers")

    @pyqtSlot(str)
    def info(self, arg1):
        print("STR " + arg1)

按钮加载正常(顺便说一句,有没有更好的方法来识别我想要添加按钮的父级,没有使用 findChild 进行任何查看)。不工作的是 component.setProperty.... 部分。如果我在引擎的 rootContext 中设置属性,它可以正常工作(调用 SignalHandler 方法)。已经检查过类似的主题(如Load a qml component from another qml file dynamically and access that component's qml type properties ...)

这可能吗,还是我这里有什么问题?

谢谢

【问题讨论】:

什么是 SignalHandler? 我也添加了 SignlaHandler 代码。它只是一个简单的 QObject,用于处理来自视图的一些调用。 【参考方案1】:

据我了解,您只想在TestViewButton.qml 中加载配置对象,而在SimpleMainWindow.qml 中不可见。

为此,TestViewButton.qml 在加载时必须有自己的QQmlContext,而不是rootContext()

为了测试我的响应并观察该行为,我们将创建一个类似的按钮来尝试使用configurator,如果按下它应该会抛出一个错误,指出该属性不存在但如果加载的按钮被按下QQmlComponent 应该正常工作。

qml/SimpleMainWindow.qml

import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.3
import QtQuick.Controls 1.4

ApplicationWindow 
    id: window
    visible: true
    width: 640
    color: "red"
    height: 480
    title: qsTr("Hello World")

    Button 
        x: 100
        y: 100
        text: "ApplicationWindow"
        onClicked: 
            console.log("ApplicationWindow")
            configurator.sum(1, 2)
            configurator.info("TestOutput")
        
    

正如我之前评论的那样,我添加了具有新上下文的组件:

if __name__ == "__main__":
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()

    configurator = SignalHandler()

    engine.load("qml/SimpleMainWindow.qml")
    engine.quit.connect(app.quit)

    rootWindow = engine.rootObjects()[0]
    content_item = rootWindow.property("contentItem")

    context = QQmlContext(engine)
    component = QQmlComponent(engine)
    component.loadUrl(QUrl("qml/TestViewButton.qml"))
    itm = component.create(context)
    context.setContextProperty("configurator", configurator)
    itm.setProperty("parent", content_item)

    sys.exit(app.exec_())

最后我们得到以下输出:

qml: Component
Adding two numbers
STR TestOutput
qml: ApplicationWindow
file:///xxx/qml/SimpleMainWindow.qml:20: ReferenceError: configurator is not defined
qml: Component
Adding two numbers
STR TestOutput
qml: ApplicationWindow
file:///xxx/qml/SimpleMainWindow.qml:20: ReferenceError: configurator is not defined

我们在哪里观察到期望的行为。完整的例子可以在下面的link找到。

【讨论】:

非常感谢。这正是我正在寻找的。另一个问题:让 python 加载 QML-Views 是一种好习惯(我要使用一种 MVC/MVVM 模式),还是应该以某种方式将它委托给 QML。有没有更简单的方法来识别我认为的元素? (目前使用 objectName 属性和 findChild-Method)。再次感谢 @spooky 实际上不推荐从 python/C++ 创建组件,而是创建新类型的项目或属性。例如,在python/C++中创建业务逻辑并注入到QML中,让GUI的主要任务去做QML。另一个问题,通过 objectName 和 findChildren 访问是访问元素的最佳方式。 我可能在这里遗漏了一些东西。我已经看到我可以注册自己的类型(通过 qmlRegisterType),如果我使用它,我的 QObject 的新对象(使用 pyqtProperty)就会被创建。但是,我的业务逻辑仍应决定创建哪个(以及何时)特定视图。然后我仍然需要从 python 加载 qml 文件或类型。也许你有一个链接/教程,我可以看看? 我建议您不要将 .py 与 .qml 混合太多,创建一个用 python 制作的对象,但管理 QML 中的可见性。我的意思是尽量在 QML 中做大​​部分事情,如果你不能在 .py 中做。要加载新组件,有 Loaders 或 Component。 QML 是准备做大部分事情的,实际上很多 QML 的东西都是用 python 写的。

以上是关于将属性注入 QML 组件的主要内容,如果未能解决你的问题,请参考以下文章

如何在重用 QML 组件时读写属性更改

27.Qt Quick QML-StateTransition

如何使用 Angular 中的依赖注入将属性指令实例传递给嵌套组件

是否可以将服务注入 Angular 1.5 组件的 templateUrl 属性?

QML QtCreator 使用无效代码重新格式化组件类型的属性

将属性默认值注入 3rd 方 Vue 组件