python - 连接 QML 和 Python,其中通过单击按钮将文本发送到 python 函数,结果以 QML 打印
问题描述
我对 QML 非常陌生,并且对如何在 python 和 QML 中正确连接多个元素有些困惑(我使用的是 python 2.7!)。我有 python 脚本来打印天气数据,还有一个 QML 应用程序应该将输入作为“城市名称”。
理论上,用户在文本字段中键入一个城市,点击按钮,python 接受输入,找到天气数据,并将其打印到 QML 窗口。我正在为与 textfield+button+pythonfunction 的连接如何工作而苦苦挣扎!没有 QML 的 python 函数可以工作,QML 会生成一个带有文本输入和按钮的窗口。
这是我的代码:
QML(天气5.qml)
import QtQuick 2.0
import QtQuick.Controls 2.1
import QtQuick.Window 2.2
ApplicationWindow {
title: qsTr("Test")
width: 300
height: 450
visible: true
Column {
spacing: 20
TextField {
placeholderText: qsTr("City")
echoMode: TextInput.City
id: city
selectByMouse: true
}
ListView{
model: cityy
id: hi
delegate: Text { text: city.display }
}
Button {
signal messageRequired
objectName: "myButton"
text: "Search"
onClicked: {
print(hi)
}
}
}
Connections {
target:
}
}
这是蟒蛇!(pyweather.py)
import requests, json, os
from PyQt5.QtQml import QQmlApplicationEngine, QQmlEngine, QQmlComponent, qmlRegisterType
from PyQt5.QtCore import QUrl, QObject, QCoreApplication, pyqtProperty, QStringListModel, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QApplication
from PyQt5.QtGui import QGuiApplication
import sys
class City(QObject):
def __init__(self):
QObject.__init__(self)
enterCity = pyqtSignal(str, arguments=["weat"])
@pyqtSlot(str)
def weat(self, city_name):
api_key = "key" #I've excluded my key for this post
base_url = "http://api.openweathermap.org/data/2.5/weather?"
complete_url = "http://api.openweathermap.org/data/2.5/weather?q=" + city_name + api_key
response = requests.get(complete_url)
x = response.json()
if x["cod"] != "404":
res = requests.get(complete_url)
data = res.json()
temp = data['main']['temp']
description = data['weather'][0]['description']
print('Temperature : {} degree Kelvin'.format(temp))
rett = ['Temperature : ' + str(temp) + " degree Kelvin"]
return rett
self.enterCity.emit(rett)
else:
print(" City Not Found ")
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
city = City()
engine.rootContext().setContextProperty("cityy", city)
engine.load(QUrl.fromLocalFile('weather5.qml'))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
解决方案
逻辑是通过信号或属性返回信息,在这种情况下,我将展示如何通过属性返回信息。
因为它必须更新到 QML 的某些元素,所以它必须通知它,然后它必须与信号相关联。另一方面,您不应该使用请求,因为它会阻塞事件循环(并冻结 GUI)。
考虑到上述情况,解决方案是:
主文件
from functools import cached_property
import json
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl, QUrlQuery
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply, QNetworkRequest
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQml import QQmlApplicationEngine
import logging
logging.basicConfig(level=logging.DEBUG)
class WeatherWrapper(QObject):
BASE_URL = "http://api.openweathermap.org/data/2.5/weather"
dataChanged = pyqtSignal()
def __init__(self, api_key; str ="", parent: QObject = None) -> None:
super().__init__(parent)
self._data = dict()
self._has_error = False
self._api_key = api_key
@cached_property
def manager(self) -> QNetworkAccessManager:
return QNetworkAccessManager(self)
@property
def api_key(self):
return self._api_key
@api_key.setter
def api_key(self, key):
self._api_key = key
@pyqtProperty("QVariantMap", notify=dataChanged)
def data(self) -> dict:
return self._data
@pyqtSlot(result=bool)
def hasError(self):
return self._has_error
@pyqtSlot(str)
def update_by_city(self, city: str) -> None:
url = QUrl(WeatherWrapper.BASE_URL)
query = QUrlQuery()
query.addQueryItem("q", city)
query.addQueryItem("appid", self.api_key)
url.setQuery(query)
request = QNetworkRequest(url)
reply: QNetworkReply = self.manager.get(request)
reply.finished.connect(self._handle_reply)
def _handle_reply(self) -> None:
has_error = False
reply: QNetworkReply = self.sender()
if reply.error() == QNetworkReply.NoError:
data = reply.readAll().data()
logging.debug(f"data: {data}")
d = json.loads(data)
code = d["cod"]
if code != 404:
del d["cod"]
self._data = d
else:
self._data = dict()
has_error = True
logging.debug(f"error: {code}")
else:
self._data = dict()
has_error = True
logging.debug(f"error: {reply.errorString()}")
self._has_error = has_error
self.dataChanged.emit()
reply.deleteLater()
def main():
import os
import sys
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
app = QGuiApplication(sys.argv)
API_KEY = "API_HERE"
weather = WeatherWrapper()
weather.api_key = API_KEY
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty("weather", weather)
filename = os.path.join(CURRENT_DIR, "main.qml")
engine.load(QUrl.fromLocalFile(filename))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
if __name__ == "__main__":
main()
main.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
ApplicationWindow {
title: qsTr("Weather App")
width: 300
height: 450
visible: true
ColumnLayout {
anchors.fill: parent
spacing: 20
TextField {
id: city_tf
placeholderText: qsTr("City")
Layout.alignment: Qt.AlignHCenter
font.pointSize:14
selectByMouse: true
}
Button {
text: "Search"
Layout.alignment: Qt.AlignHCenter
onClicked: {
weather.update_by_city(city_tf.text)
}
}
Label{
Layout.alignment: Qt.AlignHCenter
id: result_lbl
}
Item {
Layout.fillHeight: true
}
}
Connections {
target: weather
function onDataChanged(){
if(!weather.hasError()){
var temperature = weather.data['main']['temp']
result_lbl.text = "Temperature : " + temperature + " degree Kelvin"
}
}
}
}
Python2 语法:
注意:安装cached_property ( python2.7 -m pip install cached_property
)
from cached_property import cached_property
import json
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl, QUrlQuery
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply, QNetworkRequest
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQml import QQmlApplicationEngine
import logging
logging.basicConfig(level=logging.DEBUG)
class WeatherWrapper(QObject):
BASE_URL = "http://api.openweathermap.org/data/2.5/weather"
dataChanged = pyqtSignal()
def __init__(self, api_key="", parent=None):
super(WeatherWrapper, self).__init__(parent)
self._data = {}
self._has_error = False
self._api_key = api_key
@cached_property
def manager(self):
return QNetworkAccessManager(self)
@property
def api_key(self):
return self._api_key
@api_key.setter
def api_key(self, key):
self._api_key = key
@pyqtProperty("QVariantMap", notify=dataChanged)
def data(self):
return self._data
@pyqtSlot(result=bool)
def hasError(self):
print(self._has_error)
return self._has_error
@pyqtSlot(str)
def update_by_city(self, city):
url = QUrl(WeatherWrapper.BASE_URL)
query = QUrlQuery()
query.addQueryItem("q", city)
query.addQueryItem("appid", self.api_key)
url.setQuery(query)
request = QNetworkRequest(url)
reply = self.manager.get(request)
reply.finished.connect(self._handle_reply)
def _handle_reply(self):
has_error = False
reply = self.sender()
if reply.error() == QNetworkReply.NoError:
data = reply.readAll().data()
logging.debug("data: {}".format(data))
d = json.loads(data)
code = d["cod"]
if code != 404:
del d["cod"]
self._data = d
else:
self._data = {}
has_error = True
logging.debug("error: {}".format(code))
else:
self._data = {}
has_error = True
logging.debug("error: {}".format(reply.errorString()))
self._has_error = has_error
self.dataChanged.emit()
reply.deleteLater()
def main():
import os
import sys
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
app = QGuiApplication(sys.argv)
API_KEY = "API_HERE"
weather = WeatherWrapper()
weather.api_key = API_KEY
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty("weather", weather)
filename = os.path.join(CURRENT_DIR, "main.qml")
engine.load(QUrl.fromLocalFile(filename))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
if __name__ == "__main__":
main()
推荐阅读
- c# - 使用 Where 和 SelectMany
- html - 在行跨单元格中居中垂直方向的按钮
- xpath - 如何让 Camel 使用 Saxon 作为 xpath 选择谓词?
- java - Docker 中的 Java GUI Maven 项目出现 X11 错误
- sql - Oracle:如果对行进行分组,如何更新过程中表中的每一行?
- java - 在 GAE 上运行时 Jersey java.lang.NoSuchMethodError 异常
- rest - 如何使用不同的策略在 Cloudfront 上缓存 API
- api - 来自网络的 Docker API 访问
- sympy - Sympy:在求解器返回的点处评估多元函数
- python - 当我的 Pandas 数据框有很多列时,如何显示 info()?