如何将Python和QML与PySide2连接?

时间:2018-05-30 17:00:17

标签: python qt qml pyside2

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

现在我想用几个小时来连接GUI和逻辑,但它无法正常工作。 我管理连接QML - > Python,但不是相反。我有Python类代表我的数据模型,我添加了JSON编码和解码函数。所以目前还没有涉及SQL数据库。但是,QML视图和某些数据库之间的直接连接可能会使事情变得更容易吗?

现在有些代码。

QML - >蟒

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,但只是错误的参数错误等。
  2. 这种方法有意义吗?实际上我不知道任何javascript,所以如果没有必要,我宁愿不使用它。

    1. ViewModel方法 我创建了一个文件viewmodel.py

      from PySide2.QtCore import QStringListModel
      
      class ListModel(QStringListModel):
      
      def __init__(self):
           self.textlines = ['hi', 'ho']
           super().__init__()
      
    2. 在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视图中操作数据是否存在完全不同的方法?
      2. 感谢您的帮助! 我知道Qt文档,但我对它不满意。也许我错过了一些东西。但PyQt似乎比PySide2更受欢迎(至少谷歌搜索似乎表明了这一点)和PySide引用经常使用PySide1或不使用QML QtQuick做事方式......

1 个答案:

答案 0 :(得分:4)

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

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

<强> 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专注于QML行和QML中使用的角色。首先让我们正确实现您的代码。

  3. 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,其docs的数值为:

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

    所以在QML中你应该使用display。所以正确的代码如下:

    <强> main.py

    import sys
    
    from PySide2.QtCore import QObject, Signal, Property, 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: display }
        }
    }
    

    如果您希望它可以编辑,则必须使用setData()方法:

    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: display 
                }
                TextField{
                    onTextChanged: {
                        var ix = myModel.index(index, 0)
                        myModel.setData(ix, text, 0)
                    }
                }
            }
        }
    }
    

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

    当你指出PySide2的文档并不多时,它正在实现,你可以通过以下link看到它。最常见的是PyQt5的很多例子,所以我建议你理解两者之间的等价性并进行翻译,这种翻译并不难,因为它们是最小的变化。