IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Premiers pas avec Qt Quick-PyQt

Deuxième partie : création d'interfaces graphiques avec Qt Quick et interaction avec Python

La suite d'articles proposés a pour but d'initier le lecteur à la création d'une application écrite avec PyQt et Qt Quick.

Afin d'appréhender au mieux ce qui suit, des notions de Python et Qt Quick sont nécessaires. Vous trouverez tout ce qui peut vous être utile dans la page cours Python de Developpez.com et dans mon précédent article sur Qt Quick.

De plus, une installation de PyQt 5.1 minimum est indispensable.

2 commentaires Donner une note à l´article (5)

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

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 :

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 :

Fenêtre de connexion
Cacher/Afficher le codeSélectionnez
1.
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 :

Utilisation des dispositions
Sélectionnez
1.
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 :

Exécution depuis Python
Sélectionnez
1.
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.

Modification du code Python
Sélectionnez
1.
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é.

Classe MainApp
Sélectionnez
1.
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.

Modification code QML
Sélectionnez
1.
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.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2015 Charlie Gentil. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.