本期知识点:
- 前端(QML)和后端(Python/C++)如何通过信号或property交互
- 异步处理:JS ajax, Python Thread
- SwipeView组件
代码已更新:kevinqqnj/qml_weather: PySide6+QML weather App (github.com)
小程序运行演示视频:Qt QML+PySide天气小程序
前端(QML)和后端(Python/C++)如何通过信号、setProperty交互
从QML发送signal给后端,有四种方式:
- QML调用Python slot函数
QML:
import kevinqqnj.signals.qmltopy1
Console {
id: pyConsole
}
onClicked: {
pyConsole.output(mouse.x)
}
PySide:
QML_IMPORT_NAME = "kevinqqnj.signals.qmltopy1"
@QmlElement
class Console(QtCore.QObject):
@QtCore.Slot(str)
def output(self, s):
print(s)
view = QtDeclarative.QDeclarativeView()
context = view.rootContext()
context.setContextProperty("con", con)
# or: engine.rootObjects()[0].setProperty('backend', backend)
2. QML调用slot函数,并使用返回值
QML:
import kevinqqnj.signals.qmltopy1
Console {
id: pyConsole
}
onClicked: {
helloText.rotation = rotatevalue.val()
}
PySide:
@QtCore.Slot(result=int)
def val(self):
self.r = self.r + 10
return self.r
3. Python通过engine.connect,访问QML signal
QML:
- 可在QML里隐藏Python class行为
- 但Python需要知道QML component的定义
Rectangle {
id: page
signal textRotationChanged(double rot)
// make some other signal handler emit this signal:
onRotationChanged: textRotationChanged(rotation)
PySide:
view:
root = view.rootObject()
root.textRotationChanged.connect(sayThis)
Engine:
engine.rootObjects()[0].connect(sayThis)
4. Python通过ObjectName来直接访问某component的事件
QML: 给某个component定义ObjectName
MouseArea {
id: buttonMouseArea
objectName: "buttonMouseArea"
anchors.fill: parent
}
PySide:
button = root.findChild(QtCore.QObject,"buttonMouseArea")
button.clicked.connect(lambda: sayThis("clicked button (signal directly connected)"))
button.setProperty("width", 100)
以上四种各有利弊,一般使用(3)
异步处理:JS ajax, Python Thread
QML可以直接import JavaScript
脚本,然后使用其函数和变量。
- 使用ajax,异步访问当前城市的天气预报:
- 如果QML要访问本地文件,需要在PySide中声明:
os.environ["QML_XHR_ALLOW_FILE_READ"] = "1"
function requestWeatherData(cntr) {
var xhr = new XMLHttpRequest;
xhr.open("GET", "weather.json");
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
cntr.weatherData = JSON.parse(xhr.responseText)
}
}
xhr.send();
}
QML也可以发信号给后端,让其抓取天气预报数据。后端再通过setProperty()发送回QML来显示
main.qml
发信号:
ApplicationWindow {
signal updateWeather(int cityId)
。。。
onLaunched: (cityId) => {
updateWeather(cityId)
root.isUpdating = true
stackView.push("WeatherPage.qml")
}
Python接收信号并处理:
- 使用全局变量engine root
- 通过connect来连接前端signal:root.updateWeather.connect()
- 通过threading.Thread来多线程执行,不阻塞QML运行
- 结果数据:通过
root.setProperty('weatherData', weatherdata)
,发送给QML - 通过
root.setProperty('isUpdating', False)
,来告知QML,操作已完成
root = engine.rootObjects()[0]
root.updateWeather.connect(update_weather)
def update_weather(cityId):
print('python: updating...', cityId)
task = threading.Thread(target=getData, args=(cityId,))
task.start()
def getData(cityId):
global root, weatherdata
try:
url = f"http://t.weather.sojson.com/api/weather/city/{cityId}"
print('start downloading... ', url)
response = urllib.request.urlopen(url)
data = json.loads(response.read().decode('utf-8'))
weatherdata[str(cityId)] = data['data']['forecast'][0]
weatherdata[str(cityId)]['cityInfo'] = data['cityInfo']
weatherdata[str(cityId)]['UdateTime'] = data['time']
# {'date': '07', 'high': '高温 29℃', 'low': '低温 17℃', 'ymd': '2022-06-07', 'week': '星期二',
# 'sunrise': '04:58', 'sunset': '19:09', 'aqi': 18, 'fx': '东北风', 'fl': '2级', 'type': '多云',
# 'notice': '阴晴之间,谨防紫外线侵扰', 'UdateTime': '2022-06-07 19:12:55'}
# must have some delay, either urlopen or time.sleep, otherwize QML cannot update property
time.sleep(1)
print('downloaded, data len:', len(str(data)), weatherdata)
root.setProperty('weatherData', weatherdata)
except Exception as e:
print(e)
root.setProperty('isUpdating', False)
QML:定义property,来接收Python数据
- weatherData:字典JSON格式数据
- isUpdating:布尔值,控制显示BusyIndicator(后台数据未返回前,显示转圈)
ApplicationWindow {
property var weatherData: ({})
property bool isUpdating: false
。。。
BusyIndicator {
id: busyIndicator
visible: root.isUpdating
}
- 旧数据暂存放在Settings变量里,以供离线访问:
Component.onCompleted: {
settings.savedWeatherData = root.weatherData
}
SwipeView组件
手机上常见的多页面展示方法,在下方显示多个点,以指示当前有多个页面,可左右滑动切换。
下一篇:TBD