从 QML 到 Python Signal/Slots 的数据

Posted

技术标签:

【中文标题】从 QML 到 Python Signal/Slots 的数据【英文标题】:Data from QML to Python Signal/Slots 【发布时间】:2021-06-28 19:33:33 【问题描述】:

我试图弄清楚如何将数据从 TextField 输入传递到我的 Python 类。我认为解决这个问题的方法是使用信号和插槽;但是,我在传递数据的 widget.qml(主 qml 类)中使用了自定义 QML 组件。我需要这样做,以便对通过 TextField 传入的数据执行更复杂的操作。

我对如何将数据从 BasicContainer.qml 传递到我的 python 类感到困惑,并且不确定我的 widget.qml 类中的逻辑是否支持它,因为它使用嵌套模型。我附上了一个例子,希望能用控制流来说明问题。我可以在我添加的 widget.qml 中使用基本信号,但如果我在 BasicContainer.qml 中做同样的事情,我会得到一个 PySide6.QtQuick.QQuickItem object has no attribute 'displayValueChanged'

main.py

import os
import sys

from pathlib import Path

sys.path.append(os.path.join(os.path.dirname(sys.path[0]), ".."))

from PySide6.QtCore import Property, QUrl, QObject, Qt, QCoreApplication, Slot
from PySide6.QtGui import QGuiApplication, QStandardItem, QStandardItemModel
from PySide6.QtQuick import QQuickView

CURRENT_DIRECTORY = Path(__file__).resolve().parent

# QstandardItem roles
SetText = Qt.UserRole + 1

class ControlledSignalWidget(QObject):
    def __init__(self):
        super().__init__()
        self._model = QStandardItemModel()
        # self._model.setItemRoleNames(Qt.DisplayRole: b"display")
        self._setpoints_models = []

    @Property(QObject, constant=True)
    def model(self):
        return self._model

    @Slot(int, result=QObject)
    def setpoints(self, idx):
        return self._setpoints_models[idx]

    # create custom number of widgets
    def create_widgets(self, widgets, allComponents):
        counter = 1
        # Iterates for the amount of widgets created
        for general, widget in widgets.items():
            # for key in widget:
            print("Adding new widget")
            item = QStandardItem(widget["Header"])
            self._model.appendRow(item)
            self.create_setpoints(allComponents["item" + str(counter) + "Components"])
            print("Added widget")
            counter += 1

    def create_setpoints(self, component):
        setpoints_model = QStandardItemModel()
        setpoints_model.setItemRoleNames(SetText: b"textField")
        for subWidget in component:
            print(subWidget)
            item = QStandardItem()
            item.setData(subWidget["title"], SetText)
            setpoints_model.appendRow(item)
        self._setpoints_models.append(setpoints_model)

def display(s):
    print(s)
    
if __name__ == "__main__":
    app = QGuiApplication(sys.argv)

    view = QQuickView()
    view.setResizeMode(QQuickView.SizeViewToRootObject)

    url = QUrl.fromLocalFile(os.fspath(CURRENT_DIRECTORY / "widget.qml"))

    def handle_status_changed(status):
        if status == QQuickView.Error:
            QCoreApplication.exit(-1)

    widgets = 
        "widget1": "Header": "Header Text",
        "widget2": "Header": "Header 2 Text",
    

    item1Components = ["title": "widget1random", "title": "widget1random2"]

    item2Components = ["title": "widget2random", "title": "widget2random2"]

    allComponents = 
        "item1Components": item1Components,
        "item2Components": item2Components,
    

    mainWidget = ControlledSignalWidget()
    mainWidget.create_widgets(widgets, allComponents)

    view.rootContext().setContextProperty("mainWidget", mainWidget)

    view.statusChanged.connect(
        handle_status_changed, Qt.ConnectionType.QueuedConnection
    )
    view.setSource(url)
    
    # SIGNAL CONNECTION MADE
    root = view.rootObject()
    root.displayValueChanged.connect(display)

    view.show()

    sys.exit(app.exec())

widget.qml

import QtQuick 2.0
import QtQuick.Controls.Material 2.15
import QtQuick.Layouts 1.12

Item 
    id: root
    width: 1000
    height: 800
    
    signal displayValueChanged(string setpoint)
    
    GridLayout
        columns: 3

        Repeater
            id: repeater1

            model: mainWidget.model
                ColumnLayout
                    property int outerIndex: index
                    Repeater
                        id: repeater2
                        model: mainWidget.setpoints(outerIndex)
                        ColumnLayout
                            BasicContainer
                                Component.onCompleted: 
                                    //Signal called
                                    displayValueChanged(inputText)
                                
                                
                            
                        
                    
            
        
    

BasicContainer.qml

    import QtQuick 2.12
    import QtQuick.Controls.Material 2.15
    import QtQuick.Layouts 1.12
    
    Item 

    id: basicContainerItem
    width: 300
    height: 60
    visible: true
    
    signal valueChanged()
    
    property alias inputText: containerInput.text

    Rectangle 
        id: rectangle
        width: parent.width
        
        ColumnLayout 
            TextField 
                id: containerInput
                visible: true
                placeholderText: qsTr("Text Field")
                text: "Default value"
                // Contended line
                //textColor: "#FF3333"        
                onAccepted: 
                    console.log(text)
                    basicContainerItem.valueChanged()        
                
            
                
           
                
    

重要编辑: 我修改了代码以使用信号将值从 BasicContainer 类一直传递到 Python,因此当您进行文本输入并按 Enter 时,将记录您输入的新文本。这应该解决了一切;但是,当我尝试进行任何样式更改时,例如 BasicContainer.qml 中的文本颜色更改行:textColor: "#FF3333" 将导致我的应用程序中断,从而导致此错误: root.displayValueChanged.connect(display) AttributeError 'Nonetype' object has no attribute 'displayValueChanged'

【问题讨论】:

【参考方案1】:

没关系,似乎正确的解决方案就是我上面所说的;期待我使用 textColor 的部分,因为显然这应该是 color 而不是 textColor。但是,信号按预期工作,这是我对这个问题的目标。

【讨论】:

以上是关于从 QML 到 Python Signal/Slots 的数据的主要内容,如果未能解决你的问题,请参考以下文章

如何将元组列表从 python 发送和解包到 qml?

从 Python 设置 qml 属性?

使用 QAbstractListModel 从 python 访问 QML 中的列表元素

如何将信息从 ComboBox 发送到 Python

向 QML 公开一个循环更改的 python 变量

如何正确地将 ComboBox 的模型从 python (pyQt5) 传递给 QML?