對於PyQt5+QML+Python3混合編程,如何實現PyQt5與QML響應彼此發送的信號,這是一個棘手的問題。
大抵有如下五種方式:
(要運行下面五個例子,千萬不能在eric6中運行,會報錯。錯誤信息是:qml-test.py文件的第一個字符是無效的標識符)
(1)QML顯式的調用Python函數,無返回值
#文件名:qml-test.py
|
#文件名:test.qml
|
#!/usr/bin/env python ''' (1)QML顯式的調用Python函數 定義一個類,並繼承QtCore.QObject對象,並使用@修飾符修飾pyqtSlot 創建rootContext對象,並使用setContextProperty(string, object)注冊對象,
|
import QtQuick 2.0 Rectangle { width: 320; height: 240 color: "lightblue" Text { id: txt text: "Clicked me" font.pixelSize: 20 anchors.centerIn: parent } MouseArea { id: mouse_area anchors.fill: parent // 有效區域 onClicked: { con.outputString("Hello, Python3") //QML顯式的調用Python函數 } } }
|
(2)QML顯式的調用Python函數,有返回值
#文件名:qml-test2.py |
#文件名:test2.qml |
#!/usr/bin/env python ''' (2)QML顯式的調用Python函數,並有返回 這個例子跟上一個相類似,只是這次調用Python的函數具有返回值功能。 運行程序后,點擊鼠標,左上角會顯示數字30。 ''' from PyQt5.QtCore import QUrl, QObject, pyqtSlot from PyQt5.QtGui import QGuiApplication from PyQt5.QtQuick import QQuickView class MyClass(QObject): @pyqtSlot(int, result=str) # 聲明為槽,輸入參數為int類型,返回值為str類型 def returnValue(self, value): return str(value+10) if __name__ == '__main__': path = 'test2.qml' # 加載的QML文件 app = QGuiApplication([]) view = QQuickView() con = MyClass() context = view.rootContext() context.setContextProperty("con", con) view.engine().quit.connect(app.quit) view.setSource(QUrl(path)) view.show() app.exec_()
|
import QtQuick 2.0 Rectangle { id: root width: 320; height: 240 color: "lightgray" Text { id: txt text: "Clicked me" font.pixelSize: 20 anchors.centerIn: parent } Text { id: txt1 text: "..." font.pixelSize: 20 } MouseArea { id: mouse_area anchors.fill: parent // 有效區域 onClicked: { console.log("test...") // 控制台打印信息 txt1.text = con.returnValue(20) //QML顯式的調用Python函數 } } }
|
(3)QML連接信號到Python
#文件名:qml-test3.py |
#文件名:test3.qml |
#!/usr/bin/env python ''' (3)QML連接信號到Python 當QML觸發事件的時候,發射一個信號給Python,此時Python調用一個函數。 先在QML中定義一個信號, 然后在捕獲事件的時候,發射信號, 最后Python中創建一個rootObject對象,然后連接這個對象, 這個例子中,當點擊鼠標的時候,控制台會打印信息。 ''' from PyQt5.QtCore import QUrl from PyQt5.QtGui import QGuiApplication from PyQt5.QtQuick import QQuickView def outputString(string): print(string) if __name__ == '__main__': path = 'test3.qml' # 加載的QML文件 app = QGuiApplication([]) view = QQuickView() view.engine().quit.connect(app.quit) view.setSource(QUrl(path)) view.show() context = view.rootObject() context.sendClicked.connect(outputString) # 連接QML信號sendCLicked app.exec_()
|
import QtQuick 2.0 Rectangle { id: root width: 320; height: 240 color: "lightgray" signal sendClicked(string str) // 定義信號 Text { id: txt text: "Clicked me" font.pixelSize: 20 anchors.centerIn: parent } MouseArea { id: mouse_area anchors.fill: parent //有效區域 onClicked: { root.sendClicked("Hello, Python3")//發射信號到Python } } }
|
(4)Python調用QML函數
#文件名:qml-test4.py |
#文件名:test4.qml |
# -*- coding: utf-8 -*- ''' (4)Python調用QML函數 QML中創建一個函數, Python中創建一個rootObject對象,並連接這個函數, 例子中,每隔1s,指針會旋轉45 deg;。 ''' from PyQt5.QtCore import QUrl, QTimer from PyQt5.QtGui import QGuiApplication from PyQt5.QtQuick import QQuickView if __name__ == '__main__': path = 'test4.qml' # 加載的QML文件 app = QGuiApplication([]) view = QQuickView() view.engine().quit.connect(app.quit) view.setSource(QUrl(path)) view.show() timer = QTimer() timer.start(2000) root = view.rootObject() timer.timeout.connect(root.updateRotater) # 調用QML函數 app.exec_()
|
import QtQuick 2.0 Rectangle { id: page width: 500; height: 200 color: "lightgray" function updateRotater() {// 定義函數 rotater.angle += 5 } Rectangle { id: rotater property real angle : 0 x: 240; y: 95 width: 100; height: 5 color: "black" transform: Rotation { origin.x: 10; origin.y: 5 angle: rotater.angle } } }
|
(5)信號/槽 機制
#文件名:qml-test5.py |
#文件名:test5.qml |
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QRectF, Qt, QUrl from PyQt5.QtGui import QColor, QGuiApplication, QPainter, QPen from PyQt5.QtQml import qmlRegisterType from PyQt5.QtQuick import QQuickPaintedItem, QQuickView class PieChart(QQuickPaintedItem): chartCleared = pyqtSignal() # 定義信號 @pyqtProperty(str) def name(self): return self._name @name.setter def name(self, name): self._name = name @pyqtProperty(QColor) def color(self): return self._color @color.setter def color(self, color): self._color = QColor(color) def __init__(self, parent=None): super(PieChart, self).__init__(parent) self._name = '' self._color = QColor() def paint(self, painter): painter.setPen(QPen(self._color, 2)) painter.setRenderHints(QPainter.Antialiasing, True) rect = QRectF(0, 0, self.width(), self.height()).adjusted(1, 1, -1, -1) painter.drawPie(rect, 90 * 16, 290 * 16) @pyqtSlot() def clearChart(self): self.color = QColor(Qt.transparent) self.update() self.chartCleared.emit() # 發射信號 if __name__ == '__main__': import os import sys app = QGuiApplication(sys.argv) qmlRegisterType(PieChart, "Charts", 1, 0, "PieChart") view = QQuickView() view.setResizeMode(QQuickView.SizeRootObjectToView) view.setSource( QUrl.fromLocalFile( os.path.join(os.path.dirname(__file__),'tes5.qml'))) view.show() sys.exit(app.exec_())
|
import Charts 1.0 import QtQuick 2.0 Item { width: 300; height: 200 PieChart { id: aPieChart anchors.centerIn: parent width: 100; height: 100 color: "red" onChartCleared: console.log("The chart has been cleared") //槽 } MouseArea { anchors.fill: parent onClicked: aPieChart.clearChart() } Text { anchors { bottom: parent.bottom; horizontalCenter: parent.horizontalCenter; bottomMargin: 20 } text: "Click anywhere to clear the chart" } }
|
參考:
【QML與Python通信】
http://my.oschina.net/u/1275030/blog/186341
【Connecting QML signals in PySide】
http://qt-project.org/wiki/Connecting_QML_Signals_in_PySide
【PyQt 5.1.1 Reference Guide -> Support for Signals and Slots】:
http://pyqt.sourceforge.net/Docs/PyQt5/signals_slots.html?highlight=pyqtslot#PyQt5.QtCore.pyqtSlot