Qt Quick在QML和Python交互

前言

在 PySide2 中,Python和QML之间的交互主要使用信号与槽机制。

QML调用Python

以下是一个简单的步骤说明:

方法的类

创建一个 QObject 子类

首先,你需要创建一个继承自 QObject 的 Python 类,并在类中定义你希望在 QML 中调用的方法。

使用 @Slot 装饰器来标记这些方法,以便它们可以在 QML 中被调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from PySide2.QtCore import QObject, Slot

from utils.z_setting import save_config,get_config
from utils.z_twain_utils import select_device
from typing import Dict, List, Literal, Optional, Tuple

class QmlCommon(QObject):
@Slot(str, str, result=None)
def saveConfig(self, key, value):
save_config(key,value)


@Slot(str, result=str)
def getConfig(self, key)->str:
return get_config(key)


@Slot(result=list)
def getScannerList(self)-> list:
scanner_list = select_device()
return scanner_list

注意下面的写法是不行的

1
2
3
4
5
@Slot(result=List[str])
def getScannerList(self)-> List[str]:
scanner_list = select_device()
print(scanner_list)
return scanner_list

这是因为在 PyQt 或 PySide 中,槽函数的返回类型声明需要使用 Python 的内置类型(如 list),而不是类型注解(如 List[str])。List[str]typing 模块中的类型,而 PyQt 或 PySide 的槽机制不直接支持 typing 模块的类型声明。

改成这样也是可以的

1
2
3
4
5
@Slot(result=list)
def getScannerList(self)-> List[str]:
scanner_list = select_device()
print(scanner_list)
return scanner_list

暴露实例

将 Python 对象暴露给 QML

接下来,你需要将这个 Python 对象暴露给 QML。

你可以通过 QQuickViewQQmlApplicationEngine 来加载 QML 文件,并将 Python 对象设置为 QML 上下文中的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine

app = QGuiApplication([])

engine = QQmlApplicationEngine()

# 创建 QmlCommon 的实例
qml_object = QmlCommon()

# 将 qml_object 暴露给 QML
engine.rootContext().setContextProperty("qmlObject", qml_object)

# 加载 QML 文件
engine.load("main.qml")

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

exit(app.exec_())

注意

一定要在加载页面前设置属性,否则在Component.onCompleted生命周期中获取不到。

例如

1
2
3
4
5
6
7
Component.onCompleted: {
let saveFolderPath = qmlObject.getConfig("saveFolderPath")
if (saveFolderPath)
{
saveFolder.text = saveFolderPath;
}
}

QML中调用

在 QML 中调用 Python 方法

在 QML 文件中,你可以通过 qmlObject 来访问 myMethod 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import QtQuick 2.12
import QtQuick.Controls 2.12

ApplicationWindow {
visible: true
width: 640
height: 480

Button {
text: "Call Python Method"
onClicked: {
qmlObject.saveConfig("name", "zhangsan")
let result = qmlObject.getConfig("name")
console.info("result:"+result)
}
}
}

总结

  1. 创建一个继承自 QObject 的 Python 类,并使用 @Slot 装饰器标记你希望在 QML 中调用的方法。
  2. 将这个 Python 对象通过 setContextProperty 方法暴露给 QML。
  3. 在 QML 中通过对象名调用 Python 方法。

这样,你就可以在 QML 中调用 Python 方法了。

Python调用QML方法

准确来说这个不是Python直接调用QML的方法,而是通过QML监听Python的信号来使用同样的目的。

数据的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from PySide2.QtCore import Property, QObject, Signal, Slot

class QmlCommon(QObject):
scanNumChanged = Signal(int)

def __init__(self):
super().__init__()
self._scanNum = 0

@Property(int)
def scanNum(self):
return self._scanNum

@Slot(str)
def updateScanNum(self, newData):
if self._scanNum != newData:
self._scanNum = newData
self.scanNumChanged.emit(newData)

也就是定义信号和发送信号

1
2
3
4
# 定义信号
scanNumChanged = Signal(int)
# 发送信号
self.scanNumChanged.emit(123)

其中

  • @Property(int)是为了在QML中能取值
  • @Slot(str)是为了在QML中能调用方法

多个参数的信号

1
showToast = Signal(str, int)

注意:

信号在QML中用 on+信号名(首字母大写)进行监听。

暴露实例

1
2
3
4
5
6
7
# 创建 QmlCommon 的实例
qml_object = QmlCommon()
# 将 qml_object 暴露给 QML
engine.rootContext().setContextProperty("qmlObject", qml_object)

# 更新数据
qml_object.updateScanNum(10)

QML中监听

1
2
3
4
5
6
7
8
Connections {
function onScanNumChanged(num) {
console.log("onScanNumChanged");
scanNumComp.text = num.toString(); // 更新 QML 中的文本
}

target: qmlObject
}

监听到但是无法更新

如果我们监听到事件了,在事件中更新UI,但是不生效,是因为接受信号时,UI还没渲染完毕。

我们可以这样处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from PySide2.QtCore import QUrl, QTimer

engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty("qmlObject", qml_object)

def showUpadteDialog(msg):
qml_object.showUpdateDialogAction(msg)

def on_object_created(obj, url):
if obj is None:
print(f"QML 文件加载失败: {url}")
else:
print(f"QML 文件加载完成: {url}")
QTimer.singleShot(200, lambda: showUpadteDialog("要更新啦"))

# 连接 objectCreated 信号到槽函数
engine.objectCreated.connect(on_object_created)

注意

QML的创建完毕事件中,UI也没有渲染完毕,这里使用的延迟发送信号。

当然我们再调用接口后更新的时候UI基本都渲染完毕了。

QML绑定Python中的属性

使用Q_PROPERTY

定义数据类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from PySide2.QtCore import Property, QObject, Signal


class DataObject(QObject):
def __init__(self):
super().__init__()
self._customData = 0
customDataChanged = Signal(int)

@Property(int, notify=customDataChanged)
def customData(self):
return self._customData

@customData.setter
def customData(self, value):
self._customData = value
self.customDataChanged.emit(value)

这里属性和信号绑定,一旦收到变化信号,绑定这个属性的就会自动更新。

跟上面的区别在于,不用监听事件自己处理了。

暴露实例

1
2
3
4
5
6
7
# 创建实例
dataObj = DataObject()
# 暴露给 QML
engine.rootContext().setContextProperty("dataObj", dataObj)

# 更新数据
dataObj.customData = 123

QML中使用

1
2
3
4
5
6
7
8
9
10
11
12
Button {
text: "Change Data"
anchors.centerIn: parent
onClicked: {
dataObj.customData = 42
}
}
Text {
text: "Data value: " + dataObj.customData
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
}