我刚刚开始为即将到来的项目使用PySide2和QML,但我立即发现一个问题:如何将python类(继承自QObject)发出的信号连接到.qml文件中的插槽? 例如:我有一个QThread(python类),每50毫秒生成一对xy坐标。我想将生成的对添加到QML文件中定义的LineSeries中,以生成类似示波器的图。
这个问题可能真的很简单,但是我真的需要一些帮助。
最诚挚的问候
Lando
编辑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);
}
}
}
}
}
}
}
}
答案 0 :(得分:1)
最简单的解决方案是使用Connections,但是在PySide / PySide2的情况下,您无法获取参数,因此我将使用它们在this answer中指向的技巧。
如果要发送点,请使用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";
}
}
}
}
}
}
}
}