Qt包括QML作为一种声明性地描述用户界面并使用JavaScript作为其中的脚本语言的手段。
可以编写完整的独立QML应用程 序,或将它们与C ++结合使用。PyQt5允许QML以完全相同的方式与Python集成。
特别是:
- 从QObject中细分的Python类型可以使用QML注册。
- 可以创建已注册的Python类型的实例,并使其可用于QML脚本。
- 可以通过QML脚本创建已注册Python类型的实例。
- 注册Python类型的Singleton实例可以由QML引擎自动创建,并可用于QML脚本。
- QML脚本通过其属性,信号和插槽与Python对象进行交互。
- Python属性,信号和插槽可以给出修订号,只有特定版本实现的那些才能用于QML。
注意
PyQt对QML的支持需要了解实现QML的C ++代码的内部。这可以(并且确实)在Qt版本之间发生变化,这可能意味着某些功能仅适用于特定的Qt版本,并且可能根本不适用于Qt的某些未来版本。
建议在MVC架构中,QML只应用于实现视图。模型和控制器应该用Python实现。
注册Python类型
使用QML注册Python类型的方式与使用C ++类完成相同,即使用qmlRegisterType,qmlRegisterSingletonType, qmlRegisterUncreatableType和 qmlRegisterRevision函数。
在C ++中,这些是基于模板的函数,它们将C ++类(有时是修订版)作为模板参数。在Python实现中,这些只是作为相应函数的第一个参数传递。
一个简单的例子
以下简单示例演示了使用QML注册的Python类的实现。该类定义了两个属性。执行QML脚本,该脚本创建类的实例并设置属性的值。然后将该实例返回给Python,然后打印这些属性的值。
希望评论是自我解释的:
import sys
from PyQt5.QtCore import pyqtProperty, QCoreApplication, QObject, QUrl
from PyQt5.QtQml import qmlRegisterType, QQmlComponent, QQmlEngine
# This is the type that will be registered with QML. It must be a
# sub-class of QObject.
class Person(QObject):
def __init__(self, parent=None):
super().__init__(parent)
# Initialise the value of the properties.
self._name = ''
self._shoeSize = 0
# Define the getter of the 'name' property. The C++ type of the
# property is QString which Python will convert to and from a string.
@pyqtProperty('QString')
def name(self):
return self._name
# Define the setter of the 'name' property.
@name.setter
def name(self, name):
self._name = name
# Define the getter of the 'shoeSize' property. The C++ type and
# Python type of the property is int.
@pyqtProperty(int)
def shoeSize(self):
return self._shoeSize
# Define the setter of the 'shoeSize' property.
@shoeSize.setter
def shoeSize(self, shoeSize):
self._shoeSize = shoeSize
# Create the application instance.
app = QCoreApplication(sys.argv)
# Register the Python type. Its URI is 'People', it's v1.0 and the type
# will be called 'Person' in QML.
qmlRegisterType(Person, 'People', 1, 0, 'Person')
# Create a QML engine.
engine = QQmlEngine()
# Create a component factory and load the QML script.
component = QQmlComponent(engine)
component.loadUrl(QUrl('example.qml'))
# Create an instance of the component.
person = component.create()
if person is not None:
# Print the value of the properties.
print("The person's name is %s." % person.name)
print("They wear a size %d shoe." % person.shoeSize)
else:
# Print all errors that occurred.
for error in component.errors():
print(error.toString())
以下example.qml
是执行的QML脚本:
import People 1.0
Person {
name: "Bob Jones"
shoeSize: 12
}
使用QQmlListProperty
在Python中定义可以从QML更新的基于列表的属性是使用QQmlListProperty类完成的。但是它在Python中的使用方式与它在C ++中的使用方式略有不同。
在简单的情况下,QQmlListProperty包装一个通常是实例sttribute的Python列表,例如:
class BirthdayParty(QObject):
def __init__(self, parent=None):
super().__init__(parent)
# The list which will be accessible from QML.
self._guests = []
@pyqtProperty(QQmlListProperty)
def guests(self):
return QQmlListProperty(Person, self, self._guests)
QML现在可以操作Python Person
实例列表。QQmlListProperty还充当Python列表的代理,以便可以编写以下内容:
for guest in party.guests:
print("Guest:", guest.name)
QQmlListProperty也可用于包装虚拟 列表。以下代码片段取自chapter5-listproperties.py
PyQt5附带的 示例:
class PieChart(QQuickItem):
@pyqtProperty(QQmlListProperty)
def slices(self):
return QQmlListProperty(PieSlice, self,
append=lambda pie_ch, pie_sl: pie_sl.setParentItem(pie_ch))
PieChart
并且PieSlice
是使用qmlRegisterType注册的快速项目 。两者的实例都可以从QML创建。 slices
就PieChart
QML而言,是一个属性,是一个PieSlice
实例列表。
pyqtProperty装饰器指定该属性是QQmlListProperty,其名称是slices
,并且该slices()
函数是其getter。
getter返回QQmlListProperty的实例。这指定列表的元素应该是类型PieSlice
,PieChart
实例(即self
)具有属性,并定义将被调用以便将新元素附加到列表的可调用对象。
该append
调用传递两个参数:其属性将被更新(即,该对象PyChart
实例),并且元件被附加(即,PieSlice
实例)。在这里,我们只需将图表设置为切片的父项。请注意,实际上并没有任何列表 - 这是因为,在这个特定示例中,不需要一个列表。
append
可调用的签名与相应的C ++函数的签名略有不同。在C ++中,第一个参数是QQmlListProperty实例而不是PyChart
实例。的签名at
,clear
和count
可调用都以同样的方式不同。
使用附加属性
为了在C ++中使用附加属性,需要采取三个步骤。
- 具有附加属性的类型必须实现一个名为的静态函数
qmlAttachedProperties
。这是一个工厂,它创建要附加的属性对象的实例。 - 具有附加属性的类型需要使用
QML_DECLARE_TYPEINFO
带QML_HAS_ATTACHED_PROPERTIES
参数的宏来定义 。 - 使用
qmlAttachedPropertiesObject()
模板函数检索附加属性对象的实例 。模板类型是具有附加属性的类型。
PyQt5使用类似但稍微简单的步骤来实现相同的功能。
- 调用qmlRegisterType以注册具有附加属性的类型时,属性对象的类型将作为
attachedProperties
参数传递 。此类型将用作创建属性对象实例的工厂。 - 使用qmlAttachedPropertiesObject函数检索附加属性对象的实例, 方法与C ++相同。就像qmlRegisterType一样, qmlAttachedPropertiesObject接受另一个第一个参数,该参数在C ++中是模板参数。
有关attach.py
显示附加属性使用的完整示例,请参阅PyQt5附带的示例。
使用属性值源
属性值源在PyQt5中实现的方式与在C ++中实现的方式相同。只需从QObject和 QQmlPropertyValueSource子类, 并提供setTarget方法的实现。
使用QQmlParserStatus
监视QML解析器状态在PyQt5中实现的方式与在C ++中实现的方式相同。只需从QObject和 QQmlParserStatus中进行子类化, 并提供 classBegin和 componentComplete方法的实现。
为qmlscene编写Python插件
Qt允许编写实现QML模块的插件,这些插件可以由C ++应用程序(例如qmlscene)动态加载。这些插件是QQmlExtensionPlugin的子类。PyQt5支持完全相同的东西,并允许这些插件用Python编写。换句话说,可以将用Python编写的QML扩展提供给C ++应用程序,并将用C ++编写的QML扩展提供给Python应用程序。
PyQt5提供了一个名为的QML插件pyqt5qmlplugin
。这充当了实现插件的Python代码的包装。它处理Python解释器的加载,定位和导入包含QQmlExtensionPlugin实现的Python模块,创建该类的实例,并调用实例的registerTypes方法。默认情况下,pyqt5qmlplugin
它安装在PyQt5
Qt安装plugin
目录的子目录中。
注意
pyqt5qmlplugin
是QML看到的插件的名称。它的实际文件名将是不同的并且取决于操作系统。
QML扩展模块是包含名为的文件的目录qmldir
。该文件包含模块的名称和实现该模块的插件的名称。它还可以指定包含插件的目录。通常这不需要,因为插件安装在同一目录中。
因此,对于调用的QML扩展模块Charts
,qmldir
文件的内容 可能是:
module Charts
plugin pyqt5qmlplugin /path/to/qt/plugins/PyQt5
在pyqt5qmlplugin
希望找到一个文件名结尾在同一个目录中的Python模块plugin.py
或plugin.pyw
。在这种情况下,名称chartsplugin.py
将是一个明智的选择。在导入此模块之前,pyqt5qmlplugin
首先将目录的名称放在开头sys.path
。
注意
pyqt5qmlplugin
必须找到包含qmldir
文件本身的目录。它使用QML使用的相同算法来执行此操作,即它搜索由指定的一些标准位置和位置 QML2_IMPORT_PATH
环境变量。使用 qmlscene时,pyqt5qmlplugin
不知道其-I
选项指定的任何其他位置。因此, QML2_IMPORT_PATH
应始终用于指定要搜索的其他位置。
由于QML的限制,多个QML模块不可能使用相同的C ++插件。在C ++中,这不是问题,因为模块和插件之间存在一对一的关系。但是,使用Python时, pyqt5qmlplugin
每个模块都使用它。有两种解决方案:
- 在支持它的操作系统上,在包含
qmldir
指向实际文件的文件的目录中放置一个符号链接pyqt5qmlplugin
pyqt5qmlplugin
在包含该qmldir
文件的目录中制作副本。
在这两种情况下,qmldir
文件的内容都可以简化为:
module Charts
plugin pyqt5qmlplugin
PyQt5提供了一个可以如下运行的示例:
cd /path/to/examples/quick/tutorials/extending/chapter6-plugins
QML2_IMPORT_PATH=. /path/to/qmlscene app.qml
在Linux上,您可能还需要为其设置值 LD_LIBRARY_PATH
环境变量。