向 QML 公开一个循环更改的 python 变量

Posted

技术标签:

【中文标题】向 QML 公开一个循环更改的 python 变量【英文标题】:Expose a looped changing python variable to QML 【发布时间】:2020-08-05 01:43:24 【问题描述】:

我正在尝试获取一个 python 变量并在 QML 中使用它。我的实现是创建一个按钮,单击该按钮时,它将获取从我的程序生成的数据并将浮点值附加到线系列的 x 和 y 上。有什么建议吗?

这是我的程序中的一小段代码,可以复制我的问题:

# ========================================================================  #
from math import exp


class ErrorFunction:
    def firstPoint(self, param):
        if param < 0:
            return -self.firstPoint(-param)
        else:
            p = 0.47047
            a = [0.3480242, -0.0958798, 0.7478556]
            t = 1.0 / (1.0 + p * param)
            return 1 - t * (a[0] + t * (a[1] + t * a[2])) * exp(-(param ** 2))

    def successivePoint(self, param):
        return 1 - self.firstPoint(param)
# ========================================================================  #
# ========================================================================  #
import os
import sys

from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtWidgets import QApplication

from ErrorFunction import ErrorFunction


class ErrorFunctionRunnable:
    def __init__(self):
        errorfunction = ErrorFunction()
        n = 100
        errorPoints = [(0, 0)] * n
        for i in range(n):
            x = i * 0.1 - 5.0
            errorPoints[i] = (errorfunction.firstPoint(x), errorfunction.successivePoint(x))
            print(errorPoints[i])


if __name__ == "__main__":
    os.environ["QT_QUICK_CONTROLS_STYLE"] = "Material"
    app = QApplication(sys.argv)

    engine = QQmlApplicationEngine()

    engine.load(os.path.join(os.path.dirname(__file__), "gui.qml"))

    if not engine.rootObjects():
        sys.exit(-1)

    sys.exit(app.exec_())
# ========================================================================  #
// ========================================================================  #
import QtQuick 2.0
import QtQuick.Controls 2.5
import QtQuick.Controls.Material 2.3
import QtCharts 2.3

ApplicationWindow 
    id: applicationWindow
    visible: true
    width: 720
    height: 780
    title: qsTr("QML Test")
    Material.background: Material.color(Material.Grey, Material.Shade900)

    GroupBox 
        id: groupBox
        width: 114
        anchors.top: parent.top
        anchors.topMargin: 0
        anchors.bottom: parent.bottom
        anchors.bottomMargin: 0
        anchors.left: parent.left
        anchors.leftMargin: 0

        Button 
            id: button
            x: 8
            y: 0
            width: 74
            height: 48
            text: qsTr("E_r")
            font.pointSize: 10
            Material.foreground: Material.color(Material.Grey, Material.Shade100)
            Material.background: Material.color(Material.Grey, Material.Shade800)
            onClicked: 
                line.removeAllSeries()
                /*
                Run the "ErrorFunctionRunnable" init function to generate "errorPoints"
                */
            
        
    

    GroupBox 
        id: groupBox2
        anchors.right: parent.right
        anchors.rightMargin: 0
        anchors.top: parent.top
        anchors.topMargin: 0
        anchors.left: parent.left
        anchors.leftMargin: 114
        anchors.bottom: parent.bottom
        anchors.bottomMargin: 75

        ChartView 
            id: line
            dropShadowEnabled: false
            anchors.left: parent.left
            anchors.leftMargin: 0
            anchors.top: parent.top
            anchors.topMargin: 0
            anchors.right: parent.right
            anchors.rightMargin: 0
            anchors.bottom: parent.bottom
            anchors.bottomMargin: 0
            theme: ChartView.ChartThemeDark
            antialiasing: true

            backgroundColor: Material.color(Material.Grey, Material.Shade900)
            LineSeries 
                name: "Marshak Wave"
                /*
                Update the line series with the points from "errorPoints"
                */
            
        
    

    GroupBox 
        id: groupBox1
        y: 704
        height: 76
        anchors.right: parent.right
        anchors.rightMargin: 0
        anchors.left: parent.left
        anchors.leftMargin: 114
        anchors.bottom: parent.bottom
        anchors.bottomMargin: 0


    


// ========================================================================  #

【问题讨论】:

我不确定我还能展示什么。我已经删除了所有将 python 变量暴露给 QML 的尝试,因为它们显然不起作用。 第一个代码段是不言自明的。它所做的只是将浮点数添加到绘图点数组中。 这个项目有数千行代码来产生这些情节点。它不仅仅是一个随机数生成器。这是一个科学程序,它采用数学方程并运行加热管内某些温度和气体行为的测试序列,以生成显示马沙克波的图表 我发布的 QML 位只是我想要的一个示例,而是硬编码的。 好的我明白了,给我一点时间写点东西 【参考方案1】:

一个可能的解决方案是创建一个 QObject,通过一个方法向 QML 公开 QPointF 列表,此外还必须更新轴的限制,如下所示:

import math
import os
import sys

from PySide2.QtCore import QObject, QPointF, Slot
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtWidgets import QApplication


class ErrorFunction:
    def firstPoint(self, param):
        if param < 0:
            return -self.firstPoint(-param)
        else:
            p = 0.47047
            a = [0.3480242, -0.0958798, 0.7478556]
            t = 1.0 / (1.0 + p * param)
            return 1 - t * (a[0] + t * (a[1] + t * a[2])) * math.exp(-(param ** 2))

    def successivePoint(self, param):
        return 1 - self.firstPoint(param)


class Bridge(QObject):
    @Slot(result="QVariantList")
    def produce(self):
        f = ErrorFunction()
        values = []
        n = 100
        for i in range(n):
            x = i * 0.1 - 5.0
            p = QPointF(f.firstPoint(x), f.successivePoint(x))
            values.append(p)
        return values


if __name__ == "__main__":
    os.environ["QT_QUICK_CONTROLS_STYLE"] = "Material"
    app = QApplication(sys.argv)

    bridge = Bridge()

    engine = QQmlApplicationEngine()
    engine.rootContext().setContextProperty("bridge", bridge)

    engine.load(os.path.join(os.path.dirname(__file__), "gui.qml"))

    if not engine.rootObjects():
        sys.exit(-1)

    sys.exit(app.exec_())
import QtQuick 2.0
import QtQuick.Controls 2.5
import QtQuick.Controls.Material 2.3
import QtCharts 2.3

ApplicationWindow 
    id: applicationWindow
    visible: true
    width: 720
    height: 780
    title: qsTr("QML Test")
    Material.background: Material.color(Material.Grey, Material.Shade900)

    GroupBox 
        id: groupBox
        width: 114
        anchors.top: parent.top
        anchors.topMargin: 0
        anchors.bottom: parent.bottom
        anchors.bottomMargin: 0
        anchors.left: parent.left
        anchors.leftMargin: 0

        Button 
            id: button
            x: 8
            y: 0
            width: 74
            height: 48
            text: qsTr("E_r")
            font.pointSize: 10
            Material.foreground: Material.color(Material.Grey, Material.Shade100)
            Material.background: Material.color(Material.Grey, Material.Shade800)
            onClicked: 
                line.removePoints(0, line.count)
                var x_min = line.axisX.min
                var x_max = line.axisX.max
                var y_min = line.axisY.min
                var y_max = line.axisY.max

                var values = bridge.produce()
                for(var i in values)
                    line.append(values[i].x, values[i].y)
                    x_min = Math.min(x_min, values[i].x)
                    x_max = Math.max(x_max, values[i].x)
                    y_min = Math.min(y_min, values[i].y)
                    y_max = Math.max(y_max, values[i].y)
                
                line.axisX.min = x_min
                line.axisX.max = x_max
                line.axisY.min = y_min
                line.axisY.max = y_max
            
        
    

    GroupBox 
        id: groupBox2
        anchors.fill: parent
        anchors.rightMargin: 0
        anchors.topMargin: 0
        anchors.leftMargin: 114
        anchors.bottomMargin: 75

        ChartView 
            id: view
            dropShadowEnabled: false
            anchors.fill: parent
            anchors.margins: 0
            theme: ChartView.ChartThemeDark
            antialiasing: true

            backgroundColor: Material.color(Material.Grey, Material.Shade900)
            LineSeries 
                id: line
                name: "Marshak Wave"
            
        
    
    GroupBox 
        id: groupBox1
        y: 704
        height: 76
        anchors.right: parent.right
        anchors.rightMargin: 0
        anchors.left: parent.left
        anchors.leftMargin: 114
        anchors.bottom: parent.bottom
        anchors.bottomMargin: 0
    

如果数据有很多元素,缺点是 QML 中的 for 循环,可能的优化是使用 replace 方法,但在 QML 中无法访问,因此必须将元素导出到 python:

import math
import os
import sys

from PySide2.QtCore import QObject, QPointF, Slot
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtWidgets import QApplication
from PySide2.QtCharts import QtCharts


class ErrorFunction:
    def firstPoint(self, param):
        if param < 0:
            return -self.firstPoint(-param)
        else:
            p = 0.47047
            a = [0.3480242, -0.0958798, 0.7478556]
            t = 1.0 / (1.0 + p * param)
            return 1 - t * (a[0] + t * (a[1] + t * a[2])) * math.exp(-(param ** 2))

    def successivePoint(self, param):
        return 1 - self.firstPoint(param)


class Bridge(QObject):
    @Slot(QtCharts.QAbstractSeries)
    def fill(self, series):
        axis_x = series.property("axisX")
        axis_y = series.property("axisY")
        xmin = axis_x.min()
        xmax = axis_x.max()
        ymin = axis_y.min()
        ymax = axis_y.max()
        f = ErrorFunction()
        values = []
        n = 100
        for i in range(n):
            x = i * 0.1 - 5.0
            xi = f.firstPoint(x)
            yi = f.successivePoint(x)
            p = QPointF(xi, yi)
            xmin = min(xmin, xi)
            xmax = max(xmax, xi)
            ymin = min(ymin, yi)
            ymax = max(ymax, yi)
            values.append(p)
        series.replace(values)
        axis_x.setMin(xmin)
        axis_x.setMax(xmax)
        axis_y.setMin(ymin)
        axis_y.setMax(ymax)


if __name__ == "__main__":
    os.environ["QT_QUICK_CONTROLS_STYLE"] = "Material"
    app = QApplication(sys.argv)

    bridge = Bridge()

    engine = QQmlApplicationEngine()
    engine.rootContext().setContextProperty("bridge", bridge)

    engine.load(os.path.join(os.path.dirname(__file__), "gui.qml"))

    if not engine.rootObjects():
        sys.exit(-1)

    sys.exit(app.exec_())
import QtQuick 2.0
import QtQuick.Controls 2.5
import QtQuick.Controls.Material 2.3
import QtCharts 2.3

ApplicationWindow 
    id: applicationWindow
    visible: true
    width: 720
    height: 780
    title: qsTr("QML Test")
    Material.background: Material.color(Material.Grey, Material.Shade900)

    GroupBox 
        id: groupBox
        width: 114
        anchors.top: parent.top
        anchors.topMargin: 0
        anchors.bottom: parent.bottom
        anchors.bottomMargin: 0
        anchors.left: parent.left
        anchors.leftMargin: 0

        Button 
            id: button
            x: 8
            y: 0
            width: 74
            height: 48
            text: qsTr("E_r")
            font.pointSize: 10
            Material.foreground: Material.color(Material.Grey, Material.Shade100)
            Material.background: Material.color(Material.Grey, Material.Shade800)
            onClicked: 
                bridge.fill(line)
            
        
    

    GroupBox 
        id: groupBox2
        anchors.fill: parent
        anchors.rightMargin: 0
        anchors.topMargin: 0
        anchors.leftMargin: 114
        anchors.bottomMargin: 75

        ChartView 
            id: view
            dropShadowEnabled: false
            anchors.fill: parent
            anchors.margins: 0
            theme: ChartView.ChartThemeDark
            antialiasing: true

            backgroundColor: Material.color(Material.Grey, Material.Shade900)
            LineSeries 
                id: line
                name: "Marshak Wave"
            
        
    
    GroupBox 
        id: groupBox1
        y: 704
        height: 76
        anchors.right: parent.right
        anchors.rightMargin: 0
        anchors.left: parent.left
        anchors.leftMargin: 114
        anchors.bottom: parent.bottom
        anchors.bottomMargin: 0
    

【讨论】:

以上是关于向 QML 公开一个循环更改的 python 变量的主要内容,如果未能解决你的问题,请参考以下文章

QML ListView:检测到属性“高度”的绑定循环

C.dll 如何向 Python 公开变量?

当变量在 QML 中更改其值时,如何在 Qt 中执行函数?

从 QML 生成 KeyEvent

QML 绑定整数属性 - c++ 中的更改未发送到 QML

QML:如何获取文件baseName