Подключите сигнал Python к слоту пользовательского интерфейса QML с помощью PySide2 - PullRequest
0 голосов
/ 02 января 2019

Я только начал играть с PySide2 и QML для предстоящего проекта, и я сразу же наткнулся на проблему: как мне подключить сигнал, излучаемый классом python (который наследуется от QObject), к слоту в файле .qml?Например: у меня есть QThread (класс python), который генерирует пару координат xy каждые 50 миллисекунд.Я хочу добавить сгенерированную пару в LineSeries, определенную в файле QML, для создания осциллографического графика.

Вопрос, вероятно, очень простой и тупой, но мне действительно нужна помощь.

С уважением

Ландо

РЕДАКТИРОВАТЬ 4:

Я нашел решение, но мне оно не очень нравится.Можете ли вы предложить мне (если он существует) более элегантный способ сделать это?

Код для Python:

class Manager(QObject):

    dataReady = Signal(float,float)

    def __init__(self):
        QObject.__init__(self)
        self._currX = 0
        self._currY = 0
        self._delay = 0.5
        self._multiplier = 1.0
        self._power = 1.0
        self._xIncrement = 1.0
        self._starter = False
        self._threader = None

    @Property(bool)
    def starter(self):
        return self._starter

    @starter.setter
    def setStarter(self, val):
        print("New val: {0}, oldVal: {1}".format(val,self._starter))
        if self._starter == val:
            return

        self._starter = val
        if val:
            self.start()
        else:
            self.stop()

    @Property(float)
    def multiplier(self):
        return self._multiplier

    @multiplier.setter
    def setMultiplier(self, val):
        if self._multiplier == val:
            return
        print(val)
        self._multiplier = val

    @Property(int)
    def power(self):
        return self._power

    @power.setter
    def setPower(self, val):
        if self._power == val:
            return
        print(val)
        self._power = val

    @Property(float)
    def delay(self):
        return self._delay

    @delay.setter
    def setDelay(self, val):
        if self._delay == val:
            return
        print(val)
        self._delay = val

    @Property(float)
    def xIncrement(self):
        return self._xIncrement

    @xIncrement.setter
    def setXIncrement(self, val):
        if self._xIncrement == val:
            return
        print(val)
        self._xIncrement = val

    def generatePoint(self):
        self._currX += self._xIncrement
        self._currY = self._multiplier*(self._currX**self._power)

        return self._currX,self._currY

    def stop(self):
        self._goOn = False
        if self._threader is not None:
            while self._threader.isRunning():
                sleep(0.1)

    def start(self):
        self._goOn = True
        self._threader = Threader(core=self.core)
        self._threader.start()

    def core(self):
        while self._goOn:
            x,y = self.generatePoint()
            print([x,y])
            self.dataReady.emit(x,y)
            sleep(self._delay)

class Threader(QThread):

    def __init__(self,core,parent=None):
        QThread.__init__(self,parent)
        self._core = core
        self._goOn = False

    def run(self):
        self._core()

if __name__ == "__main__":
    os.environ["QT_QUICK_CONTROLS_STYLE"] = "Material"
    app = QApplication(sys.argv)
    manager = Manager()
    engine = QQmlApplicationEngine()
    ctx = engine.rootContext()
    ctx.setContextProperty("Manager", manager)
    engine.load('main.qml')
    if not engine.rootObjects():
        sys.exit(-1)

    sys.exit(app.exec_())

Код для QML:

ApplicationWindow {
id: mainWindow
width:640
height: 480
title: qsTr("Simple ui")
visible: true
locale:locale

property var controlsColor: Material.DeepPurple
property var controlsAccent: Material.BlueGrey
property real x: 0.0
property int controlsElevation: 6
property int paneElevation: 4

function drawPoint(theX,theY){
    console.log(theX);
    mainLine.append(theX,theY)
    if (theX >= testXAxis.max){
        testXAxis.max = theX;
    }
    if (theY >= testYAxis.max){
        testYAxis.max = theY;
    }
    if (theY <= testYAxis.min){
        testYAxis.min = theY;
    }
}

function clearLine(){
    mainLine.clear();
    mainLine.append(0,0);
}

Pane{
    id: mainPanel
    anchors.fill: parent
    //Material.theme: Material.Dark

    RowLayout{
        id: mainRowLO
        anchors.fill: parent
        spacing: 15

        //Chart pane
        Pane{
            id: chartPane
            Material.elevation: paneElevation
            //Material.background: Material.Grey
            Layout.fillHeight: true
            Layout.fillWidth: true
            Layout.minimumHeight: 200
            Layout.minimumWidth: 400

            ChartView {
                id: testChart
                title: "Line"
                anchors.fill: parent
                antialiasing: true
                LineSeries {
                    id: mainLine
                    name: "LineSeries"
                    axisX: ValueAxis{
                        id: testXAxis
                        min: 0.0
                        max: 2.0
                    }
                    axisY: ValueAxis{
                        id: testYAxis
                        min: 0.0
                        max: 2.0
                    }
                    XYPoint { x: 0; y: 0 }
                }
            }
        }

        Pane{
            id: controlsPane
            Material.elevation: paneElevation
            //Material.background: Material.Grey
            Layout.fillHeight: true
            Layout.fillWidth: true
            Layout.minimumHeight: 200
            Layout.minimumWidth: 200
            Layout.maximumWidth: 200

            ColumnLayout{
                id: controlsColumnLO
                anchors.fill: parent
                spacing: 40

                Label{
                    id: powerLabel
                    text: "Exponent"
                    Layout.topMargin: 40
                    Layout.leftMargin: 10
                    Layout.rightMargin: 10
                }

                SpinBox{
                    id: powerNum
                    from: 0
                    value: 1
                    to: 5
                    stepSize: 1
                    width: 80
                    validator: DoubleValidator {
                        bottom: Math.min(powerNum.from, powerNum.to)
                        top:  Math.max(powerNum.from, powerNum.to)
                    }
                    Material.foreground: controlsColor
                    Material.accent: controlsAccent
                    Material.elevation: controlsElevation
                    Layout.fillWidth: true
                    Layout.leftMargin: 10
                    Layout.rightMargin: 10
                    editable: true
                    onValueChanged: function(){
                        Manager.power = value;
                    }
                }

                Label{
                    id: multiplierLabel
                    text: "Multiplier"
                    Layout.fillWidth: true
                    Layout.leftMargin: 10
                    Layout.rightMargin: 10
                }

                Slider{
                    id: multiplierSlider
                    from: -50
                    value: 1
                    to: 50
                    Material.foreground: controlsColor
                    Material.accent: controlsAccent
                    Material.elevation: controlsElevation
                    Layout.leftMargin: 10
                    Layout.rightMargin: 10
                    Layout.fillWidth: true
                    onValueChanged: function(){
                        Manager.multiplier = value;
                    }
                }

                Label{
                    id: multValueLabel
                    text: String(multiplierSlider.value)
                    Layout.leftMargin: 10
                    Layout.rightMargin: 10
                }

                Label{
                    id: delayLable
                    text: "Delay[s]"
                    Layout.fillWidth: true
                    Layout.leftMargin: 10
                    Layout.rightMargin: 10
                }

                Slider{
                    id: delaySlider
                    from: 0.05
                    value: 0.1
                    to: 1
                    stepSize: 0.01
                    Material.foreground: controlsColor
                    Material.accent: controlsAccent
                    Material.elevation: controlsElevation
                    Layout.leftMargin: 10
                    Layout.rightMargin: 10
                    Layout.fillWidth: true
                    onValueChanged: function(){
                        Manager.delay = value;
                    }
                }

                Label{
                    id: incrementLable
                    text: "Increment"
                    Layout.fillWidth: true
                    Layout.leftMargin: 10
                    Layout.rightMargin: 10
                }

                Slider{
                    id: incrementSlider
                    from: 1.0
                    value: 1.0
                    to: 5.0
                    stepSize: 0.01
                    Material.foreground: controlsColor
                    Material.accent: controlsAccent
                    Material.elevation: controlsElevation
                    Layout.leftMargin: 10
                    Layout.rightMargin: 10
                    Layout.fillWidth: true
                    onValueChanged: function(){
                        Manager.xIncrement = value;
                    }
                }

                Item {
                    // spacer item
                    id: controlsSpacer
                    Layout.fillWidth: true
                    Layout.fillHeight: true
                    Pane { anchors.fill: parent }//; Material.background: Material.Light; Material.elevation: 4 } // to visualize the spacer
                }

                Button{
                    id: startPointBtn
                    text: "START"
                    Material.foreground: controlsColor
                    Material.accent: controlsAccent
                    Material.elevation: controlsElevation
                    Layout.fillWidth: true
                    Layout.leftMargin: 10
                    Layout.rightMargin: 10
                    onClicked: function(){
                        console.log(text);
                        console.log(text=="START")
                        if(text=="START"){
                            Manager.starter = true;
                            Manager.dataReady.connect(drawPoint);
                            clearLine();
                            text = "STOP";
                        }
                        else{
                            Manager.starter = false;
                            text = "START";
                            Manager.dataReady.disconnect(drawPoint);
                        }
                    }
                }
            }
        }
    }
}
}

1 Ответ

0 голосов
/ 02 января 2019

Самое простое решение - использовать Connections, но в случае PySide / PySide2 вы не можете получить аргументы, поэтому я воспользуюсь уловкой, на которую они указывают в этом ответе .

Если вы собираетесь отправить точку, используйте QPoint, так как она прямо переведена в тип точки в QML.Вы также должны рассчитать максимальное и минимальное значения для обновления осей.

Учитывая вышеизложенное, решение состоит в следующем:

*. Py

import os
import sys
import time
from PySide2 import QtCore, QtWidgets, QtQml

class Manager(QtCore.QObject):
    dataReady = QtCore.Signal(QtCore.QPointF, name='dataReady')

    def __init__(self, parent=None):
        super(Manager, self).__init__(parent)
        self._currX = 0
        self._currY = 0
        self._delay = 0.5
        self._multiplier = 1.0
        self._power = 1.0
        self._xIncrement = 1.0
        self._starter = False
        self._goOn = False
        self._threader = None

    @QtCore.Property(bool)
    def starter(self):
        return self._starter

    @starter.setter
    def setStarter(self, val):
        if self._multiplier == val:
            return
        print(val)
        if val:
            self.start()
        else:
            self.stop()
        self._starter = val

    @QtCore.Property(float)
    def multiplier(self):
        return self._multiplier

    @multiplier.setter
    def setMultiplier(self, val):
        if self._multiplier == val:
            return
        print(val)
        self._multiplier = val

    @QtCore.Property(int)
    def power(self):
        return self._power

    @power.setter
    def setPower(self, val):
        if self._power == val:
            return
        print(val)
        self._power = val

    @QtCore.Property(float)
    def delay(self):
        return self._delay

    @delay.setter
    def setDelay(self, val):
        if self._delay == val:
            return
        print(val)
        self._delay = val

    @QtCore.Property(float)
    def xIncrement(self):
        return self._xIncrement

    @xIncrement.setter
    def setXIncrement(self, val):
        if self._xIncrement == val:
            return
        print(val)
        self._xIncrement = val

    def generatePoint(self):
        self._currX += self._xIncrement
        self._currY = self._multiplier*(self._currX**self._power)

        return self._currX,self._currY

    def stop(self):
        self._goOn = False
        if self._threader is not None:
            while self._threader.isRunning():
                time.sleep(0.1)

    def start(self):
        self._goOn = True
        self._threader = Threader(self.core, self)
        self._threader.start()

    def core(self):
        while self._goOn:
            p = QtCore.QPointF(*self.generatePoint())
            self.dataReady.emit(p)
            time.sleep(self._delay)

# ------------------------------------------------- 

class Threader(QtCore.QThread):
    def __init__(self,core,parent=None):
        super(Threader, self).__init__(parent)
        self._core = core

    def run(self):
        self._core()

if __name__ == "__main__":
    os.environ["QT_QUICK_CONTROLS_STYLE"] = "Material"
    app = QtWidgets.QApplication(sys.argv)
    manager = Manager()
    app.aboutToQuit.connect(manager.stop)
    manager.start()
    engine = QtQml.QQmlApplicationEngine()
    ctx = engine.rootContext()
    ctx.setContextProperty("Manager", manager)
    engine.load('main.qml')
    if not engine.rootObjects():
        sys.exit(-1)

    sys.exit(app.exec_())

*. Qml

import QtQuick 2.9
import QtCharts 2.2
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.5
import QtQuick.Controls.Material 2.12

ApplicationWindow {
    id: mainWindow
    width:640
    height: 480
    title: qsTr("Simple ui")
    visible: true
    locale:locale

    property int controlsColor: Material.DeepPurple
    property int controlsAccent: Material.BlueGrey
    property real x: 0.0
    property int controlsElevation: 6
    property int paneElevation: 4

    signal reemitted(point p)
    Component.onCompleted: Manager.dataReady.connect(mainWindow.reemitted)
    onReemitted: {
        testXAxis.max = Math.max(testXAxis.max, p.x)
        testXAxis.min = Math.min(testXAxis.min, p.x)
        testYAxis.max = Math.max(testYAxis.max, p.y)
        testYAxis.min = Math.min(testYAxis.min, p.y)
        mainLine.append(p.x, p.y)
    }

    function drawPoint(xy){
        mainLine.append(xy[0],xy[1])
        if (mainWindow.x >= testXAxis.max){
            testXAxis.max = mainWindow.x;
        }
        if (py >= testYAxis.max){
            testYAxis.max = py;
        }
        if (py <= testYAxis.min){
            testYAxis.min = py;
        }
    }

    function clearLine(){
        mainLine.clear();
        mainLine.append(0,0);
    }

    Pane{
        id: mainPanel
        anchors.fill: parent
        //Material.theme: Material.Dark

        RowLayout{
            id: mainRowLO
            anchors.fill: parent
            spacing: 15

            //Chart pane
            Pane{
                id: chartPane
                Material.elevation: paneElevation
                //Material.background: Material.Grey
                Layout.fillHeight: true
                Layout.fillWidth: true
                Layout.minimumHeight: 200
                Layout.minimumWidth: 400

                ChartView {
                    id: testChart
                    title: "Line"
                    anchors.fill: parent
                    antialiasing: true
                    LineSeries {
                        id: mainLine
                        name: "LineSeries"
                        axisX: ValueAxis{
                            id: testXAxis
                            min: 0.0
                            max: 2.0
                        }
                        axisY: ValueAxis{
                            id: testYAxis
                            min: 0.0
                            max: 2.0
                        }
                        XYPoint { x: 0; y: 0 }
                    }
                }
            }

            Pane{
                id: controlsPane
                Material.elevation: paneElevation
                //Material.background: Material.Grey
                Layout.fillHeight: true
                Layout.fillWidth: true
                Layout.minimumHeight: 200
                Layout.minimumWidth: 200
                Layout.maximumWidth: 200

                ColumnLayout{
                    id: controlsColumnLO
                    anchors.fill: parent
                    spacing: 40

                    Label{
                        id: powerLabel
                        text: "Exponent"
                        Layout.topMargin: 40
                        Layout.leftMargin: 10
                        Layout.rightMargin: 10
                    }

                    SpinBox{
                        id: powerNum
                        from: 0
                        value: 1
                        to: 5
                        stepSize: 1
                        width: 80
                        validator: DoubleValidator {
                            bottom: Math.min(powerNum.from, powerNum.to)
                            top:  Math.max(powerNum.from, powerNum.to)
                        }
                        Material.foreground: controlsColor
                        Material.accent: controlsAccent
                        Material.elevation: controlsElevation
                        Layout.fillWidth: true
                        Layout.leftMargin: 10
                        Layout.rightMargin: 10
                        editable: true
                        onValueChanged: function(){
                            Manager.power = value;
                        }
                    }

                    Label{
                        id: multiplierLabel
                        text: "Multiplier"
                        Layout.fillWidth: true
                        Layout.leftMargin: 10
                        Layout.rightMargin: 10
                    }

                    Slider{
                        id: multiplierSlider
                        from: -50
                        value: 1
                        to: 50
                        Material.foreground: controlsColor
                        Material.accent: controlsAccent
                        Material.elevation: controlsElevation
                        Layout.leftMargin: 10
                        Layout.rightMargin: 10
                        Layout.fillWidth: true
                        onValueChanged: function(){
                            Manager.multiplier = value;
                        }
                    }

                    Label{
                        id: multValueLabel
                        text: String(multiplierSlider.value)
                        Layout.leftMargin: 10
                        Layout.rightMargin: 10
                    }

                    Label{
                        id: delayLable
                        text: "Delay[s]"
                        Layout.fillWidth: true
                        Layout.leftMargin: 10
                        Layout.rightMargin: 10
                    }

                    Slider{
                        id: delaySlider
                        from: 0.05
                        value: 0.1
                        to: 1
                        stepSize: 0.01
                        Material.foreground: controlsColor
                        Material.accent: controlsAccent
                        Material.elevation: controlsElevation
                        Layout.leftMargin: 10
                        Layout.rightMargin: 10
                        Layout.fillWidth: true
                        onValueChanged: function(){
                            Manager.delay = value;
                        }
                    }

                    Label{
                        id: incrementLable
                        text: "Increment"
                        Layout.fillWidth: true
                        Layout.leftMargin: 10
                        Layout.rightMargin: 10
                    }

                    Slider{
                        id: incrementSlider
                        from: 1.0
                        value: 1.0
                        to: 5.0
                        stepSize: 0.01
                        Material.foreground: controlsColor
                        Material.accent: controlsAccent
                        Material.elevation: controlsElevation
                        Layout.leftMargin: 10
                        Layout.rightMargin: 10
                        Layout.fillWidth: true
                        onValueChanged: function(){
                            Manager.xIncrement = value;
                        }
                    }

                    Item {
                        // spacer item
                        id: controlsSpacer
                        Layout.fillWidth: true
                        Layout.fillHeight: true
                        Pane { anchors.fill: parent }//; Material.background: Material.Light; Material.elevation: 4 } // to visualize the spacer
                    }

                    Button{
                        id: startPointBtn
                        text: "START"
                        Material.foreground: controlsColor
                        Material.accent: controlsAccent
                        Material.elevation: controlsElevation
                        Layout.fillWidth: true
                        Layout.leftMargin: 10
                        Layout.rightMargin: 10
                        onClicked: function(){
                            console.log(text);
                            if(text=="START"){
                                clearLine();
                                Manager.starter = true;
                                text = "STOP";
                            }
                            else{
                                Manager.starter = false;
                                text = "START";
                            }
                        }
                    }
                }
            }
        }
    }
}

enter image description here

...