且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何用 PySide2 连接 Python 和 QML?

更新时间:2023-01-18 08:36:09

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

Your question has many aspects so I will try to be detailed in my answer and also this answer will be continuously updated because this type of questions are often asked but they are solutions for a specific case so I am going to take the liberty of giving it a general approach and be specific in the possible scenarios.

QML 到 Python:

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

Your method works because the type conversion in python is dynamic, in C++ it does not happen. It works for small tasks but it is not maintainable, the logic must be separated from the view so it should not be dependent. To be concrete, let's say that the printed text will be taken by the logic to perform some processing, then if you modify the name of the signal, or if the data does not depend on ApplicationWindow but on another element, etc. then you will have to change a lot connection code.

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

The recommended as you indicate is to create a class that is responsible for mapping the data you need your logic and embed it in QML, so if you change something in the view you just change the connection:

示例:

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

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

Now if you want the text to be provided by another element you just have to change the line: onClicked: backend.text = tf.text.

Python 到 QML:

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

  1. I can not tell you what you did wrong with this method because you do not show any code, but I do indicate the disadvantages. The main disadvantage is that to use this method you must have access to the method and for that there are 2 possibilities, the first one is that it is a rootObjects as it is shown in your first example or searching through the objectName, but it happens that you initially look for the object, you get it and this is removed from QML, for example the Pages of a StackView are created and deleted every time you change pages so this method would not be correct.

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

The second method for me is the correct one but you have not used it correctly, unlike the QtWidgets that focus on the row and the column in QML the roles are used. First let's implement your code correctly.

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

First textlines is not accessible from QML since it is not a qproperty. As I said you must access through the roles, to see the roles of a model you can print the result of roleNames():

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

输出:

{
    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 是:

In the case that you want to obtain the text you must use the role Qt::DisplayRole, whose numerical value according to the docs is:

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

所以在 QML 中你应该使用 model.display(或者只使用 display).所以正确的代码如下:

so in QML you should use model.display(or only display). so the correct code is as follows:

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

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

If you want it to be editable you must use the 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++ 交互,但***的方法是通过 setContextProperty 嵌入在 Python/C++ 中创建的对象.


There are many other methods to interact with Python/C++ with QML but the best methods involve embedding the objects created in Python/C++ through setContextProperty.

正如你所说的 PySide2 的文档不多,它正在实施中,你可以通过以下 链接.存在最多的是 PyQt5 的许多示例,因此我建议您了解两者之间的等价关系并进行翻译,这种翻译并不难,因为它们的变化很小.

As you indicate the docs of PySide2 is not much, it is being implemented and you can see it through the following link. What exists most are many examples of PyQt5 so I recommend you understand what are the equivalences between both and make a translation, this translation is not hard since they are minimal changes.