抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

最近在做学校的研究性学习任务,起初怀着挑战 盲目的态度选定了课题:基于计算机视觉与深度学习技术的linux平台开源学习辅助软硬件 (听上去是不是很高大上?其实可以说是翻译笔的拙劣模仿,但我们没有对标现有的翻译笔,因为会让我们相形见绌)现在做得磕磕绊绊…

这不是广告,只是怕有人不知道翻译笔是什么,这是有道翻译笔

顺便放一下项目链接,如果你会编程的话,欢迎加入!

https://github.com/Micraow/prism

正题

做这个项目需要一个gui,我在仔细比对后选择了Qt(PySide6),最主要是因为它比较主流,支持比较好,也能和python对接上,Qt呢,有Qt widgets
和Qt QML,Qt QML看起来代码量少一点,也比较新,被我选择。

问题在于,怎么让QML应用,使用我Python里已经写好的对象和方法?

以下内容系多方资料整理,比较硬核

1
2
3
4
5
Qt 中,C++ 和 QML 交互一般有如下三种方法

上下文属性:setContextProperty( )
向引擎注册类型:调用 qmlRegisterType( )
QML 扩展插件:虽然有很大的灵活性,但是用 Python 创建 QML 插件比较麻烦,所以这种方法不适用于 Python

将 Python 代码暴露给 QML:上下文属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import random
import sys
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtCore import QUrl, QObject, Signal, Slot

class NumberGenerator(QObject):
def __init__(self):
QObject.__init__(self)

nextNumber = Signal(int, arguments=['number'])


@Slot()
def giveNumber(self):
self.nextNumber.emit(random.randint(0, 99))

if __name__ == "__main__":
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()

number_generator = NumberGenerator()
engine.rootContext().setContextProperty('numberGenerator', number_generator)

engine.load(QUrl("main.qml"))

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

以下是QML部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import QtQuick 2.14
import QtQuick.Window 2.14
import QtQuick.Controls 2.14

Window {
id: root
width: 640
height: 480
visible: true
title: qsTr("Hello World")

Flow {
Button {
text: qsTr("Give me a number")
onClicked: numberGenerator.giveNumber()
}
Label {
id: numberLabel
text: qsTr("no number")
}
}
Connections {
target:numberGenerator
function onNextNumber(number) {
numberLabel.text = number
}
}

}

上述代码要结合 .py 文件进行理解,onClicked(发射 clicked 信号)会触发槽函数 **numberGenerator.giveNumber()**,该函数会发射 numberGenerator.nextNumber 信号,这个信号又被 QML 中的 onNextNumber 捕获,并修改 label 的显示结果。

.py 文件使用 **setContextProperty()**函数 把 Python 对象 number_generator 暴露给 QML (对应 QML 中的 numberGenerator),这种方式会直接添加到 QML 的上下文环境中,在QML 中可以直接使用,不需要重新导入,使用方便,但容易导致命名冲突。

这里使用 Slot 装饰符giveNumber() 变成槽函数,不然无法使用

将 Python 对象暴露给 QML :注册类型

QML:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import QtQuick
import QtQuick.Window
import QtQuick.Controls

import Generators

Window {
id: root

width: 640
height: 480
visible: true
title: qsTr("Hello Python World!")

Flow {
Button {
text: qsTr("Give me a number!")
onClicked: numberGenerator.giveNumber()
}
Label {
id: numberLabel
text: qsTr("no number")
}
}

NumberGenerator {
id: numberGenerator
}

Connections {
target: numberGenerator
function onNextNumber(number) {
numberLabel.text = number
}
}
}

Python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import random
import sys

from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine, qmlRegisterType
from PySide6.QtCore import QUrl, QObject, Signal, Slot

class NumberGenerator(QObject):
def __init__(self):
QObject.__init__(self)

nextNumber = Signal(int, arguments=['number'])

@Slot()
def giveNumber(self):
self.nextNumber.emit(random.randint(0, 99))


if __name__ == '__main__':
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()

qmlRegisterType(NumberGenerator, 'Generators', 1, 0, 'NumberGenerator')

# engine.load(QUrl("main.qml"))
import os
path = os.path.dirname(__file__) + os.sep + 'main.qml'
engine.load(path)

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

sys.exit(app.exec())

main.qml 文件中需要导入 Python 注册的模块 Generators,并将类实例化为 **NumberGenerator{…}**,该实例就可以向任何其他 QML 元素一样工作。

qmlRegisterType( ) 函数

把 Python 对象暴露给 QML ,主要使用 qmlRegisterType() 函数。qmlRegisterType( ) 函数来自于 PySide6.QtQml 模块并接收5个参数:

qmlRegisterType (pytype: type, uri: str, versionMajor: int, versionMinor: int, qmlName: str)
参数:
pytype (type) – Python 类(py文件中的类名)
uri (str) – 表示对类的引用,如本案例的 Generator(QML中 import 的名称)
versionMajor (int) – 主要版本编号,如本案例中的 1
versionMinor (int) – 次要版本编号,如本案例中的 0
qmlName (str) – 暴露给QML的类名称,本案例中的 NumberGenerator
返回类型:int (the QML type id)

在 QML 中调用 Python 属性的方法

这是一种常用的方法,先介绍 Python 中的 Property( ) 函数——property() 函数的作用是在新式类中返回属性值。

1
class property([fget[, fset[, fdel[, doc]]]])

参数

1
2
3
4
5
fget – 获取属性值的函数
fset – 设置属性值的函数
fdel – 删除属性值函数
doc – 属性描述信息
返回值: 返回新式类属性。

举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class C (object):
def __init__(self):
self._x = None

def getx(self):
return self._x

def setx(self, value):
self._x = value

def delx(self):
del self._x

x = property(getx, setx, delx, "I am the 'x' property.")

如果 c = C( ),则 c.x 将触发 getter 信号, c.x = value 将触发 setter 信号,del c.x 将触发 deleter 信号。

参照 Python 中的 Property( ) 函数,Qt 中不仅提供了自己的属性,还提供了信号和槽的支持。由此可以理解,以下代码的几个参数分别表示类型,已及 getter 信号、setter 信号和通知信号(当属性改变时需要发出该信号,通知属性的变化):

1
2
from PySide6.QtCore import Property
maxNumber = Property(int, get_max_number, set_max_number, notify = maxNumberChanged)

之所以绕一圈进行修改,是因为在 QML 中直接通过 JavaScript 更改属性会破坏与属性的绑定,而通过显示使用 setter( ) 函数可以避免这种情况。

结束了

评论

留下神评妙论