PyQt (PySide) 与 QML 的信号连接及注意事项

QML 调用 Python 函数

// === view.qml ===
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Window 2.12

Window {
    width: 600; height: 400
    visible: true

    Button {
        text: "hello"
        onClicked: speaker.say(text)
    }
}

# === main.py ===
import sys

from PySide2.QtCore import *
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtWidgets import QApplication


class Speaker(QObject):
    
    # noinspection PyCallingNonCallable
    @Slot(str)
    def say(self, s):
        print(s)


if __name__ == '__main__':
    app = QApplication()
    engine = QQmlApplicationEngine()
    
    x = Speaker()
    engine.rootContext().setContextProperty('speaker', x)
    
    engine.load(QUrl.fromLocalFile('./view.qml'))
    sys.exit(app.exec_())

运行截图:

PyQt (PySide) 与 QML 的信号连接及注意事项_第1张图片

注意事项

主要是 main.py 有很多坑, 这里一一道来:

# === main.py ===
import sys

from PySide2.QtCore import *
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtWidgets import QApplication


class Speaker(QObject):  # 1) 必须继承 QObject.
    
    @Slot(str)  # 2) 这里 Pycharm 会标记黄色警示, 忽略即可.
    def say(self, s):  # 3) 第一个参数 self 不要丢了.
        print(s)


if __name__ == '__main__':
    app = QApplication()
    engine = QQmlApplicationEngine()
    
    x = Speaker()  # 4) 经测试, 必须先实例化 Speaker 赋给一个变量, 然后把变量传
    # 进去.
    engine.rootContext().setContextProperty('speaker', x)
    # 5) 如果我们没有赋给变量传进去, 而是直接实例化给第二个参数: 
    #        engine.rootContext().setContextProperty('speaker', Speaker())
    #    在 QML 中就会报错. 确实让人无法理解.
    
    # 6) 当把 Speaker 传入上下文后, 我们才可以 load 布局文件.
    engine.load(QUrl.fromLocalFile('./view.qml'))
    
    sys.exit(app.exec_())

Python 连接 QML 信号 (QML 发信号, Python 为槽)

个人比较喜欢的做法, 唯一的难点是 Python 要如何找到目标 QML 对象 (特别是找某个 parent 的 child items 比较头疼).

下面会介绍两种情况, 一种是根布局下的信号, 一种是其他 qml 文件的信号.

根布局的信号连接

// === view.qml ===
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Window 2.12

Window {
    visible: true
    width: 600; height: 400

    signal say(string s)

    Button {
        text: "hello"
        onClicked: say(text)
    }
}

# === main.py ===
import sys

from PySide2.QtCore import *
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtWidgets import QApplication


def say(s):
    print(s)


if __name__ == '__main__':
    app = QApplication()
    engine = QQmlApplicationEngine()
    engine.load(QUrl.fromLocalFile('./view.qml'))
    
    # 获取 root 对象.
    root = engine.rootObjects()[0]  # type: QObject
    # 找到目标对象. 由于我们的目标对象是 Window, 也就是 root 对象. 所以直接用.
    target_view = root
    # 绑定信号.
    target_view.say.connect(say)  # 前一个 say 是 qml 的信号, 后一个是 Python 的
    # say() 方法.
    
    sys.exit(app.exec_())

其他 qml 文件的信号连接

// === view.qml ===
import QtQuick 2.14
import QtQuick.Window 2.12

Window {
    visible: true
    width: 600; height: 400

    MyButton {}
}

// === MyButton.qml ===
import QtQuick 2.14
import QtQuick.Controls 2.14

Item {
    objectName: "my_item"
    signal say(string s)
    
    Button {
        text: "hello"
        onClicked: say(text)
    }
}

# === main.py ===
import sys

from PySide2.QtCore import *
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtWidgets import QApplication


def say(s):
    print(s)


if __name__ == '__main__':
    app = QApplication()
    engine = QQmlApplicationEngine()
    engine.load(QUrl.fromLocalFile('./view.qml'))
    
    # 获取 root 对象.
    root = engine.rootObjects()[0]  # type: QObject
    # 找到目标对象.
    target_view = root.findChild(QObject, 'my_item')
    # 绑定信号.
    target_view.say.connect(say)  # 前一个 say 是 qml 的信号, 后一个是 Python 的
    # say() 方法.
    
    sys.exit(app.exec_())

QML 连接 Python 信号 (Python 发信号, QML 为槽)

这个需求比较少见, 一般不使用这种方案.

请阅读这篇文章实现: TODO


参考:

  • https://www.cnblogs.com/ibgo/p/11589613.html
  • https://stackoverflow.com/questions/57619227/connect-qml-signal-to-pyside2-slot

你可能感兴趣的:(Python,GUI,界面开发)