【问题标题】:Connecting QML and Python where text is sent by a button click to a python function, and the result prints in QML连接 QML 和 Python,其中通过单击按钮将文本发送到 python 函数,结果在 QML 中打印
【发布时间】:2023-04-10 19:42:02
【问题描述】:

我对 QML 非常陌生,对如何在 python 和 QML 中正确连接多个元素有些困惑(我使用的是 python 2.7!)。我有 python 脚本来打印天气数据,还有一个 QML 应用程序,它应该将输入作为“cityname”。

理论上,用户在文本字段中键入一个城市,点击按钮,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_())

【问题讨论】:

    标签: python pyqt pyqt5 qml


    【解决方案1】:

    逻辑是通过信号或属性返回信息,本例我将展示如何通过属性返回信息。

    因为它必须更新到 QML 的某些元素,所以它必须通知它,然后它必须与信号相关联。另一方面,您不应该使用请求,因为它会阻塞事件循环(并冻结 GUI)。

    综合以上,解决办法是:

    ma​​in.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()
    

    ma​​in.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()
    

    【讨论】:

    • 嗨!非常感谢您的全面回复!!由于技术限制,我实际上正在使用 python 2.7,我不确定它是否相关,但“->”是语法错误。请问您是否知道解决此问题的任何方法?再次感谢!
    • @sun 删除-> Foo。我总是建议您指明您使用的软件版本,因为通常我们会假设每个软件的最新版本正在被使用。
    • 我一定会记住这一点,谢谢!!很抱歉再次询问,但我在 def 参数中的所有冒号上也出现语法错误(即父级:QObject = None),以及所有引用“回复”的地方的错误,我可以'找不到任何文档说明原因!
    • @sun 再次python版本指出,写完代码再修改很烦人。一会儿我会提供类似python2.7的兼容代码,你用的是哪个版本的PySide2?
    • 啊,对不起,我真的很感激!我正在使用 PyQt5,但如果需要,我也可以获取/转换到 PySide2!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-17
    • 2019-07-13
    • 2020-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多