正题 做这个项目需要一个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 QGuiApplicationfrom PySide6.QtQml import QQmlApplicationEnginefrom PySide6.QtCore import QUrl, QObject, Signal, Slotclass 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 QtQuickimport QtQuick.Windowimport QtQuick.Controlsimport GeneratorsWindow { 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 QGuiApplicationfrom PySide6.QtQml import QQmlApplicationEngine, qmlRegisterTypefrom PySide6.QtCore import QUrl, QObject, Signal, Slotclass 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( ) 函数可以避免这种情况。