I. Introduction▲
L'article précédent expliquait la manière de générer une fenêtre avec des composants simples en QML dans le but de créer une première application graphique. Il est temps maintenant d'approfondir l'étude du langage QML et de découvrir la manière d'interagir entre la fenêtre et le code Python.
Les lignes de code qui suivent, dans un premier temps, auront pour objectif de créer une application graphique plus développée que celle vue dans le précédent article puis de la lancer depuis un script Python.
Dans un deuxième temps, elles montreront la manière de récupérer des valeurs de champs et aussi comment en envoyer.
L'exemple sera la création d'un simple programme d'identification d'utilisateur. Les fonctionnalités de base attendues seront donc :
- capture d'un identifiant ;
- capture d'un mot de passe ;
- vérification des informations saisies ;
- refus ou acceptation de la connexion.
Même si son emploi n'est pas indispensable, je vous conseille fortement l'utilisation de Qt Creator. En effet, cet EDI apporte deux avantages :
- depuis sa version 2.8, il offre quelques fonctionnalités pour les développeurs Python ;
- il est prévu pour fonctionner avec le langage QML, base de Qt Quick (coloration syntaxique, aide à la saisie et même un Designer).
II. Présentation de nouveaux modules Qt Quick▲
Le premier article montrait la création d'une interface graphique en utilisant QML et en créant chaque composant à partir de zéro : ainsi, créer un simple bouton nécessitait la création d'un rectangle, puis l'ajout de diverses propriétés en exploitant les composants de base de Qt Quick 2.
Heureusement, Qt Quick met à disposition beaucoup d'autres modules, dont certains fournissent des composants plus complets qu'un simple rectangle, par exemple.
Ces modules comptent notamment Qt Quick Controls, qui sera entre autres l'objet du chapitre suivant.
II-A. Création d'une fenêtre et de ses composants▲
Qt Quick Controls contient un certain nombre de composants courants comme la fenêtre principale de notre application, des boutons, des zones de texte, tout en restant dans la logique de Qt Quick, à savoir très personnalisables.
Dans le cadre du projet de cet article, une fenêtre de connexion, outre la fenêtre principale, il faudra une zone de saisie de l'identifiant, une zone de saisie du mot de passe, un bouton de validation et pour finir une zone de texte indiquant si la connexion est autorisée ou non.
Sans plus attendre, voici une manière d'écrire le code QML générant cette fenêtre :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
import
QtQuick 2.4
import
QtQuick.Controls 1.3
ApplicationWindow
{
// Création de la fenêtre principale avec quelques attributs pratiques comme menuBar et statusBar
title
:
qsTr("Fenêtre de connexion"
)
width
:
400
height
:
200
menuBar
:
MenuBar
{
Menu
{
title
:
qsTr("&File"
)
MenuItem
{
text
:
"Une action"
onTriggered
:
console.log("Magique cette barre de menu"
) // console.log(<msg>) affiche dans la console de sortie le message <msg>.
}
}
}
statusBar
:
StatusBar
{
Label
{
text
:
"QML est vraiment merveilleux"
}
}
// Puis on crée et on place les composants souhaités.
Label
{
text
:
"Login"
x
:
10
y
:
25
}
TextField
{
id
:
login
x
:
100
y
:
20
placeholderText
:
qsTr("Enter your login"
)
}
Label
{
text
:
"Mot de passe"
x
:
10
y
:
55
}
TextField
{
id
:
password
x
:
100
y
:
50
placeholderText
:
qsTr("Enter your password"
)
echoMode
:
TextInput.Password
}
Button
{
text
:
"Se connecter"
x
:
10
y
:
100
}
Label
{
id
:
result
x
:
10
y
:
135
}
}
Le code proposé ne doit pas poser de problème de compréhension après le premier article. La documentation officielle des composants QML fournit plus de détails au besoin.
C'est bien joli tout ça, mais le placement des composants est loin d'être simple (positions absolues) et l'application est loin d'être très dynamique. À titre d'exemple, on ne redimensionne pas les widgets si la taille de la fenêtre change.
II-B. Premiers pas avec les dispositions▲
Heureusement pour nous, Qt Quick réserve encore de belles surprises comme les dispositions. Il en existe trois :
- GridLayout place ses composants dans une grille ;
- ColumnLayout place ses composants en colonne ;
- RowLayout place ses composants en ligne.
Ces dispositions sont fournies par le module Qt Quick Layouts. GridLayout est certes la plus complexe à utiliser, mais également la plus adaptée à la situation.
Voici le code ci-dessus retravaillé avec GridLayout :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
import
QtQuick 2.4
import
QtQuick.Controls 1.3
import
QtQuick.Layouts 1.1
ApplicationWindow
{
title
:
qsTr("Fenêtre de connexion"
)
width
:
400
height
:
200
menuBar
:
MenuBar
{
Menu
{
title
:
qsTr("&File"
)
MenuItem
{
text
:
"Une action"
onTriggered
:
console.log("Magique cette barre de menu"
)
}
}
}
statusBar
:
StatusBar
{
Label
{
text
:
"QML est vraiment merveilleux"
}
}
GridLayout
{
id
:
grid
anchors.fill
:
parent
// Ancre la disposition au composant souhaité
columns
:
2
// Nombre de colonnes
anchors.margins
:
5
// Marge entre la disposition et les bords de son conteneur
columnSpacing
:
10
// Espace entre chaque colonne
Label
{
text
:
"Identifiant"
}
TextField
{
id
:
login
Layout.fillWidth
:
true
placeholderText
:
qsTr("Enter your login"
)
}
Label
{
text
:
"Mot de passe"
}
TextField
{
id
:
password
Layout.fillWidth
:
true
placeholderText
:
qsTr("Enter your password"
)
echoMode
:
TextInput.Password
}
Button
{
text
:
"Se connecter"
// Ces deux lignes autorisent le redimensionnement automatique du composant
Layout.fillWidth
:
true
Layout.fillHeight
:
true
Layout.columnSpan
:
2
// Étalé sur deux colonnes
}
Label
{
id
:
result
text
:
"Non connecté"
Layout.fillWidth
:
true
Layout.columnSpan
:
grid.columns // Étalé sur toutes les colonnes
}
}
}
Ah… enfin un résultat simple, dynamique et très pratique. Il ne reste plus qu'à rendre le code fonctionnel en interagissant avec un code Python.
Une autre solution aurait été d'affecter un pourcentage de la hauteur et/ou de la largeur de la fenêtre à chaque composant en utilisant leurs propriétés height ou width. N'hésitez pas à faire des essais si cette solution vous intéresse.
III. Interaction avec un code Python▲
Maintenant que l'application est prête d'un point de vue graphique, il est temps de la rendre fonctionnelle par Python.
III-A. Lancement de la fenêtre depuis Python▲
Heureusement, cette intégration se fait très simplement par les lignes de code suivantes :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import
sys
from
PyQt5.QtWidgets import
QApplication
from
PyQt5.QtQml import
QQmlApplicationEngine
if
__name__
==
"__main__"
:
app =
QApplication
(
sys.argv)
engine =
QQmlApplicationEngine
(
)
engine.load
(
'test.qml'
)
win =
engine.rootObjects
(
)[0
]
win.show
(
)
sys.exit
(
app.exec_
(
))
Ce script est assez simple : il crée un objet QQmlApplicationEngine auquel un document QML est associé et affiché. En lançant ce code, la fenêtre attendue doit apparaître.
Très bien ! Après l'affichage d'une fenêtre créée en QML via Python, il reste maintenant à interagir pleinement avec elle depuis Python.Interaction avec le code.
Ce chapitre présente trois manières de créer une interaction entre du code QML et du code Python. Ces trois méthodes sont équivalentes : le choix de l'une ou l'autre se porte sur la situation ou les habitudes.
Il y aura des modifications à apporter aussi bien dans la partie Python que QML. Les éléments nouveaux seront expliqués au fur et à mesure.
III-B. Modification du code Python▲
Dans un premier temps, le code Python de lancement de l'application est modifié pour passer au contexte Qt Quick d'une instance de la classe MainApp (à créer) qui implémente la partie Python de la communication.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
if
__name__
==
"__main__"
:
app =
QApplication
(
sys.argv)
engine =
QQmlApplicationEngine
(
)
# Création d'un objet QQmlContext pour communiquer avec le code QML
ctx =
engine.rootContext
(
)
engine.load
(
'test.qml'
)
win =
engine.rootObjects
(
)[0
]
py_mainapp =
MainApp
(
ctx, win)
ctx.setContextProperty
(
"py_MainApp"
, py_mainapp)
win.show
(
)
sys.exit
(
app.exec
(
))
Une fois ceci fait, il reste à créer la classe MainApp et à y mettre les fonctions nécessaires pour la communication. La première méthode consiste à renvoyer la réponse par une propriété du contexte global : pour y accéder en QML, il suffira de taper le nom de la propriété.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import
sys
from
PyQt5.QtCore import
QObject, pyqtSlot, QVariant
# Classe servant dans l'interaction.
class
MainApp
(
QObject):
def
__init__
(
self, context, parent=
None
):
super(
MainApp, self).__init__
(
parent)
# Recherche d'un enfant appelé myButton dont le signal clicked sera connecté à la fonction test3
self.win =
parent
self.win.findChild
(
QObject, "myButton"
).clicked.connect
(
self.test3)
self.ctx =
context
# Création de la fonction d'identification
def
verifConnection
(
self, login, password):#voir PEP8
if
login ==
"login"
and
password ==
"mdp"
:
return
"Bonjour
%s
. Vous êtes maintenant connecté"
%
login
else
:
return
"Désolé, authentification impossible"
# Premier test de communication : propriétés contextuelles.
@pyqtSlot(QVariant, QVariant)
def
test1
(
self, login, password):
txt =
self.verifConnection
(
login, password)
# Transmission du résultat comme une propriété nommée retour
self.ctx.setContextProperty
(
"retour"
, txt)
return
0
# Deuxième test de communication : création d'une fonction ayant pour type de retour un QVariant (obligatoire ici pour que QML sache l'interpréter).
@pyqtSlot(QVariant, QVariant, result=QVariant)
def
test2
(
self, login, password):
return
self.verifConnection
(
login, password)
# Troisième test de communication : modification directe d'un composant QML.
def
test3
(
self):
# Recherche des enfants par leur attribut objectName, récupération de la valeur de leur propriété text
login =
self.win.findChild
(
QObject, "myLogin"
).property(
"text"
)
password =
self.win.findChild
(
QObject, "myPassword"
).property(
"text"
)
txt =
self.verifConnection
(
login, password)
self.win.findChild
(
QObject, "labelCo"
).setProperty
(
"text"
, txt)
return
0
III-C. Modification du code QML▲
Maintenant que notre code Python est prêt, il ne reste plus qu'à modifier légèrement le code QML pour exploiter ces moyens de communication.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
import
QtQuick 2.4
import
QtQuick.Controls 1.3
import
QtQuick.Layouts 1.1
ApplicationWindow
{
title
:
qsTr("Fenêtre de connexion"
)
width
:
400
height
:
200
menuBar
:
MenuBar
{
Menu
{
title
:
qsTr("&File"
)
MenuItem
{
text
:
"Une action"
onTriggered
:
console.log("Magique cette barre de menu"
)
}
}
}
statusBar
:
StatusBar
{
Label
{
text
:
"QML est vraiment merveilleux"
}
}
GridLayout
{
id
:
grid
anchors.fill
:
parent
columns
:
2
anchors.margins
:
5
columnSpacing
:
10
Label
{
text
:
"Identifiant"
}
TextField
{
id
:
login
objectName
:
"myLogin"
// test3()cherche un enfant nommé myLogin
Layout.fillWidth
:
true
placeholderText
:
qsTr("Enter your login"
)
}
Label
{
text
:
"Mot de passe"
}
TextField
{
id
:
password
objectName
:
"myPassword"
Layout.fillWidth
:
true
placeholderText
:
qsTr("Enter your password"
)
echoMode
:
TextInput.Password
}
Button
{
text
:
"Se connecter par test1()"
Layout.fillWidth
:
true
Layout.fillHeight
:
true
Layout.columnSpan
:
2
onClicked
: {
py_MainApp.test1
(
login.
text
,
password
.
text
)
result.
text
=
retour
}
}
Button
{
text
:
"Se connecter par test2()"
Layout.fillWidth
:
true
Layout.fillHeight
:
true
Layout.columnSpan
:
2
onClicked
:
result.text =
py_MainApp.test2(login.text, password.text)
}
Button
{
text
:
"Se connecter par test3()"
objectName
:
"myButton"
Layout.fillWidth
:
true
Layout.fillHeight
:
true
Layout.columnSpan
:
2
}
Label
{
id
:
result
objectName
:
"labelCo"
text
:
"Non connecté"
Layout.fillWidth
:
true
Layout.columnSpan
:
grid.columns
}
}
}
IV. Conclusion▲
Après une première partie destinée à découvrir Qt Quick, cette deuxième montre comment créer une interface graphique très fonctionnelle avec un minimum de code ainsi que l'interaction entre du code Python et du code QML.
La lecture de ces deux premières parties doit vous permettre d'imaginer sereinement vos prochaines lignes de code. Cependant, Qt Quick possède encore beaucoup d'autres fonctionnalités qui seront abordées dans la troisième et dernière partie.
V. Remerciements▲
Je tiens tout particulièrement à remercier Thibaut Cuvelier et Claude LELOUP pour leur relecture attentive.