首页 > 解决方案 > 连接 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_())

标签: pythonpyqtpyqt5qml

解决方案


逻辑是通过信号或属性返回信息,在这种情况下,我将展示如何通过属性返回信息。

因为它必须更新到 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()

推荐阅读