首页 > 解决方案 > 从 QML 到 Python Signal/Slots 的数据

问题描述

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

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

主文件

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())

小部件.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)
                                
                                }
                            }
                        }
                    }
            }
        }
    }
}

基本容器.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'

标签: pythonqtqmlpyside

解决方案


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


推荐阅读