Reputation: 49
I'm very new to QML and somewhat confused on how to properly connect more than one element across python and QML (I'm using python 2.7!). I have python script to print weather data, and a QML application that is supposed to take in the input as "cityname."
The user theoretically types a city into the textfield, hits the button, and python takes the input, finds the weather data, and prints it to the QML window. I'm struggling with how the connections with textfield+button+pythonfunction will work! The python function without QML works and the QML produces a window with a textinput and a button.
Here's my code:
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:
}
}
and here's the python!! (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_())
Upvotes: 1
Views: 1595
Reputation: 244132
The logic is to return the information through a signal or a property, in this case I will show how to return the information through a property.
As it has to update to some element of the QML then it has to notify then it must have associated to a signal. On the other hand, you should not use requests since it can block the eventloop (and freeze the GUI).
Considering the above, the solution is:
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 syntax:
Note: Install 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()
Upvotes: 1