17. QML与Python混合开发

一、QML与Python混合开发

  为了实现用户界面与应用程序逻辑分离的目的,QML 支持使用 Python 进行扩展,允许将 QML、JavaScript 和 Python 三者进行混合开发。由于 QML 引擎与 Qt 元对象系统的集成,实现了在 QML 中可以直接调用 Python 的功能,而 QML 模块提供的 Python 类能够帮助开发人员从 Python 加载、维护 QML 对象。

  通过 QM L与 Python 的集成,可以提供下面这些优势。

  • 将用户界面代码与应用程序逻辑代码分离。用户界面可以基于 QML 和 JavaScript 实现,程序逻辑则可以使用 Python 实现。
  • 在 QML 中调用 Python 功能。例如,调用程序逻辑、使用由 Python 实现的数据模型或者调用第三方 Python 库中的一些函数等。
  • 使用 Qt QML 或 Qt Quick 模块中现成的 Python 接口。例如,使用 QQuickImageProvider 来动态生成图像。
  • 使用 Python 实现自定义的 QML 对象类型。既可以在自己指定的应用程序中使用,也可以分配给其他程序使用。

  我们可以在终端中使用 pip 安装 PySide6 模块。默认是从国外的主站上下载,因此,我们可能会遇到网络不好的情况导致下载失败。我们可以在 pip 指令后通过 -i 指定国内镜像源下载

pip install pyside6 -i https://mirrors.aliyun.com/pypi/simple

  国内常用的 pip 下载源列表:

二、QML中调用Python类

  我们可以将 QObject 的子类可以注册到 QML 类型系统,这样就可以在 QML 代码中将该 Python 类作为一个数据类型使用,从而访问它的 属性槽函数信号

2.1、QML调用Python类的槽函数

  我们新建一个 my_object.py 文件,该文件定义一个类继承于 QObject 。在该类中,我们定义了一个槽函数。

from PySide6.QtCore import QObject
from PySide6.QtCore import Slot

# 创建一个类继承自QObject
class MyObject(QObject):
    
    # 定义一个槽函数
    @Slot(str, int)
    def my_slot(self, name: str, age: int) -> None:
        print(f"你好,我是{name},我今年{age}岁")

  我们新建一个 template.py 文件,该文件在加载 QML 引擎之后加载 QML 文件之前使用 content = engine.rootContext() 方法 获取引擎的上下文。然后,通过 content.setContextProperty("上下文属性名", 类对象) 方法 设置上下文属性

import sys

from PySide6.QtWidgets import QApplication
from PySide6.QtQml import QQmlApplicationEngine

from my_object import MyObject

if __name__ == "__main__":
    my_object = MyObject()

    app = QApplication(sys.argv)                                                # 1.创建一个QApplication类的实例
    engine = QQmlApplicationEngine()                                            # 2.创建QML引擎对象
    content = engine.rootContext()                                              # 3.获取QML引擎的根上下文
    content.setContextProperty("myObject", my_object)                           # 4.注册QML引擎的上下文属性,使QML文件可以访问该对象
    engine.load("template.qml")                                                 # 5.加载QML文件
    sys.exit(app.exec())                                                        # 6.进入程序的主循环并通过exit()函数确保主循环安全结束

  我们新建一个 template.qml 文件,在该文件中,我们创建一个按钮控件。当我们点击按钮的时候,会调用对应上下文属性的槽函数。

import QtQuick.Window
import QtQuick.Controls

// Window控件表示一个顶级窗口
// 在QML中,元素是通过大括号{}内的属性来配置的。
Window {
    width: 800                                                                  // 窗口的宽度
    height: 600                                                                 // 窗口的高度
    visible: true                                                               // 显示窗口
    color: "lightgray"                                                          // 窗口的背景颜色
    
    Button {
        width: 90
        height: 40
        anchors.centerIn: parent

		font.family: "楷体"
        font.pointSize: 32
        text: "按钮"

        onClicked: {
            myObject.my_slot("小樱", 10)                                        // 调用mySlot槽函数
        }
    }
}

2.2、QML响应Python类的信号

  修改 my_object.py 文件的内容,在该文件中,我们自定义信号,然后在槽函数中触发这个信号。

from PySide6.QtCore import QObject
from PySide6.QtCore import Signal, Slot

# 创建一个类继承自QObject
class MyObject(QObject):
    mySignal = Signal(str, int)                                                 # 自定义一个信号
    
    # 定义一个槽函数,在该槽函数触发信号
    @Slot(str, int)
    def my_slot(self, name: str, age: int) -> None:
        self.mySignal.emit(name, age)                                           # 触发信号

  在 QML 文件中,我们使用 Connections 对象 创建一个到 QML 的信号的连接Connections 对象有一个 target 属性,用来 指向发出信号的对象。它的一般用法如下:

Connections {
    target: 发出信号的对象ID名

    // 定义信号处理函数,函数名为 on<SingnalName>,SingnalName首字母大写
    function on信号名(参数名) {
        // 处理信号的逻辑
    }
}

  修改 template.qml 文件的内容。

import QtQuick.Window
import QtQuick.Controls

// Window控件表示一个顶级窗口
// 在QML中,元素是通过大括号{}内的属性来配置的。
Window {
    width: 800                                                                  // 窗口的宽度
    height: 600                                                                 // 窗口的高度
    visible: true                                                               // 显示窗口
    color: "lightgray"                                                          // 窗口的背景颜色
    
    Button {
        id: takePhotoButtonId
        width: 90
        height: 40
        anchors.centerIn: parent
        
        font.family: "楷体"
        font.pointSize: 32
        text: "按钮"

        onClicked: {
            myObject.my_slot("小樱", 10)                                        // 调用mySlot槽函数
        }
    }

    Connections {
        target: myObject                                                        // 连接到myObject对象

        // 当mySignal信号触发时,执行对应的信号处理器
        function onMySignal(name, age) {                                                  
            console.log("你好,我是" + name + ",我今年" + age + "岁")
        }
    }
}

2.3、QML访问Python类的属性

  如果想要在 QML 文件中访问 Python 类的属性,我们可以在 Python 类中 PySide6 提供的 Property 类定义属性。它的定义如下:

Property(self, 
         type: type,                                                            # 属性类型
         fget: Optional[Callable] = None,                                       # 获取属性值的方法
         fset: Optional[Callable] = None,                                       # 设置属性值的方法
         freset: Optional[Callable] = None,                                     # 重置属性值的方法
         fdel: Optional[Callable] = None,                                       # 删除属性值的方法
         doc: str = '',                                                         # 属性的文档字符串
         notify: Optional[PySide6.QtCore.Signal] = None,                        # 属性值改变时触发的信号
         designable: bool = True,                                               # 是否可在GUI设计工具(Qt Designer)中编辑
         scriptable: bool = True,                                               # 是否可在脚本引擎中访问
         stored: bool = True, user: bool = False,                               # 是单独存在还是依赖于其他值
         constant: bool = False,                                                # 是否为常量属性,即不可改变
         final: bool = False                                                    # 是否为最终属性,即不可被子类覆盖
) -> PySide6.QtCore.Property

  修改 my_object.py 文件的内容。我们在在 Python 类中使用 Property 类定义属性。这里,我们在修改属性之后,一定要触发对应的信号,否则 QML 中不会感应到属性值被修改。

from PySide6.QtCore import QObject
from PySide6.QtCore import Signal
from PySide6.QtCore import Property

# 创建一个类继承自QObject
class MyObject(QObject):
    modifyNamePropertySignal = Signal()                                         # 自定义一个信号
    modifyAgePropertySignal = Signal()                                          # 自定义一个信号

    def __init__(self) -> None:
        super().__init__()
        self.__name = ""
        self.__age = 0

    def get_name(self) -> str:
        return self.__name
    
    def set_name(self, name: str) -> None:
        self.__name = name
        self.modifyNamePropertySignal.emit()                                    # 触发信号
    
    def get_age(self) -> int:
        return self.__age
    
    def set_age(self, age: int) -> None:
        self.__age = age
        self.modifyAgePropertySignal.emit()                                     # 触发信号

    # 定义一个属性
    name = Property(str, get_name, set_name, notify=modifyNamePropertySignal)
    age = Property(int, get_age, set_age, notify=modifyAgePropertySignal)

  修改 template.py 文件的内容。

import sys

from PySide6.QtWidgets import QApplication
from PySide6.QtQml import QQmlApplicationEngine

from my_object import MyObject

if __name__ == "__main__":
    my_object = MyObject()
    my_object.set_name("小樱")
    my_object.set_age(10)

    app = QApplication(sys.argv)                                                # 1.创建一个QApplication类的实例
    engine = QQmlApplicationEngine()                                            # 2.创建QML引擎对象
    content = engine.rootContext()                                              # 3.获取QML引擎的根上下文
    content.setContextProperty("myObject", my_object)                           # 4.注册QML引擎的上下文属性,使QML文件可以访问该对象
    engine.load("template.qml")                                                 # 5.加载QML文件
    sys.exit(app.exec())                                                        # 6.进入程序的主循环并通过exit()函数确保主循环安全结束

  修改 template.qml 文件的内容。

import QtQuick.Window
import QtQuick.Controls

// Window控件表示一个顶级窗口
// 在QML中,元素是通过大括号{}内的属性来配置的。
Window {
    width: 800                                                                  // 窗口的宽度
    height: 600                                                                 // 窗口的高度
    visible: true                                                               // 显示窗口
    color: "lightgray"                                                          // 窗口的背景颜色

    Text {
        id: textId
        anchors.top: parent.top                                                 // 顶部对齐父元素的顶部
        anchors.topMargin: 10                                                   // 顶部边距
        anchors.horizontalCenter: parent.horizontalCenter                       // 水平居中对齐父元素的水平中心

        font.family: "楷体 "                                                    // 字体族
        font.pointSize: 24                                                      // 字体大小

        text: "你好,我是" + myObject.name + ",我今年" + myObject.age + "岁"
    }
    
    Grid {
        anchors.centerIn: parent                                                // 居中对齐父元素的中心
        columns: 2                                                              // 列数
        spacing: 10                                                             // 单元格间距

        Text {
            font: textId.font                                                   // 字体
            text: "姓名:"                                                       // 文本
        }

        // 创建一个文本输入框
        TextField {
            width: 600                                                          // 宽度
            font: textId.font                                                   // 字体
            placeholderText: "请输入姓名"                                         // 占位符文本
            text: myObject.name

            // 编辑完成时触发信号
            onEditingFinished: {
                myObject.name = text
            }
        }

        Text {
            font: textId.font                                                   // 字体
            text: "年龄:"                                                       // 文本
        }

        // 创建一个数字选择控件
        SpinBox {
            id: spinBoxId
            width: 600                                                          // 宽度

            font: textId.font                                                   // 字体
            from: 0                                                             // 最小值
            to: 300                                                             // 最大值
            stepSize: 1                                                         // 步长
            editable: true                                                      // 可编辑
            value: myObject.age                                                 // 数值

            validator: IntValidator {                                           // 整数验证器
                bottom: spinBoxId.from                                          // 最小值 
                top: spinBoxId.to                                               // 最大值
            }

            // 数值改变时触发信号
            onValueModified: {
                myObject.age = value
            }
        }
    }
}

2.4、上下文对象

  在之前的代码中,我们通过 setContextProperty("属性名", 类对象) 方法将 Python 的类对象通过 属性名 的方式暴露给 QML,这样在 QML 端,我们可以通过 属性名.属性属性名.槽函数() 的方式访问 Python 类的成员。如果我们想要在 QML 中直接注入一个属性,那么可以使用 上下文对象

  修改 my_object.py 文件的内容。这里,我们使用 @Property 装饰器简化写法。

from PySide6.QtCore import QObject
from PySide6.QtCore import Signal
from PySide6.QtCore import Property

# 创建一个类继承自QObject
class MyObject(QObject):
    modifyNamePropertySignal = Signal()                                         # 自定义一个信号
    modifyAgePropertySignal = Signal()                                          # 自定义一个信号

    def __init__(self) -> None:
        super().__init__()
        self.__name = ""
        self.__age = 0

    @Property(str, notify=modifyNamePropertySignal)
    def name(self) -> str:
        return self.__name
    
    @name.setter
    def name(self, name: str) -> None:
        self.__name = name
        self.modifyNamePropertySignal.emit()                                    # 触发信号
    
    @Property(int, notify=modifyAgePropertySignal)
    def age(self) -> int:
        return self.__age
    
    @age.setter
    def age(self, age: int) -> None:
        self.__age = age
        self.modifyAgePropertySignal.emit()                                     # 触发信号

  修改 template.py 文件的内容。在该文件中我们使用 setContextObject(类对象) 方法 注册到 QML 引擎的上下文对象中

import sys

from PySide6.QtWidgets import QApplication
from PySide6.QtQml import QQmlApplicationEngine

from my_object import MyObject

if __name__ == "__main__":
    my_object = MyObject()
    my_object.name = "小樱"
    my_object.age = 10

    app = QApplication(sys.argv)                                                # 1.创建一个QApplication类的实例
    engine = QQmlApplicationEngine()                                            # 2.创建QML引擎对象
    content = engine.rootContext()                                              # 3.获取QML引擎的根上下文
    content.setContextObject(my_object)                                         # 4.注册到QML引擎的上下文对象中
    engine.load("template.qml")                                                 # 5.加载QML文件
    sys.exit(app.exec())                                                        # 6.进入程序的主循环并通过exit()函数确保主循环安全结束

  修改 template.qml 文件的内容,在 QML 文件中,我们可以直接访问注册到 QML 引擎的上下文对象中类对象的属性和槽函数。

import QtQuick.Window
import QtQuick.Controls

// Window控件表示一个顶级窗口
// 在QML中,元素是通过大括号{}内的属性来配置的。
Window {
    width: 800                                                                  // 窗口的宽度
    height: 600                                                                 // 窗口的高度
    visible: true                                                               // 显示窗口
    color: "lightgray"                                                          // 窗口的背景颜色

    Text {
        id: textId
        anchors.top: parent.top                                                 // 顶部对齐父元素的顶部
        anchors.topMargin: 10                                                   // 顶部边距
        anchors.horizontalCenter: parent.horizontalCenter                       // 水平居中对齐父元素的水平中心

        font.family: "楷体 "                                                    // 字体族
        font.pointSize: 24                                                      // 字体大小

        text: "你好,我是" + name + ",我今年" + age + "岁"
    }
    
    Grid {
        anchors.centerIn: parent                                                // 居中对齐父元素的中心
        columns: 2                                                              // 列数
        spacing: 10                                                             // 单元格间距

        Text {
            font: textId.font                                                   // 字体
            text: "姓名:"                                                       // 文本
        }

        // 创建一个文本输入框
        TextField {
            width: 600                                                          // 宽度
            font: textId.font                                                   // 字体
            placeholderText: "请输入姓名"                                         // 占位符文本
            text: name

            // 编辑完成时触发信号
            onEditingFinished: {
                name = text
            }
        }

        Text {
            font: textId.font                                                   // 字体
            text: "年龄:"                                                       // 文本
        }

        // 创建一个数字选择控件
        SpinBox {
            id: spinBoxId
            width: 600                                                          // 宽度

            font: textId.font                                                   // 字体
            from: 0                                                             // 最小值
            to: 300                                                             // 最大值
            stepSize: 1                                                         // 步长
            editable: true                                                      // 可编辑
            value: age                                                          // 数值

            validator: IntValidator {                                           // 整数验证器
                bottom: spinBoxId.from                                          // 最小值 
                top: spinBoxId.to                                               // 最大值
            }

            // 数值改变时触发信号
            onValueModified: {
                age = value
            }
        }
    }
}

使用 QQmlContext.setContextProperty() 显式设置的属性会优先于上下文对象的属性。

三、Python中访问QML

  Qt 最核心的一个基础特性,就是元对象系统,通过元对象系统,你可以查询 QObject 的某个派生类的类名、有哪些信号、槽、属性、可调用方法等信息。

3.1、Python访问QML中的控件

  修改 template.qml 文件的内容。这里我们在按钮的 onClicked() 处理器中调用 Python 类的一个槽方法,在这个槽方法中,我们访问 QML 中的控件。

import QtQuick.Window
import QtQuick.Controls

// Window控件表示一个顶级窗口
// 在QML中,元素是通过大括号{}内的属性来配置的。
Window {
    width: 800                                                                  // 窗口的宽度
    height: 600                                                                 // 窗口的高度
    visible: true                                                               // 显示窗口
    color: "lightgray"                                                          // 窗口的背景颜色

    Text {
        id: textId
        objectName: "textObjectName"
        anchors.top: parent.top
        anchors.topMargin: 10
        anchors.horizontalCenter: parent.horizontalCenter

        font.family: "楷体"
        font.pointSize: 22
        text: "你好,我是小樱。"
    }

    Button {
        width: 120
        height: 60
        anchors.centerIn: parent

        font.family: "楷体"
        font.pointSize: 32
        text: "按钮"

        onClicked: {
            myObject.my_slot(textId.objectName)                                 // 调用mySlot槽函数
        }
    }
}

  修改 template.py 文件的内容。在 QML 引擎加载完 QML 文件之后,我们可以使用 engine.rootObjects() 方法 获取 QML 中根对象列表,列表中的第一个元素就是我们需要的 QML 根对象。

import sys

from PySide6.QtWidgets import QApplication
from PySide6.QtQml import QQmlApplicationEngine

from my_object import MyObject

if __name__ == "__main__":
    my_object = MyObject()

    app = QApplication(sys.argv)                                                # 1.创建一个QApplication类的实例
    engine = QQmlApplicationEngine()                                            # 2.创建QML引擎对象

    content = engine.rootContext()                                              # 3.获取QML引擎的根上下文
    content.setContextProperty("myObject", my_object)                           # 4.注册QML引擎的上下文属性,使QML文件可以访问该对象

    engine.load("template.qml")                                                 # 5.加载QML文件
    
    root_objects = engine.rootObjects()                                         # 6.获取QML文件中根对象列表
    if not root_objects:
        sys.exit(-1)

    root_object = root_objects[0]                                               # 7.获取QML文件中根对象列表的第一个元素
    my_object.set_qml_root_object(root_object)

    sys.exit(app.exec())                                                        # 8.进入程序的主循环并通过exit()函数确保主循环安全结束

  修改 my_object.py 文件的内容。在 Python 代码中获取 QML 的根对象之后,我们可以使用 findChild("objectName属性名") 方法 查找特定的子控件。找到子控件之后,我们可以使用 property("属性名") 方法 访问属性,使用 setProperty("属性名", 属性值) 的方法 设置属性

import random

from PySide6.QtCore import QObject
from PySide6.QtCore import Slot

class MyObject(QObject):
    def __init__(self) -> None:
        super().__init__()
        self.qml_root_object = None
        self.messages = [
            "没问题!无论发生什么事,我都会用笑容去面对的!",
            "虽然我现在的力量还不够强,可是我会努力变强的。",
            "我不会放弃的,不管多少次失败,我都一定会成功给你看!",
            "不论前方有多少难关,我都会一一克服,然后到达你的身边!"
        ]

    def set_qml_root_object(self, root: QObject) -> None:
        self.qml_root_object = root

    # 定义一个槽函数
    @Slot(str)
    def my_slot(self, object_name: str) -> None:
        if self.qml_root_object:
            obj = self.qml_root_object.findChild(QObject, object_name)
            if obj:
                message = random.choice(self.messages)
                obj.setProperty("text", message)
                print(obj.property("text"))

3.2、Python调用QML的函数

  在 Python 代码中获取 QML 的根对象之后,我们可以使用 QMetaObject.invokeMethod() 调用 QML 中的函数。该函数的说明如下:

返回值 = QMetaObject.invokeMethod( 
            QML根对象,
            要调用的QML函数名,
            调用方式,
            Q_RETURN_ARG("返回值类型"),
            Q_ARG("参数类型", 参数值),
            Q_ARG("参数类型", 参数值),
            Q_ARG("参数类型", 参数值),
        )

参数数量限制为 10。

  修改 template.qml 文件,在该文件中定义一个函数。

import QtQuick.Window
import QtQuick.Controls

// Window控件表示一个顶级窗口
// 在QML中,元素是通过大括号{}内的属性来配置的。
Window {
    width: 800                                                                  // 窗口的宽度
    height: 600                                                                 // 窗口的高度
    visible: true                                                               // 显示窗口
    color: "lightgray"                                                          // 窗口的背景颜色

    Button {
        width: 120
        height: 60
        anchors.centerIn: parent

        font.family: "楷体"
        font.pointSize: 32
        text: "按钮"

        onClicked: {
            myObject.my_slot("getMessage", "你好,我是小樱。")                    // 调用mySlot槽函数
        }
    }

    function getMessage(message) {
        console.log(message)
        return message
    }
}

  修改 my_object.py 文件,在获取 QML 根对象之后,使用 QMetaObject.invokeMethod() 调用 QML 中的函数。

from PySide6.QtCore import QObject, Qt
from PySide6.QtCore import Slot
from PySide6.QtCore import QMetaObject
from PySide6.QtCore import Q_ARG, Q_RETURN_ARG

class MyObject(QObject):
    def __init__(self) -> None:
        super().__init__()
        self.qml_root_object = None

    def set_qml_root_object(self, root) -> None:
        self.qml_root_object = root

    # 定义一个槽函数
    @Slot(str, str)
    def my_slot(self, func_name: str, func_arg1: str) -> None:
        if self.qml_root_object:
            # 使用invokeMethod调用QML函数
            result = QMetaObject.invokeMethod( 
                self.qml_root_object,                                           # QML根对象
                func_name,                                                      # 要调用的QML函数名
                Qt.ConnectionType.DirectConnection,                             # 调用方式(直接在当前线程执行)
                Q_RETURN_ARG("QVariant"),                                       # 声明返回值类型
                Q_ARG("QVariant", func_arg1),                                   # 第一个参数
            )
            if result:
                print(result)

四、Python类注册为QML类型

  我们可以在 Python 文件中自定义一个类,接着使用 @QmlElement 装饰器或 qmlRegisterType() 函数注册 Python 类为 QML 类型,然后再 QML 文件中使用该类型。

  修改 my_object.py 文件的内容。我们可以使用 @QmlElement 装饰器装饰自定义的类。在使用 @QmlElement 装饰器前,我们还需要类外定义 QML_IMPORT_NAMEQML_IMPORT_MAJOR_VERSION 两个常量指明在 QML 文件中导入的 模块标识主版本号

from PySide6.QtCore import QObject
from PySide6.QtCore import Signal
from PySide6.QtCore import Property
from PySide6.QtQml import QmlElement

# 定义QML模块和主版本号
QML_IMPORT_NAME = "star.light.components"
QML_IMPORT_MAJOR_VERSION = 1

# 创建一个类继承自QObject
@QmlElement
class MyObject(QObject):
    modifyNamePropertySignal = Signal()                                         # 自定义一个信号
    modifyAgePropertySignal = Signal()                                          # 自定义一个信号

    def __init__(self) -> None:
        super().__init__()
        self.__name = ""
        self.__age = 0

    def get_name(self) -> str:
        return self.__name
    
    def set_name(self, name: str) -> None:
        self.__name = name
        self.modifyNamePropertySignal.emit()                                    # 触发信号
    
    def get_age(self) -> int:
        return self.__age
    
    def set_age(self, age: int) -> None:
        self.__age = age
        self.modifyAgePropertySignal.emit()                                     # 触发信号

    # 定义一个属性
    name = Property(str, get_name, set_name, notify=modifyNamePropertySignal)
    age = Property(int, get_age, set_age, notify=modifyAgePropertySignal)

  修改 templaet.py 文件的内容。这里,我们需要将被 @QmlElement 装饰器装饰的 Python 类所在的模块(Python 文件)在 创建和加载 QML 引擎之前 被导入。

import sys

from PySide6.QtWidgets import QApplication
from PySide6.QtQml import QQmlApplicationEngine

# 导入自定义的Python类
import my_object

if __name__ == "__main__":
    app = QApplication(sys.argv)                                                # 1.创建一个QApplication类的实例
    engine = QQmlApplicationEngine()                                            # 2.创建QML引擎对象
    engine.load("template.qml")                                                 # 3.加载QML文件
    sys.exit(app.exec())                                                        # 4.进入程序的主循环并通过exit()函数确保主循环安全结束

  修改 templae.qml 文件的内容。在这个文件中,我们使用用 Python 类注册的 QML 类型。

import QtQuick.Window
import QtQuick.Controls

// 导入自定义的QML模块
import star.light.components

// Window控件表示一个顶级窗口
// 在QML中,元素是通过大括号{}内的属性来配置的。
Window {
    width: 800                                                                  // 窗口的宽度
    height: 600                                                                 // 窗口的高度
    visible: true                                                               // 显示窗口
    color: "lightgray"                                                          // 窗口的背景颜色

    MyObject {
        id: myObjectId

        name: "小樱"
        age: 10
    }

    Text {
        anchors.centerIn: parent

        font.family: "楷体"
        font.pointSize: 32
        text: "姓名:" + myObjectId.name + ",年龄:" + myObjectId.age
    }
}

  我们还可以使用 qmlRegisterType() 函数注册 Python 类为 QML 类型,它的定义如下:

qmlRegisterType(Python类, "模块名", 主版本, 次版本, "QML中的类型名")

  我们修改 my_object.py 文件的内容。

from PySide6.QtCore import QObject
from PySide6.QtCore import Signal
from PySide6.QtCore import Property

class MyObject(QObject):
    modifyNamePropertySignal = Signal()                                         # 自定义一个信号
    modifyAgePropertySignal = Signal()                                          # 自定义一个信号

    def __init__(self) -> None:
        super().__init__()
        self.__name = ""
        self.__age = 0

    def get_name(self) -> str:
        return self.__name
    
    def set_name(self, name: str) -> None:
        self.__name = name
        self.modifyNamePropertySignal.emit()                                    # 触发信号
    
    def get_age(self) -> int:
        return self.__age
    
    def set_age(self, age: int) -> None:
        self.__age = age
        self.modifyAgePropertySignal.emit()                                     # 触发信号

    # 定义一个属性
    name = Property(str, get_name, set_name, notify=modifyNamePropertySignal)
    age = Property(int, get_age, set_age, notify=modifyAgePropertySignal)

  我们修改 template.py 文件的内容。在该文件中,我们使用 qmlRegisterType() 函数注册 Python 类为 QML 类型。

import sys

from PySide6.QtWidgets import QApplication
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtQml import qmlRegisterType

from my_object import MyObject

if __name__ == "__main__":
    # 使用 qmlRegisterType 手动注册
    qmlRegisterType(MyObject, "star.light.components", 1, 0, "MyObject")

    app = QApplication(sys.argv)                                                # 1.创建一个QApplication类的实例
    engine = QQmlApplicationEngine()                                            # 2.创建QML引擎对象
    engine.load("template.qml")                                                 # 3.加载QML文件
    sys.exit(app.exec())                                                        # 4.进入程序的主循环并通过exit()函数确保主循环安全结束

五、单例对象

  单例是一种在整个应用程序中只存在一个对象的类型。在 PySide6 中,我们可以使用 @QmlSingleton 装饰器来实现单例模式。我们修改 my_object.py 文件的内容。

from PySide6.QtCore import QObject
from PySide6.QtCore import Slot
from PySide6.QtQml import QmlElement, QmlSingleton

QML_IMPORT_NAME = "star.light.components"
QML_IMPORT_MAJOR_VERSION = 1

# 创建一个类继承自QObject
# @QmlSingleton装饰器必须放在@QmlElement装饰器之后,否则单例不起作用
@QmlElement
@QmlSingleton
class MyObject(QObject):
    # 定义一个槽函数
    @Slot(str, int)
    def my_slot(self, name: str, age: int) -> None:
        print(f"你好,我是{name},我今年{age}岁")

  我们修改 template.py 文件的内容。

import sys

from PySide6.QtWidgets import QApplication
from PySide6.QtQml import QQmlApplicationEngine

# 导入自定义的Python类
import my_object

if __name__ == "__main__":
    app = QApplication(sys.argv)                                                # 1.创建一个QApplication类的实例
    engine = QQmlApplicationEngine()                                            # 2.创建QML引擎对象
    engine.load("template.qml")                                                 # 3.加载QML文件
    sys.exit(app.exec())                                                        # 4.进入程序的主循环并通过exit()函数确保主循环安全结束

  修改 template.qml 文件的内容。

import QtQuick.Window
import QtQuick.Controls

// 导入自定义的QML模块
import star.light.components

// Window控件表示一个顶级窗口
// 在QML中,元素是通过大括号{}内的属性来配置的。
Window {
    width: 800                                                                  // 窗口的宽度
    height: 600                                                                 // 窗口的高度
    visible: true                                                               // 显示窗口
    color: "lightgray"                                                          // 窗口的背景颜色


    Button {
        width: 120
        height: 60
        anchors.centerIn: parent

        font.family: "楷体"
        font.pointSize: 32
        text: "按钮"

        onClicked: {
            MyObject.my_slot("小樱", 10)                                        // 调用mySlot槽函数
        }
    }
}

使用 PySide6 的单例模式后,我们可以不创建对象,直接使用类型名的方式调用它的成员。如果我们创建了实例,会报 Element is not creatable.

  我们还可以使用 qmlRegisterSingletonType() 函数 或 qmlRegisterSingletonInstance()函数 使用实现的单例模式。

  修改 my_object.py 文件的内容。

from PySide6.QtCore import QObject
from PySide6.QtCore import Slot

class MyObject(QObject):
    # 定义一个槽函数
    @Slot(str, int)
    def my_slot(self, name: str, age: int) -> None:
        print(f"你好,我是{name},我今年{age}岁")

  修改 template.py 文件的内容,使用 qmlRegisterSingletonType() 函数实现单例模式。

import sys

from PySide6.QtWidgets import QApplication
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtQml import qmlRegisterSingletonType

from my_object import MyObject
import my_object

if __name__ == "__main__":
    # 注册MyObject类为单例实例
    qmlRegisterSingletonType(MyObject, "star.light.components", 1, 0, "MyObject")

    app = QApplication(sys.argv)                                                # 1.创建一个QApplication类的实例
    engine = QQmlApplicationEngine()                                            # 2.创建QML引擎对象
    engine.load("template.qml")                                                 # 3.加载QML文件
    sys.exit(app.exec())                                                        # 4.进入程序的主循环并通过exit()函数确保主循环安全结束

  修改 template.py 文件的内容,使用 qmlRegisterSingletonInstance() 函数实现单例模式。

import sys

from PySide6.QtWidgets import QApplication
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtQml import qmlRegisterSingletonInstance

from my_object import MyObject
import my_object

if __name__ == "__main__":
    # 注册MyObject类为单例实例
    my_object = MyObject()
    qmlRegisterSingletonInstance(MyObject, "star.light.components", 1, 0, "MyObject", my_object)

    app = QApplication(sys.argv)                                                # 1.创建一个QApplication类的实例
    engine = QQmlApplicationEngine()                                            # 2.创建QML引擎对象
    engine.load("template.qml")                                                 # 3.加载QML文件
    sys.exit(app.exec())                                                        # 4.进入程序的主循环并通过exit()函数确保主循环安全结束
posted @ 2025-10-15 22:11  星光映梦  阅读(34)  评论(0)    收藏  举报