我对QML还是很陌生,在如何跨python和QML正确连接多个元素上有些困惑(我正在使用python 2.7!)。我有用于打印天气数据的python脚本,还有一个QML应用程序,该应用程序应将输入作为“城市名称”。
用户理论上将城市键入文本字段,单击按钮,然后python获取输入,找到天气数据,并将其打印到QML窗口。我正在努力与textfield + button + pythonfunction的连接如何工作!没有QML的python函数起作用,并且QML生成带有文本输入和按钮的窗口。
这是我的代码:
QML(weather5.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)。
考虑到上述,解决方法是:
main.py
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()