如何使用 PySide2 连接 Python 和 QML?

2024-01-17

我想在 Ubuntu 上编写一个简单的桌面应用程序,我认为一个简单的方法是使用 Qt 和 QML 作为 GUI,使用 Python 作为逻辑语言,因为我对 Python 有点熟悉。

现在我花了几个小时尝试以某种方式连接 GUI 和逻辑,但它不起作用。 我管理连接 QML --> Python 但不是相反。我有代表我的数据模型的 Python 类,并且添加了 JSON 编码和解码函数。所以目前还没有涉及 SQL 数据库。但也许 QML 视图和某些数据库之间的直接连接会让事情变得更容易?

现在一些代码。

QML --> Python

QML 文件:

ApplicationWindow {

// main window
id: mainWindow
title: qsTr("Test")
width: 640
height: 480

signal tmsPrint(string text)

Page {
    id: mainView

    ColumnLayout {
        id: mainLayout

        Button {
            text: qsTr("Say Hello!")
            onClicked: tmsPrint("Hello!")
        }
    }
}    
}

然后我有我的slots.py:

from PySide2.QtCore import Slot

def connect_slots(win):
    win.tmsPrint.connect(say_hello)

@Slot(str)
def say_hello(text):
    print(text)

最后是我的 main.py:

import sys
from controller.slots import connect_slots

from PySide2.QtWidgets import QApplication
from PySide2.QtQml import QQmlApplicationEngine 

if __name__ == '__main__':
    app = QApplication(sys.argv)

    engine = QQmlApplicationEngine()
    engine.load('view/main.qml')

    win = engine.rootObjects()[0]
    connect_slots(win)

    # show the window
    win.show()
    sys.exit(app.exec_())

这工作正常,我可以打印“Hello!”。但这是最好的方法还是创建一个带有插槽的类并使用更好setContextProperty能够直接调用它们而不添加额外的信号?

Python --> QML

我无法完成这件事。我尝试了不同的方法,但没有一个有效,而且我也不知道哪一种是最好的。例如,我想要做的是显示对象列表并提供操作应用程序中的数据的方法等。

  1. 包括 JavaScript: 我添加了一个附加文件application.js具有仅打印某些内容的功能,但它可能可用于设置文本字段等的上下文。 然后我尝试使用 QMetaObject 和 invokeMethod,但只是出现参数错误等错误。

这种方法有意义吗?实际上我不懂任何javascript,所以如果没有必要,我宁愿不使用它。

  1. 视图模型方法 我创建了一个文件 viewmodel.py

    from PySide2.QtCore import QStringListModel
    
    class ListModel(QStringListModel):
    
    def __init__(self):
         self.textlines = ['hi', 'ho']
         super().__init__()
    

在 main.py 中我添加了:

model = ListModel()
engine.rootContext().setContextProperty('myModel', model)

ListView 看起来像这样:

ListView {
            width: 180; height: 200

            model: myModel
            delegate: Text {
                text: model.textlines
            }
        }

我收到错误“myModel 未定义”,但我猜它无论如何都无法工作,因为委托只接受一个元素而不是列表。 这种方法好吗?如果是的话,我该如何让它发挥作用?

  1. 是否有一种完全不同的方法来操作 QML 视图中的数据?

我感谢您的帮助! 我知道 Qt 文档,但我对它不满意。所以也许我错过了一些东西。但 PyQt 似乎比 PySide2 更受欢迎(至少谷歌搜索似乎表明了这一点),并且 PySide 参考文献经常使用 PySide1 或不使用 QML QtQuick 的做事方式......


你的问题有很多方面,所以我会尽力在我的答案中详细说明,而且这个答案也会不断更新,因为这类问题经常被问到,但它们是针对特定案例的解决方案,所以我将冒昧地给出它通用方法并针对可能的情况进行具体说明。

QML 到 Python:

您的方法之所以有效,是因为 python 中的类型转换是动态的,而在 C++ 中则不会发生。它适用于小任务,但不可维护,逻辑必须与视图分离,因此它不应该是依赖的。具体来说,假设打印的文本会被逻辑拿来进行一些处理,那么如果修改信号的名称,或者数据不依赖于ApplicationWindow但在另一个元素等上,那么您将不得不更改很多连接代码。

正如您所指出的,建议创建一个类,负责映射您需要逻辑的数据并将其嵌入QML,因此如果您更改视图中的某些内容,只需更改连接即可:

Example:

main.py

import sys

from PySide2.QtCore import QObject, Signal, Property, QUrl
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine

class Backend(QObject):
    textChanged = Signal(str)

    def __init__(self, parent=None):
        QObject.__init__(self, parent)
        self.m_text = ""

    @Property(str, notify=textChanged)
    def text(self):
        return self.m_text

    @text.setter
    def setText(self, text):
        if self.m_text == text:
            return
        self.m_text = text
        self.textChanged.emit(self.m_text)   

if __name__ == '__main__':
    app = QGuiApplication(sys.argv)
    backend = Backend()

    backend.textChanged.connect(lambda text: print(text))
    engine = QQmlApplicationEngine()
    engine.rootContext().setContextProperty("backend", backend)
    engine.load(QUrl.fromLocalFile('main.qml'))
    if not engine.rootObjects():
        sys.exit(-1)
    sys.exit(app.exec_())

main.qml

import QtQuick 2.10
import QtQuick.Controls 2.1
import QtQuick.Window 2.2

ApplicationWindow {
    title: qsTr("Test")
    width: 640
    height: 480
    visible: true
    Column{
        TextField{
            id: tf
            text: "Hello"
        }
        Button {
            text: qsTr("Click Me")
            onClicked: backend.text = tf.text
        } 
    }
}

现在,如果您希望由另一个元素提供文本,您只需更改该行:onClicked: backend.text = tf.text.


Python 到 QML:

  1. 我无法告诉你这种方法做错了什么,因为你没有显示任何代码,但我确实指出了缺点。主要缺点是要使用此方法,您必须有权访问该方法,并且有两种可能性,第一种是它是 rootObjects,如第一个示例中所示或通过 objectName 进行搜索,但它发生了您最初查找该对象,得到它,并将其从 QML 中删除,例如,每次更改页面时都会创建和删除 StackView 的页面,因此此方法不正确。

  2. 对我来说第二种方法是正确的,但您没有正确使用它,与 QtWidgets 不同,QtWidgets 专注于 QML 中的行和列,使用角色。首先让我们正确实现您的代码。

First textlines无法从以下位置访问QML因为它不是一个qproperty。正如我所说,您必须通过角色访问,要查看模型的角色,您可以打印结果roleNames():

model = QStringListModel()
model.setStringList(["hi", "ho"])
print(model.roleNames())

output:

{
    0: PySide2.QtCore.QByteArray('display'),
    1: PySide2.QtCore.QByteArray('decoration'),
    2: PySide2.QtCore.QByteArray('edit'),
    3: PySide2.QtCore.QByteArray('toolTip'),
    4: PySide2.QtCore.QByteArray('statusTip'),
    5: PySide2.QtCore.QByteArray('whatsThis')
}

如果您想获取文本,则必须使用角色Qt::DisplayRole,其数值根据docs http://doc.qt.io/qt-5/qt.html#ItemDataRole-enum is:

Qt::DisplayRole 0   The key data to be rendered in the form of text. (QString)

so in QML你应该使用model.display(或仅display)。所以正确的代码如下:

main.py

import sys

from PySide2.QtCore import QUrl, QStringListModel
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine  

if __name__ == '__main__':
    app = QGuiApplication(sys.argv)
    model = QStringListModel()
    model.setStringList(["hi", "ho"])

    engine = QQmlApplicationEngine()
    engine.rootContext().setContextProperty("myModel", model)
    engine.load(QUrl.fromLocalFile('main.qml'))
    if not engine.rootObjects():
        sys.exit(-1)
    sys.exit(app.exec_())

main.qml

import QtQuick 2.10
import QtQuick.Controls 2.1
import QtQuick.Window 2.2

ApplicationWindow {
    title: qsTr("Test")
    width: 640
    height: 480
    visible: true
    ListView{
        model: myModel
        anchors.fill: parent
        delegate: Text { text: model.display }
    }
}

如果您希望它可编辑,则必须使用model.display = foo:

import QtQuick 2.10
import QtQuick.Controls 2.1
import QtQuick.Window 2.2

ApplicationWindow {
    title: qsTr("Test")
    width: 640
    height: 480
    visible: true
    ListView{
        model: myModel
        anchors.fill: parent
        delegate: 
        Column{
            Text{ 
                text: model.display 
            }
            TextField{
                onTextChanged: {
                    model.display = text
                }
            }
        }
    }
}

还有许多其他方法可以通过 QML 与 Python/C++ 交互,但最好的方法包括通过以下方式嵌入在 Python/C++ 中创建的对象:setContextProperty.

正如您所指出的,PySide2的文档不多,它正在实现,您可以通过以下内容看到它link https://doc-snapshots.qt.io/qtforpython/index.html。存在的最多的是 PyQt5 的许多示例,因此我建议您了解两者之间的等效项并进行翻译,此翻译并不难,因为它们是最小的更改。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用 PySide2 连接 Python 和 QML? 的相关文章

随机推荐