SCP3008
SCP3008

Reputation: 145

Data from QML to Python Signal/Slots

I am trying to figure out how to pass data from a TextField input to my Python class. I think the way to go about this is through using signals and slots; however, I am using custom QML components in my widget.qml (main qml class) that are passing the data. I need to do this so I can perform more complex operations on the data passed in through the TextField.

I am confused about how I can pass data from my BasicContainer.qml to my python class, and not sure if the logic in my widget.qml class would even support that since it uses nested models. I've attached an example that hopefully gets to the point with the control flow. I can use a basic signal in widget.qml that I added, but if I do the same thing in BasicContainer.qml I get a 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()        
                }
            }
                
        }   
                
    }
}

IMPORTANT EDIT: I modified the code to use signals to pass values from the BasicContainer class all the way to Python, so when you make a text input and press enter, the new text you enter will be logged. This should have solved everything; however, the moment I try to make any styling changes like the text color change line in BasicContainer.qml: textColor: "#FF3333" will cause my application to break, resulting in this error: root.displayValueChanged.connect(display) AttributeError 'Nonetype' object has no attribute 'displayValueChanged'

Upvotes: 0

Views: 1336

Answers (1)

SCP3008
SCP3008

Reputation: 145

Nevermind, it seems that the correct solution is just what I had above; expect for the part where I used textColor because apparently that is just supposed to be color and not textColor.However, the signals work as expected which was my goal to this question.

Upvotes: 1

Related Questions