How can I change all the points in an XYSeries in qml or PySide2?

前端 未结 2 1242
忘掉有多难
忘掉有多难 2021-01-23 18:51

I\'m kinda new to PySide2 and QML and I really need a way to replace all the points in an XYSeries at once. Since the QML item does not have a function that does so, I thought I

2条回答
  •  抹茶落季
    2021-01-23 19:52

    i have created a "Spectrum Analyser" Python project that is fully working, i hope it may be useful for some of you.

    (In the real-life the "createserie" function may contain SCPI commands that would read actual data from any Spectrum Analyser, Oscilloscopes...)

    This example demonstrates how to use QtQuick/QML, QtCharts and QThread together.

    After clicking the "START" button the Qthread starts and enters a infinite loop (the loop can be terminated by clicking the "STOP" button).

    In each loop some "dummy" random data (basically a "QXYSeries" of 1000 points) are generated and the plot is updated (it's actually very fast).

    I am using a QThread so that the GUI remains anytime responsive.

    I want to share this example because it took me a lot of time to write it and it was not that easy to find some good QML information online.

    Main.py:

    import sys
    import os
    # import time
    import random
    from PySide2.QtCore import Qt, QUrl, QThread, QPoint, QPointF, Slot, Signal, QObject, QProcess, Property, qInstallMessageHandler, QtInfoMsg, QtWarningMsg, QtCriticalMsg, QtFatalMsg
    from PySide2.QtQuick import QQuickView
    from PySide2.QtWidgets import QApplication, QMainWindow, QMessageBox
    # from PySide2.QtGui import QGuiApplication
    from PySide2.QtQml import QQmlApplicationEngine
    from PySide2.QtCharts import QtCharts
    # import pdb
    
    print(chr(27) + "[2J")
    
    def qt_message_handler(mode, context, message):
        if mode == QtInfoMsg:
            mode = 'Info'
        elif mode == QtWarningMsg:
            mode = 'Warning'
        elif mode == QtCriticalMsg:
            mode = 'critical'
        elif mode == QtFatalMsg:
            mode = 'fatal'
        else:
            mode = 'Debug'
        print("%s: %s (%s:%d, %s)" % (mode, message, context.file, context.line, context.file))
        
    
    class Worker1(QObject):
        set_val = Signal(QtCharts.QXYSeries)
        finished = Signal()
        
        def __init__(self, serie, parent=None):
            QObject.__init__(self, parent)
            self._serie = serie
            self._isRunning = True 
            
        def run(self):
            measure(self)    
            
        def stop(self):
            self._isRunning = False
            
            
    def measure(self): # Called inside Thread1
        while 1:
            if self._isRunning == True:
                createserie(self)
                self.set_val.emit(self._serie)
                # time.sleep(0.002)
            else:
                print("QUITING LOOP")
                break
        self.finished.emit()
        return
    
    
    def createserie(self):
        points = []
        for i in range(1001):
            points.append(QPointF(i/1000, random.random()))
        self._serie.replace(points)
        
    
    class Backend(QObject):
        setval = Signal(QtCharts.QXYSeries)  
        
        def __init__(self, parent=None):
            QObject.__init__(self, parent)
            self._serie = None
        
        @Slot(QtCharts.QXYSeries) # expose QML serie to Python
        def exposeserie(self, serie):
            self._serie = serie
            print(serie)
            print("QML serie exposed to Python")
            
        @Slot(str)
        def startthread(self, text):
            self.WorkerThread = QThread()
            self.worker = Worker1(self._serie)
            self.WorkerThread.started.connect(self.worker.run)
            self.worker.finished.connect(self.end)
            self.worker.set_val.connect(self.setval)
            self.worker.moveToThread(self.WorkerThread)  # Move the Worker object to the Thread object
            self.WorkerThread.start()
            
        @Slot(str)     
        def stopthread(self, text):
            self.worker.stop()
            print("CLOSING THREAD")
                   
        def end(self):
            self.WorkerThread.quit()
            self.WorkerThread.wait()
            msgBox = QMessageBox() 
            msgBox.setText("THREAD CLOSED")
            msgBox.exec()
            
    
    class MainWindow(QObject):
        def __init__(self, parent = None):
            # Initialization of the superclass
            super(MainWindow, self).__init__(parent)
            
            qInstallMessageHandler(qt_message_handler)
            
            self.backend = Backend()
    
            # Expose the Python object to QML
            self.engine = QQmlApplicationEngine()
                    
            self.context = self.engine.rootContext()
            self.context.setContextProperty("backend", self.backend)
            
            # Load the GUI
            self.engine.load(os.path.join(os.path.dirname(__file__), "SpecPXA_QML.qml"))
            if not self.engine.rootObjects():
                sys.exit(-1)
            
            self.win = self.engine.rootObjects()[0]
            
            # Execute a function if "Start" button clicked
            startbutton = self.win.findChild(QObject, "startbutton")
            startbutton.startclicked.connect(self.startclicked)
            
            # Execute a function if "Stop" button clicked
            stopbutton = self.win.findChild(QObject, "stopbutton")
            stopbutton.stopclicked.connect(self.stopclicked)
            
        def startclicked(self):
            print("START")
            self.backend.startthread("test")
            
        def stopclicked(self):
            print("STOP")
            self.backend.stopthread("test")
    
            
    if __name__ == "__main__":
        
        if not QApplication.instance():
            app = QApplication(sys.argv)
        else:
            app = QApplication.instance()
        app.setStyle('Fusion') # 'Breeze', 'Oxygen', 'QtCurve', 'Windows', 'Fusion'
        w = MainWindow()
        sys.exit(app.exec_())
    

    and SpecPXA_QML.qml:

    import QtQuick 2.15
    import QtQuick.Window 2.15
    import QtQuick.Controls 2.15
    import QtQuick.Dialogs 1.2
    import QtCharts 2.3
    
    
    ApplicationWindow {
        width: 1200
        height: 700
        visible: true
        title: qsTr("Hello World")
        
        property var xySeries;   
    
    //    MessageDialog {
    //        id: messageDialogQuit
    //        title: "Question:"
    //        icon: StandardIcon.Question
    //        text: "Quit program?"
    //        standardButtons: StandardButton.Yes |StandardButton.No
    //        //        Component.onCompleted: visible = true
    //        onYes: {
    //            Qt.quit()
    //            close.accepted = true
    //        }
    //        onNo: {
    //            close.accepted = false
    //        }
    //     }
    //    onClosing: {
    //        close.accepted = true
    //        onTriggered: messageDialogQuit.open()
    //    }
    
        MenuBar {
            id: menuBar
            width: Window.width
    
            Menu {
                title: qsTr("&File")
                Action { text: qsTr("&New...") }
                Action { text: qsTr("&Open...") }
                Action { text: qsTr("&Save") }
                Action { text: qsTr("Save &As...") }
                MenuSeparator { }
                Action { text: qsTr("&Quit") }
            }
            Menu {
                title: qsTr("&Edit")
                Action { text: qsTr("Cu&t") }
                Action { text: qsTr("&Copy") }
                Action { text: qsTr("&Paste") }
            }
            Menu {
                title: qsTr("&Help")
                Action { text: qsTr("&About") }
            }
        }
    
        SplitView {
            id: splitView
            y: menuBar.height
            width: Window.width
            height: Window.height-(menuBar.height+infoBar.height)
            orientation: Qt.Horizontal
            Rectangle {
                id: leftitem
                height: Window.height
                implicitWidth: 200
                color: "red"
                anchors.left: parent.left
                anchors.top: parent.top
                anchors.bottom: parent.bottom
                anchors.leftMargin: 0
                anchors.bottomMargin: 0
                anchors.topMargin: 0
    
                Button {
                    //id: startbutton
                    signal startclicked
                    objectName: "startbutton"
                    y: 40
                    height: 40
                    text: qsTr("Start")
                    anchors.left: parent.left
                    anchors.right: parent.right
                    checkable: false
                    anchors.rightMargin: 30
                    anchors.leftMargin: 30
                    onClicked: startclicked("START")
                    //onClicked: backend.text = "Button was pressed"
                }
    
                Button {
                    //id: stopbutton
                    signal stopclicked
                    objectName: "stopbutton"
                    y: 100
                    height: 40
                    text: qsTr("Stop")
                    anchors.left: parent.left
                    anchors.right: parent.right
                    checked: false
                    checkable: false
                    anchors.rightMargin: 30
                    anchors.leftMargin: 30
                    onClicked: stopclicked("STOP")
                }
    
            }
            Rectangle {
                id: rightitem
                height: Window.height
                color: "green"
                anchors.right: parent.right
                anchors.top: parent.top
                anchors.bottom: parent.bottom
                anchors.topMargin: 0
                anchors.rightMargin: 0
                anchors.bottomMargin: 0
    
                Rectangle {
                    id: rectangle
                    color: "#ffffff"
                    anchors.fill: parent
                    anchors.rightMargin: 30
                    anchors.leftMargin: 30
                    anchors.bottomMargin: 30
                    anchors.topMargin: 30
    
                    ChartView {
                        id: line
                        anchors.fill: parent
                        
                        ValueAxis {
                            id: axisX
                            min: 0
                            max: 1
                        }
    
                        ValueAxis {
                            id: axisY
                            min: 0
                            max: 1
                        }
    
    //                    LineSeries {
    //                       id: xySeries
    //                       name: "my_Serie"
    //                       axisX: axisX
    //                       axisY: axisY
    //                       useOpenGL: true
    //                       XYPoint { x: 0.0; y: 0.0 }
    //                       XYPoint { x: 1.1; y: 2.1 }
    //                       XYPoint { x: 1.9; y: 3.3 }
    //                       XYPoint { x: 2.1; y: 2.1 }
    //                       XYPoint { x: 2.9; y: 4.9 }
    //                       XYPoint { x: 3.4; y: 3.0 }
    //                       XYPoint { x: 4.1; y: 3.3 }
    //                    }
                        
                        Component.onCompleted: {
                            xySeries = line.createSeries(ChartView.SeriesTypeLine, "my_plot", axisX, axisY);  
                            xySeries.useOpenGL = true                    
                            backend.exposeserie(xySeries) // expose the serie to Python (QML to Python)
                        }
                        
                    }
                }
            }
        }
    
        MenuBar {
            id: infoBar
            x: 0
            y: 440
            width: Window.width
            height: 30
            anchors.bottom: parent.bottom
            anchors.bottomMargin: 0
        }
        
       
        
        Connections {
            target: backend
            
            function onSetval(serie) {  // "serie" is calculated in python (Python to QML)
                xySeries = serie;       // progressbar.value = val  
    //            console.log(serie);
            }
        }
    }
    

    Best Regards. Olivier.

提交回复
热议问题