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.
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.
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?
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 ...
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:
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.
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.