使用 PySide2 将 python 信号连接到 QML ui 插槽
Connect python signal to QML ui slot with PySide2
我刚开始为即将进行的项目使用 PySide2 和 QML,我立即发现了一个问题:如何连接 python class 发出的信号(继承自QObject) 到 .qml 文件中的槽?
例如:我有一个 QThread (python class),它每 50 毫秒生成几个 xy 坐标。我想将生成的对添加到 QML 文件中定义的 LineSeries,以生成类似示波器的图。
这个问题可能非常基础和愚蠢,但我真的需要一些帮助。
此致
兰多
编辑 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);
}
}
}
}
}
}
}
}
最简单的解决方案是使用连接,但在 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 starter(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 multiplier(self, val):
if self._multiplier == val:
return
print(val)
self._multiplier = val
@QtCore.Property(int)
def power(self):
return self._power
@power.setter
def power(self, val):
if self._power == val:
return
print(val)
self._power = val
@QtCore.Property(float)
def delay(self):
return self._delay
@delay.setter
def delay(self, val):
if self._delay == val:
return
print(val)
self._delay = val
@QtCore.Property(float)
def xIncrement(self):
return self._xIncrement
@xIncrement.setter
def xIncrement(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";
}
}
}
}
}
}
}
}
我刚开始为即将进行的项目使用 PySide2 和 QML,我立即发现了一个问题:如何连接 python class 发出的信号(继承自QObject) 到 .qml 文件中的槽? 例如:我有一个 QThread (python class),它每 50 毫秒生成几个 xy 坐标。我想将生成的对添加到 QML 文件中定义的 LineSeries,以生成类似示波器的图。
这个问题可能非常基础和愚蠢,但我真的需要一些帮助。
此致
兰多
编辑 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);
}
}
}
}
}
}
}
}
最简单的解决方案是使用连接,但在 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 starter(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 multiplier(self, val):
if self._multiplier == val:
return
print(val)
self._multiplier = val
@QtCore.Property(int)
def power(self):
return self._power
@power.setter
def power(self, val):
if self._power == val:
return
print(val)
self._power = val
@QtCore.Property(float)
def delay(self):
return self._delay
@delay.setter
def delay(self, val):
if self._delay == val:
return
print(val)
self._delay = val
@QtCore.Property(float)
def xIncrement(self):
return self._xIncrement
@xIncrement.setter
def xIncrement(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";
}
}
}
}
}
}
}
}