-
PYTHON > PyQT5
- Import QApplication and all the required widgets from PyQt5.QtWidgets.
- Create an instance of QApplication.
- Create an instance of your application’s GUI.
- Show your application’s GUI.
- Run your application’s event loop (or main loop).
- A widget that doesn’t have a parent is a main window or a top-level window.
- A widget that has a parent (which is always another widget) is contained (or shown) within its parent.
- La défintion d’une action peut s’accompagner de son rattachement à un raccourci clavier, d’une information apparaissant dans la barre de statut (statusTip), mais surtout à une action quand elle est activée (« triggered »). Ici, l’activation de l’action est rattachée à la méthode quit de l’objet prédéfini qApp, d’une manière similaire à l’attachement à un gestionnaire d’événements en JavaScript.
- On définit un menu par la méthode menuBar. On ajoute à l’objet ainsi défini des éléments, auxquels on attache un texte (le caractère & permet d’indiquer un raccourci clavier) ainsi qu’une action (ici celle définie précédemment)
- La barre d’outils a un fonctionnement similaire.
- Enfin, la méthode statusBar permet d’accéder à la barre de statut de l’application
- la source de l’événement, qui est l’objet changeant d’état (comme un bouton qui passe à l’état cliqué)
- l’objet événement lui-même, qui porte des informations sur ce changement d’état
- la cible de l’événement, c’est-à-dire l’objet qui va devoir réagir à la survenue de l’événement.
PyQt5 permet une interface graphique, XML, network communication, regular expressions, threads, SQL, multimedia, web browsing, and other technologies available in Qt. PyQt5 implements over one thousand of these Qt classes in a set of Python modules, all of which are contained within a top-level Python package called pyQt5.
sudo apt install python3-pyqt5
FENÊTRE VIDE
On importe les classes QApplication et QWidget du module PyQt5.QtWidgets. On définit ensuite une nouvelle application (monApp), puis un « widget de 500×300 que l’on place à 500 px à gauche et 500 px en haut. Puis on le montre. sys.exit(monApp.exec_()) permet de quitter l’application en cliquant sur la croix telle qu’elle est définie par le système d’exploitation sur la fenêtre.
import sys from PyQt5.QtWidgets import QLabel, QApplication, QWidget class Fen(QWidget): def __init__(self): super().__init__() self.lanceUI() def lanceUI(self): self.resize(500,300) self.setGeometry(100, 100, 280, 80) self.move(500, 500) self.setWindowTitle("Titre de fenêtre") self.show() app=QApplication(sys.argv) w=Fen() sys.exit(app.exec_())
super()permet de remonter à la classe dont on hérite.import syspour pouvoir fermer l’application. Puis on importe QApplication, QWidget et QLabel depuis QtWidgets,(qui appartient à PyQt5.app = QApplication(sys.argv)Ici, on instancie un objet QApplication. qui doit être créé avant d’en ajouter d’autres. QApplication peut recevoir les arguments passés avec sys.argv. Si on ne veut pas passer d’arguments :app = QApplication([]).setGeometry(x, y, width, height)
On créé la fenêtre :
window = QWidget() helloMsg = QLabel('<h1>Hello World!</h1>', parent=window) helloMsg.move(60, 15)
Chaque élément needs widgets! Here, you use a QLabel object (helloMsg) to show the message Hello World! on your application’s window. QLabel objects can accept HTML text, so you can use the HTML element ‘<h1>Hello World!</h1>’ to format the text as an h1 header. Finally, you use .move() to place helloMsg at the coordinates (60, 15) on your application’s window.
Note: In PyQt5, you can use any widget (a subclass of QWidget) as a top-level window, or even a button or a label. The only condition is that you pass no parent to it. When you use a widget like this, PyQt5 automatically gives it a title bar and turns it into a normal window.
The parent-child relationship is used for two complementary purposes:
This relationship also defines ownership, with parents owning their children. The PyQt5 ownership model ensures that if you delete a parent (for example, a top-level window), then all of its children (widgets) are automatically deleted as well.
To avoid memory leaks, you should always make sure that any QWidget object has a parent, with the sole exception of top-level windows.
You’re done with step three, so let’s code the last two steps and get your first PyQt GUI application ready to go live:
window.show() sys.exit(app.exec_())
.show() schedules a paint event. In other words, it adds a new event to the application’s event queue. You cover the event loop in a later section.
Note: A paint event is a request for painting the widgets that compose a GUI.
Finally, you start the application’s event loop by calling app.exec_(). The call to .exec_() is wrapped in a call to sys.exit(), which allows you to cleanly exit Python and release memory resources when the application terminates.
Here, your application shows a window (based on QWidget) with the message Hello World! on it. To show the message, you use a QLabel that contains the message in HTML format.
Congrats! You’ve created your first PyQt GUI desktop application!
MISE EN PAGE & GRILLE
Box layout
QHBoxLayout et QVBoxLayout alignent les contenus horizontalement ou verticalement. Exemple, combinés, 3 boutons en bas de la fenêtre, répartis régulièrement :
import sys from PyQt5.QtWidgets import QWidget, QApplication, QPushButton, QHBoxLayout, QVBoxLayout class Principale(QWidget): def __init__(self): super().__init__() self.setUI() def setUI(self): btn1=QPushButton("Bouton1") btn2=QPushButton("Bouton2") btn3=QPushButton("Bouton3") hbox=QHBoxLayout() hbox.addStretch(1) hbox.addWidget(btn1) hbox.addStretch(1) hbox.addWidget(btn2) hbox.addStretch(1) hbox.addWidget(btn3) hbox.addStretch(1) vbox=QVBoxLayout() vbox.addStretch(1) vbox.addLayout(hbox) self.setLayout(vbox) self.setGeometry(300,300,500,250) self.setWindowTitle('Fenêtre principale') self.show() monApp=QApplication(sys.argv) fenetre=Principale() sys.exit(monApp.exec_())
On commence par créer les trois boutons. On crée ensuite un layout horizontal avec QHBoxLayout. La méthode addStretch permet d’ajouter un espace de largeur variable, et qui s’ajuste en fonction de la largeur de la fenêtre. Ici, on insère donc un espace variable, un bouton, un espace variable, un deuxième bouton, un espace variable, le dernier bouton et un dernier espace variable.
On crée ensuite un layout vertical, auquel on ajoute un espace variable puis le layout horizontal que l’on vient de créer. Comme on n’ajoute rien en-dessous, le layout hbox sera toujours calé sur le bas de la fenêtre.
Une telle mise en page permet de s’assurer que les boutons restent en permanence régulièrement répartis et calés en bas de la fenêtre.
Grille
Un autre système de mise en page commode est la grille, avec la classe QGridLayout.
import sys from PyQt5.QtWidgets import QWidget, QApplication, QPushButton, QGridLayout class Principale(QWidget): def __init__(self): super().__init__() self.setUI() def setUI(self): btn1=QPushButton("Bouton1") btn2=QPushButton("Bouton2") btn3=QPushButton("Bouton3") btn4=QPushButton("Bouton4") btn5=QPushButton("Bouton5") btn6=QPushButton("Bouton6") grille=QGridLayout() self.setLayout(grille) grille.addWidget(btn1, 1,1) grille.addWidget(btn2, 1,2) grille.addWidget(btn3, 1,3) grille.addWidget(btn4, 2,1) grille.addWidget(btn5, 2,2) grille.addWidget(btn6, 2,3) self.setGeometry(300,300,500,250) self.setWindowTitle('Fenêtre principale') self.show() monApp=QApplication(sys.argv) fenetre=Principale() sys.exit(monApp.exec_())
Application d’une mise en page à une fenêtre d’application
Les layouts ne peuvent être appliqués qu’à des widgets, pas à une classe héritée de QMainWindow comme nous l’avons vu précédemment. Pour cela, il faut déclarer qu’un widget est le widget central de la fenêtre. Si on a défini un layout miseEnPage (cela pourrait être par exemple l’objet grille de l’exemple précédent), on écrira
centre=QWidget() centre.setLayout(miseEnPage) self.setCentralWidget(centre)
WIDGETS
Les widgets sont positionnés à l’intérieur d’une fenêtre principale:
import sys from PyQt5.QtWidgets import QMainWindow, QApplication, QAction, qApp class Principale(QMainWindow): def __init__(self): super().__init__() self.setUI() def setUI(self): exitAction=QAction('&Exit', self) exitAction.setShortcut('Ctrl+Q') exitAction.setStatusTip("Quitter l'application") exitAction.triggered.connect(qApp.exit) menu=self.menuBar() fichierMenu=menu.addMenu("&Fichier") fichierMenu.addAction(exitAction) self.barreOutils=self.addToolBar('Quitter') self.barreOutils.addAction(exitAction) self.setGeometry(300,300,500,250) self.setWindowTitle('Fenêtre principale') self.statusBar().showMessage('Barre de statut') self.show() monApp=QApplication(sys.argv) fenetre=Principale() sys.exit(monApp.exec_())
QAction permet de définir des actions, que l’on peut attacher soit à une barre de menu (menuBar), soit à une barre d’outils (addToolBar)
Les widgets sont les composants de base de l’application. En voici quelques-uns…
import sys from PyQt5.QtWidgets import QMainWindow, QApplication, QAction, qApp, QTextEdit, QPushButton, QHBoxLayout from PyQt5.QtWidgets import QWidget, QVBoxLayout,QToolTip, QLineEdit, QLabel, QCheckBox, QComboBox, QRadioButton class Principale(QMainWindow): def __init__(self): super().__init__() self.setUI() def setUI(self): zoneTexte=QTextEdit() btnOK=QPushButton("OK", self) zoneLigne=QLineEdit() label=QLabel("Champ texte") case=QCheckBox("Case", self) combo=QComboBox(self) btnOK.resize(btnOK.sizeHint()) btnOK.setToolTip("Ceci est un bouton <i>OK</i>") combo.addItem("Choix 1") combo.addItem("Choix 2") combo.addItem("Choix 3") combo.addItem("Choix 4") hbox=QHBoxLayout() hbox.addStretch(1) hbox.addWidget(label) hbox.addWidget(zoneLigne) hbox.addWidget(zoneTexte) hbox.addWidget(btnOK) hbox.addWidget(case) hbox.addWidget(combo) vbox=QVBoxLayout() w=QWidget() w.setLayout(hbox) self.setCentralWidget(w) #Définition des actions exitAction=QAction('&Exit', self) exitAction.setShortcut('Ctrl-Q') exitAction.setStatusTip("Quitter l'application") exitAction.triggered.connect(qApp.exit) menu=self.menuBar() fichierMenu=menu.addMenu("&Fichier") fichierMenu.addAction(exitAction) self.barreOutils=self.addToolBar('Quitter') self.barreOutils.addAction(exitAction) self.setGeometry(300,300,500,250) self.setWindowTitle('Fenêtre principale') self.statusBar().showMessage('Barre de statut') self.show() monApp=QApplication(sys.argv) fenetre=Principale() sys.exit(monApp.exec_())
resize appliquée à un objet de type QPushButton permet de le redimensionner
sizeHint laisse le soin à Qt de déterminer la taille optimale du bouton
Boutons radio
QRadioButton
EVENEMENTS
Action de l’utilisateur (souris, calvier), activation d’une connexion Internet, signal envoyé par une horloge, etc. Dans tous les cas, trois éléments sont concernés :
Sans gestionnaire
PyQt5 utilise les concepts de signal et de slot pour gérer les événements. Voici un exemple simple :
import sys from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QLCDNumber, QSlider class Principale(QWidget): def __init__(self): super().__init__() self.setUI() def setUI(self): lcd=QLCDNumber(self) slider=QSlider(Qt.Horizontal, self) miseEnPage=QVBoxLayout() miseEnPage.addWidget(slider) miseEnPage.addWidget(lcd) self.setLayout(miseEnPage) slider.valueChanged.connect(lcd.display) self.setGeometry(300,300,200,100) self.setWindowTitle('Fenêtre principale') self.show() monApp=QApplication(sys.argv) fenetre=Principale() sys.exit(monApp.exec_())
QLCDNumber et QSlider sont des classes permettant d’afficher un nombre au format LCD et une barre de réglage. L’événement valueChanged du slider est connecté à l’affichage du LCD.
Avec un gestionnaire
On peut également, tout comme en JavaScript, associer un gestionnaire d’événement :
import sys from PyQt5.QtWidgets import QWidget, QApplication, QPushButton, QGridLayout, QMessageBox class Principale(QWidget): def __init__(self): super().__init__() self.setUI() def afficheMessage(self): message=QMessageBox() message.setText("<b>Bouton cliqué</b>") message.setInformativeText("avec deux boutons...") message.setWindowTitle("Message d'alerte") message.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) message.exec() def setUI(self): btn=QPushButton("Bouton", self) btn.clicked.connect(self.afficheMessage) self.setGeometry(300,300,100,30) self.setWindowTitle('Fenêtre principale') self.show() monApp=QApplication(sys.argv) fenetre=Principale() sys.exit(monApp.exec_())
On associe cette fois-ci au clic sur le bouton le gestionnaire afficheMessage, qui affiche des informations diverses dans une boîte…
—
Exercice 1 : dessiner un bouton
1e Etape: Démarrage
Dans un fichier Button.py, créer une fonction main(args) qui sera appelé à partir du point d’entrée du programme (if __name__ == "__main__":)
La méthode main doit instancier une QApplication, instancier une QMainWindow, afficher l’instance de QMainWindow, et executer la QApplication
2e Etape: Classe CanvasButton
Définir une classe CanvasButton qui hérite de QWidget. Dans la méthode main, instancier un objet de type CanvasButton et le définir comme centralWidget de l’instance de QMainWindow.
Définir comme variables d’instance de CanvasButton une variable bbox de type QRect qui décrira la bounding box du futur bouton que votre classe va dessiner (initialisez cette variable avec des dimensions arbitraires). Définir comme variable de classe une varible defaultCol de type QColor.
Dans la classe CanvasButton, surcharger les méthodes mouseMoveEvent, mousePressEvent et mouseReleaseEvent qui sont héritées de la classe QWidget (allez voir la documentation). Assurez-vous que ces méthodes sont bien appelées quand les évènements surviennent (par exemple avec des print().
Surcharger la méthode paintEvent héritée de QWidget pour qu’elle dessine une ellipse de couleur DefaultCol qui a rect comme BoundingBox. Dans les étapes suivantes, nous allons faire en sorte que cette ellipse se comporte comme un bouton cliquable.
Définir une méthode cursorOnEllipse(self, point) qui verifie si un point est contenu par lellipse.
Définir une variable d’instance nommée cursorOver et de type booléen et modifier la méthode mouseMoveEvent pour qu’elle mette à jour la valeur de cursorOver à True quand le curseur survole l’ellipse, et à False sinon.
3e Etape: Classe ButtonModel
Créer un fichier ButtonModel et y déclarer une classe du même nom et y définir les 4 variables de classe suivantes: idle, hover, pressIn et pressOut auxquelles vous attriburez des valeurs entières arbitraires. Définir une variable d’instance state dans le constructeur de la classe et l’initialiser à idle.
L’objet ButtonModel a pour objectif de spécifier dans quel état va se trouver le bouton (l’ellipse) à un instant t. Ce modèle va correspondre à la machine à état suivante:
Définir une méthode pour chaque transition présente (vous devrez définir 4 méthodes) dans la machine à état et faire en sorte que la valeur de state change en fonction de la transition et de l’état courant.
Définir une méthode action qui sera appelée lorsque la machine à état traverse l’état Action et fera un print dans la console
4e Etape: synchroniser ButtonModel et CanvasButton
Ajouter une variable d’instance de type ButtonModel dans la classe CanvasButton
Modifier les méthodes de gestion de évènements souris en sorte qu’elles appellent les méthodes correspondant aux transitions de l’objet ButtonModel. Ainsi, la variable d’self.state de l’instance de l’objet ButtonModel doit à tout instant correspondre à l’état actuel du bouton
Définir deux variables de classes hoverCol et pressCol de type QColor qui vont correspondre aux différentes couleurs adoptées par le bouton.
Modifier la méthode paintEvent pour que le bouton adopte la bonne couleur et au final le bon comportement, comme illustré ci-dessous:
Exercice 2 : Zone de dessin
1e Etape: Créer un nouveau projet affichant une zone de dessin
Créer un fichier CanvasDessin.py dans lequel vous déclarez et implémentez une classe CanvasDessin. Pour l’instant cette classe ne fera pas grand chose : elle doit juste hériter de QWidget. La taille par défaut d’un QWidget étant nulle (celle-ci étant calculée à partir des enfants qu’il contient), utiliser la méthode setMinimumSize() pour imposer une taille adéquate. Créer un nouveau fichier Dessin.py dans lequel vous déclarerez une fonction main(args) qui sera appelé à partir du point d’entrée du programme (if __name__ == "__main__":).
Créez également dans Dessin.py une classe Dessin qui hérite de QMainWindow et qui a comme variable d’instance un objet CanvasDessin qui lui est passé comme zone centrale.
À l’image des exercices précédents, modifier la méthode main pour qu’elle instancie une application Qt, instancie un Dessin et l’affiche. Tester.
2e Etape: créer une classe Trace
Creer une classe Trace dans un fichier Trace.py, qui a une variable d’instance points (de type list), width (de type int) et color (de type Qcolor). Écrire le constructeur de la classe Trace, qui permettra de spécifier en paramètre les valeurs initiales de width et une color.
3e Etape: dessiner un tracé interactivement
Modifier la classe zone de dessin de telle sorte que l’on puisse interactivement y dessiner des tracés (l’événement press commence le segment, les évènements drag le poursuivent, et le release le termine).
Pour cela, il faudra définir une variable d’instance de type list qui stockera les tracés (qui seront des objets de type Trace) effectués à la souris. Il faudra également re-implémenter la méthode paintEvent de la classe CanvasDessin pour qu’elle parcours la liste des Traces et les dessine à l’aide d’un objet QPainterPath.
4e Etape: spécifier des attributs graphiques
Rajouter à la classe CanvasDessin des variables d’instance et des fonctions permettant de modifier la couleur et l’épaisseur des prochains traits qui seront dessinés.
5e Etape: choisir interactivement les attributs graphiques
Modifiez l’interface graphique (normalement créée par la classe MainWindow ou similaire, pas par la classe de la zone de dessin) de telle sorte que l’on puisse régler interactivement les paramètres définis à la question précédente. Pour cela, ajouter une toolbar à la classe Dessin.
Pour la couleur du tracé, ajoutez à la toolbar une QAction qui ouvre un QColorDialog et recupère la valeur choisie par l’utilisateur pour la définir comme couleur à utiliser pour les tracés à venir. Pour l’epaisseur du tracé, ajoutez à la toolbar un QSlider. Enfin, ajoutez également un QPushButton qui permette d’effacer complètement le contenu du canvas.
6e Etape: mettre à jour l’affichage du bouton qui permet de choisir la couleur
En l’état actuel, l’action qui permet de spécifier la couleur du prochain tracé ne suggère pas cette couleur à l’utilisateur. Il lui est donc impossible de savoir de quelle couleur sera le tracé avant de l’avoir effectivement dessiné sur le canvas.
Modifier votre programme pour que l’icone associée à l’action qui permet de choisir la couleur affiche la couleur courante. Pour cela, vous pourrez instancier un objet de type QPixMap et utiliser la methode fill() pour remplir cette QPixMap d’une couleur donnée. Vous pourrez ensuite instancier un objet QIcon avec cette instance de QPixMap pour faire en sorte que la toolbar suggère en permanence la couleur courante.
—
Le TP s’appuie sur celui d’Eric Lecolinet
Sujet
Note préliminaire : il est préférable de lire chaque "étape" en entier (en particulier les notes ou remarques) avant de la traiter.
Télécharger les sources nécessaires à ce TP
1e Etape: Démarrage
Ouvrir le fichier mainWindow.py avec votre éditeur de texte préféré
Créer une fonction main(args) qui sera appelé à partir du point d’entrée du programme (if __name__ == "__main__":)
Afficher une trace dans la fonction main, à l’aide de la fonction print, qui affichera les arguments passés en parametre au moment de l’execution du programme, et vérifier que le programme s’execute correctement dans la console (python3 mainwindow.py).
Q1: Que faut il ne pas oublier pour que le code s’execute? Que voyez vous au print?
2e Etape: Créer une classe MainWindow
La classe MainWindow dérivera de QMainWindow. Il faudra penser à rajouter from PyQt5.QtWidgets import *
Ajouter une constructeur qui ne fait pour le moment. Créer un instance de MainWindow dans la fonction main et afficher la fenêtre avec la méthode show(). Exécuter maintenant le code.
Q2.1: une erreur se produit à l’execution ? Que devez vous faire pour la corriger ?
Une fois l’erreur corrigée, le programme s’execute:
Q2.2: Pourquoi la fenêtre ne s’affiche pas? que faut il rajouter?
3e Etape: rajouter des widgets à MainWindow
Pour l’instant MainWindow ne fait pas grand chose de plus que QMainWindow. En s’inspirant du cours créer une barre de menu (à l’aide de la méthode menuBar() QMainWindow) avec un menu déroulant "Fichier" (à l’aide la méthode addMenu() de la classe QMenuBar) contenant des items pour activer les commandes "Open…", "Save…" ,"Copy", … et "Quit…". Créer également une barre d’outils permettant d’activer les mêmes commandes. Utiliser les QAction en spécifiant les accélérateurs clavier et bulles d’aides appropriés. Ajouter également les icons aux actions en utilisant les fichiers png fournis. .
Finalement, faire en sorte que la zone centrale de la MainWindow soit un QTextEdit à l’aide de la méthode setCentralWidget()
Option: Créer une barre de status en appelant la méthode statusBar() de la classe QMainWindow.
4e Etape: définir et connecter les slots
Déclarer et implémenter les slots openFile(), saveFile() et quitApp(). Pour l’instant ils se contenteront d’afficher un message (e.g. le nom du slot) sur la console. Connecter les slots aux actions correspondantes. Tester.
Q4: Comment connecter les actions aux slots ?
5e Etape: ouvrir une boîte de dialogue pour sélectionner un fichier
On va maintenant faire en sorte que les slots openFile() et saveFile() ouvrent des boîtes de dialogue permettant de récupérer un nom de fichier. C’est le rôle du widget QFileDialog, qui peut être utilisé de plusieurs manières. En s’inspirant du cours, choisir la technique la plus simple, tant pour ouvrir un "Open" qu’un "Save" Dialog. Recupérer le nom du fichier sélectionné et l’afficher sur la console.
6e Etape: ouvrir / sauver une page HTML
On va maintenant rajouter le code nécessaire dans le slot openFile() pour lire le fichier (texte ou HTML) sélectionné via la boîte de dialogue et faire apparaître son contenu dans le QTextEdit. Pour ce faire, on utilisera un QFile et un QTextStream pour lire le fichier. Pour simplifier tout le contenu du fichier sera lu en une seule fois dans le QTextStream. Ce contenu sera alors affecté au QTextEdit via sa méthode setHtml()(). Cette méthode suppose que le contenu est du HTML (pour du texte brut on utiliserait plutot setPlainText()).
Inversement, en suivant la même logique, faire en sorte que le slot saveFile() sauvegarde le contenu du QTextEdit dans le fichier indiqué par la boîte de dialogue.
Q6: Le code ne s’execute pas correctement car vous n’avez pas acces au textEdit depuis la méthode open ou save. Comment résoudre ce problème?
7e Etape: ouvrir une boîte de dialogue pour demander confirmation
Faire en sorte qu’appuyer sur le bouton Quit ait pour effet d’ouvrir un QMessageBox comportant des boutons "Yes" et "No" permettant de demander confirmation avant de sortir de l’application.
8e Etape: demander confirmation dans tous les cas
L’étape précédente comporte une faille. En effet, l’utilisateur peut aussi quitter le programme en cliquant sur le bouton présent sur la barre de la fenêtre (généralement une croix, ou un bouton rouge). Modifier la MainWindow de telle sorte que cette action ait le même effet que lorsqu’on clique sur le bouton Quit, c’est-à-dire ouvrir une boîte de dialogue pour demander confirmation. (Indication : il faudra redéfinir QWidget::closeEvent() et ignorer l’évenement).
—