web-dev-qa-db-ja.com

PythonとQMLをPySide2に接続する方法は?

Ubuntuでシンプルなデスクトップアプリケーションを書きたいのですが、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を知らないので、必要なければJavaScriptを使用しません。

  1. ViewModelアプローチファイル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つしか取らないため、とにかく機能しません。このアプローチは良いものですか?はいの場合、どのように機能させるのですか?

  1. QMLビューでデータを操作するためのまったく異なるアプローチはありますか?

私はあなたの助けに感謝します! Qtのドキュメントは知っていますが、満足していません。だから私は何かが足りないのかもしれません。しかし、PyQtはPySide2よりもはるかに人気があるようで(少なくともgoogle検索はそれを示しているようです)、PySide参照はしばしばPySide1を使用するか、QML QtQuickの方法を使用しない...

8
Fabian

あなたの質問には多くの側面があるので、私は私の回答で詳細に説明するようにします。また、このタイプの質問はよく聞かれるので、この回答は継続的に更新されますが、それらは特定のケースの解決策なので、自由に答えます一般的なアプローチと可能なシナリオで具体的です。

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

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. コードが表示されていないため、この方法で何がおかしいのかはわかりませんが、欠点は指摘しています。主な欠点は、このメソッドを使用するには、メソッドにアクセスする必要があり、2つの可能性があることです。1つ目は、最初の例に示されているようにrootObjectsであるか、objectNameを検索することですが、最初にオブジェクトを探し、それを取得すると、QMLから削除されます。たとえば、ページを変更するたびにStackViewのページが作成および削除されるため、このメソッドは正しくありません。

  2. 私にとって2番目の方法は正しい方法ですが、ロールが使用されるQMLの行と列に焦点を合わせるQtWidgetsとは異なり、正しく使用していません。まず、コードを正しく実装しましょう。

最初のtextlinesQMLではないため、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では_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のドキュメントはそれほど多くないことを示しているので、PySide2は実装されており、次の link で確認できます。最も存在するのはPyQt5の多くの例であるため、両方の同等性を理解して翻訳することをお勧めします。これらの翻訳は最小限の変更であるため、難しくありません。

16
eyllanesc