web-dev-qa-db-fra.com

Comment connecter Python et QML avec PySide2?

Je veux écrire une application de bureau simple sur Ubuntu et je pensais qu'un moyen simple était d'utiliser Qt avec QML comme GUI et Python comme langage pour la logique, car je suis un peu familier avec Python .

Maintenant, j'essaie depuis des heures de connecter l'interface graphique et la logique, mais cela ne fonctionne pas. J'ai géré la connexion QML -> Python mais pas l'inverse. J'ai Python classes qui représentent mon modèle de données et j'ai ajouté le codage et le décodage JSON) Donc, pour l'instant, il n'y a pas de base de données SQL impliquée. Mais peut-être qu'une connexion directe entre la vue QML et certaines bases de données faciliterait les choses?

Alors maintenant, du code.

QML -> Python

Le fichier 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!")
        }
    }
}    
}

Ensuite, j'ai mon slots.py:

from PySide2.QtCore import Slot

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

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

Et enfin mon 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_())

Cela fonctionne très bien et je peux imprimer "Bonjour!". Mais est-ce la meilleure façon de le faire ou est-il préférable de créer une classe avec des slots et d'utiliser setContextProperty pour pouvoir les appeler directement sans ajouter de signaux supplémentaires?

Python -> QML

Je ne peux pas faire ça. J'ai essayé différentes approches, mais aucune n'a fonctionné et je ne sais pas non plus laquelle est la meilleure à utiliser. Ce que je veux faire, c'est par exemple afficher une liste d'objets et proposer des moyens de manipuler des données dans l'application, etc.

  1. inclure Javascript: j'ai ajouté un fichier supplémentaire application.js avec une fonction juste pour imprimer quelque chose, mais elle pourrait probablement être utilisée pour définir le contexte d'un champ de texte, etc. J'ai ensuite essayé d'utiliser QMetaObject et invokeMethod, mais j'ai juste obtenu des erreurs avec des arguments incorrects, etc.

Cette approche a-t-elle un sens? En fait, je ne connais pas de javascript, donc si ce n'est pas nécessaire, je préfère ne pas l'utiliser.

  1. Approche ViewModel J'ai créé un fichier viewmodel.py

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

Et dans le main.py j'ai ajouté:

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

et le ListView ressemble à ceci:

ListView {
            width: 180; height: 200

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

J'obtiens une erreur "monModèle n'est pas défini", mais je suppose que cela ne peut pas fonctionner de toute façon, car les délégués ne prennent qu'un élément et pas une liste. Cette approche est-elle bonne? et si oui, comment puis-je le faire fonctionner?

  1. Existe-t-il une approche totalement différente pour manipuler les données dans une vue QML?

J'apprécie ton aide! Je connais la documentation de Qt mais je n'en suis pas satisfaite. Alors peut-être que je manque quelque chose. Mais PyQt semble être beaucoup plus populaire que PySide2 (au moins les recherches Google semblent l'indiquer) et les références PySide utilisent souvent PySide1 ou non la manière QML QtQuick de faire les choses ...

8
Fabian

Votre question a de nombreux aspects donc je vais essayer d'être détaillée dans ma réponse et aussi cette réponse sera continuellement mise à jour car ce type de questions sont souvent posées mais ce sont des solutions pour un cas spécifique donc je vais me permettre de la donner une approche générale et être précis dans les scénarios possibles.

QML en Python:

Votre méthode fonctionne car la conversion de type en python est dynamique, en C++ cela ne se produit pas. Elle fonctionne pour les petites tâches mais elle n'est pas maintenable, la logique doit être séparée de la vue donc elle devrait Pour être concret, disons que le texte imprimé sera pris par la logique pour effectuer un traitement, alors si vous modifiez le nom du signal, ou si les données ne dépendent pas de ApplicationWindow mais sur un autre élément, etc., vous devrez alors modifier un code de connexion de lot.

Il est recommandé, comme vous l'indiquez, de créer une classe responsable du mappage des données dont vous avez besoin de votre logique et de l'intégrer dans QML, donc si vous changez quelque chose dans la vue, vous changez simplement la connexion:

Exemple:

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
        } 
    }
}

Maintenant, si vous voulez que le texte soit fourni par un autre élément, il vous suffit de changer la ligne: onClicked: backend.text = tf.text.


Python en QML:

  1. Je ne peux pas vous dire ce que vous avez fait de mal avec cette méthode car vous ne montrez aucun code, mais j'indique les inconvénients. Le principal inconvénient est que pour utiliser cette méthode, vous devez avoir accès à la méthode et pour cela il y a 2 possibilités, la première est qu'il s'agit d'un rootObjects comme il est montré dans votre premier exemple ou en cherchant dans le objectName, mais cela arrive que vous recherchez initialement l'objet, vous l'obtenez et cela est supprimé de QML, par exemple les pages d'un StackView sont créées et supprimées à chaque fois que vous changez de page, cette méthode ne serait donc pas correcte.

  2. La deuxième méthode pour moi est la bonne, mais vous ne l'avez pas utilisée correctement, contrairement aux QtWidgets qui se concentrent sur la ligne et la colonne dans QML, les rôles sont utilisés. Implémentons d'abord votre code correctement.

D'abord, textlines n'est pas accessible depuis QML car ce n'est pas un qproperty. Comme je l'ai dit, vous devez accéder via les rôles, pour voir les rôles d'un modèle, vous pouvez imprimer le résultat de roleNames():

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

production:

{
    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')
}

Dans le cas où vous souhaitez obtenir le texte, vous devez utiliser le rôle Qt::DisplayRole, Dont la valeur numérique selon docs est:

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

donc dans QML vous devez utiliser model.display (ou seulement display). donc le code correct est le suivant:

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 }
    }
}

Si vous voulez qu'il soit modifiable, vous devez utiliser le 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
                }
            }
        }
    }
}

Il existe de nombreuses autres méthodes pour interagir avec Python/C++ avec QML, mais les meilleures méthodes impliquent d'incorporer les objets créés dans Python/C++ via setContextProperty.

Comme vous indiquez que la documentation de PySide2 n'est pas beaucoup, elle est en cours d'implémentation et vous pouvez le voir à travers ce qui suit link . Ce qui existe le plus, ce sont de nombreux exemples de PyQt5 donc je vous recommande de comprendre quelles sont les équivalences entre les deux et de faire une traduction, cette traduction n'est pas difficile car ce sont des changements minimes.

16
eyllanesc