Qt Quick 的学习笔记(二)
和Electron 类似,PyQt 与PySide 也支持向QML 的前端暴露对象以实现前后端的通信;但是呢,似乎QML 可以往任意子元素暴露对象变量
本笔记中的所有内容均摘自Feng Misaka - 博客园。重点在于前后端的通信与动态组件的绘制。
通信
前后端通信是所有做用户界面不可避免的问题,说简单也简单,无非就是函数的调用与数据的传输罢了。但是怎么做得高效优雅就是一门学问了。PySide 中也使用了向前端暴露对象变量的方法。
前端调用后端
前端调用后端,需要后端向前端暴露一个槽函数(@Slot()
),槽函数装饰器的签名如下:
class Slot(
*types: type, # 参数类型
name: str | None = ..., # 函数名,默认与Python 函数同名
result: str | None = ... # 结果类型
)
示例代码如下:
# main.py
import sys
from PySide6.QtCore import QObject, Slot
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine
class Api(QObject):
'''
1. 暴露的变量对象必须继承自QObject
'''
def __init__(self, parent=None) -> None:
'''
2. 并且需要通过父类方法进行初始化
'''
super().__init__(parent) #
@Slot(str, result=str) # 装饰器用于告诉QML 该函数的签名
def clickme(self, arg): # 真正的函数定义
print(f"{arg} clicked")
return "123"
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.quit.connect(app.quit)
# 暴露该对象到rootContext
engine.rootContext().setContextProperty('api', Api(app))
# 也可以暴露到某个组件,但是不如直接放在全局简单粗暴
# engine.rootObjects()[0].setProperty('backend', backend)
engine.load('main.qml')
sys.exit(app.exec_())
import QtQuick 2.15
import QtQuick.Controls 2.15
ApplicationWindow {
visible: true
// ...
Button {
id: btn
text: "'click me'"
onClicked:{
var res = api.clickme("123")?1:2
console.log(res)
}
}
}
后端调用前端
正常后端可以通过同步的方法修改。也可以通过以事件的形式进行。这时就需要后台向前端发送信号Signal()
。然后在前端监听信号的变化:
from PySide6.QtCore import QObject, Slot, Signal
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine
class Api(QObject):
'''
1. 暴露的变量对象必须继承自QObject
'''
def __init__(self, parent=None) -> None:
'''
2. 并且需要通过父类方法进行初始化
'''
super().__init__(parent) #
# 不能将信号定义为实例变量
# self.sig = Signal(str, arguments=['arg1'])
# 只能将信号定义为类变量,不然的话会找不到emit 方法
sig = Signal(str, arguments=['arg1'])
@Slot(str, result=str) # 装饰器用于告诉QML 该函数的签名
def clickme(self, arg): # 真正的函数定义
print(f"{arg} clicked")
# 在槽方法中发送信号
self.sig.emit(arg)
return "123"
# ...
在前端则需要Connect
到api
变量,并且使用onSig
监听sig
信号:
import QtQuick 2.2
import QtQuick.Controls 2.2
ApplicationWindow {
// ...
Connections{
target: api
function onSig(arg1) {
btn.text = arg1
}
}
}
Signal
构造参数及意义:
```python
class Signal(
*types: type, # 参数类型
name: str | None = ..., # 函数名,默认与Python 对象同名
arguments=[str] | None = ... # 形参名
)
## 动态元素
QML 中提供了`ListView` 和`GridView` 来展示数据,并且提供了相应的`*Model` 容器来管理数据,以实现实时更新的效果。下面的代码取自[QML中动态与静态模型应用详解](https://zhuanlan.zhihu.com/p/639430500),仅作部分删减修改并添加注释:
```qml{13-17,42,45-53,71-75}
import QtQuick 2.2
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("动态添加和删除元素")
Rectangle{
width: 480
height: 300
color: "white"
// 初始化数据模型
ListModel{
id: theModel
ListElement{number:0}
ListElement{number:1}
}
Rectangle{
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: 20
height: 40
color: "darkGreen"
Text {
anchors.centerIn: parent
text: "add item"
}
MouseArea{
anchors.fill: parent
onClicked: {
theModel.append({"number": parent.count++})
}
}
property int count: 2
}
GridView{
anchors.fill: parent
anchors.margins: 20
anchors.bottomMargin: 80
clip: true
model: theModel // 绑定模型
cellWidth: 45
cellHeight: 45
delegate: numberDelegate // 子元素模板
add: Transition { // 添加元素的动画,最新的写法
NumberAnimation { properties: "x,y"; from: 100; duration: 1000 }
}
remove: Transition {
ParallelAnimation {
NumberAnimation { property: "opacity"; to: 0; duration: 1000 }
}
}
}
// 子元素的模板
Component{
id:numberDelegate
Rectangle{
id: wrapper
width: 40
height: 40
color: "lightGreen"
Text {
anchors.centerIn: parent
font.pixelSize: 10
text: number // 模型的number 属性
}
MouseArea{
anchors.fill: parent
onClicked: { // 子元素的事件监听
// 这里要判断元素是否已经被移除,所以仅凭index 还是不够的
// 默认index 属性
theModel.remove(index)
}
}
//模型元素移除时候的动画
}
}
}
}
关于动画的部分,以后有时间在学习吧。总之学了数据的增删改查一般的小项目就足够用了。而且比web 和qtdesigner 考虑的都要单纯些。