如何将 Python 和 QML 与 PySide2 连接起来



我想在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_())

这工作正常,我可以打印"你好!但是,这是最好的方法,还是创建一个带有插槽的类并使用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 {
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中,因此,如果您在视图中更改某些内容,您只需更改连接:

例:

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_())

主.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. 我不能告诉你你用这种方法做错了什么,因为你没有显示任何代码,但我确实指出了缺点。主要缺点是要使用此方法,您必须有权访问该方法,并且有 2 种可能性,第一种是它是一个 rootObjects,如您的第一个示例或搜索对象名称所示,但碰巧您最初寻找对象,您得到它并从 QML 中删除, 例如,每次更改页面时都会创建和删除 StackView 的页面,因此此方法不正确。

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

第一个textlines无法从QML访问,因为它不是qproperty。正如我所说,您必须通过角色访问,要查看模型的角色,您可以打印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,根据文档,其数值为:

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

所以在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_())

主.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++交互,但最好的方法包括通过setContextProperty嵌入在Python/C++中创建的对象。

正如您指出的 PySide2 的文档不多,它正在实现中,您可以通过以下链接查看它。大多数存在的是 PyQt5 的许多示例,因此我建议您了解两者之间的等效性并进行翻译,这种翻译并不难,因为它们是最小的更改。

最新更新