version 1.1.0, dernière mise à jour le 17 novembre 2022.
Python est un langage qui peut être utilisé pour réaliser des programmes évolués. Pour cela, il faut concevoir des interfaces utilisateur. Il existe plusieurs bibliothèques pour ce faire ; certaines ont été portées sur Python.
Tkinter est installé par défaut avec Python. C'est une bibliothèque déjà ancienne, provenant de Tk
PyGtK permet de produire des interfaces graphiques utilisant la bibliothèque GtK+ 2. Elle a été remplacée par PyGObject, qui utilise la bibliothèque GtK 3
PyQt et PySlide recourent elles à Qt, qui est une bibliothèque disponible sous deux licences différentes en fonction de leur utilisation pour la réalisation d'une application commerciale ou pas.
Nous allons aborder dans ce cours PyQt, pour laquelle il est plus facile de trouver un mode d'installation simple sous Windows. À la date de révision de ce cours, la bibliothèque est en version 6.2.2. Au-delà d'une bibliothèque pour la création d'une interface graphique, PyQt embarque aussi de nombreux composants, sous la forme de modules spécialisés. Nous n'utiliserons dans ce cours d'initiation que les modules QtCore et QtGui, parmi la vingtaine disponible.
PyQt5 permet de se simplifier la vie, et de coder simplement une interface utilisateur. Analysons l'exemple suivant…
import sys
from PyQt5.QtWidgets import QApplication, QWidget
monApp=QApplication(sys.argv)
w=QWidget()
w.setGeometry(200,300,400, 500)
w.setWindowTitle("Titre de fenêtre")
w.show()
sys.exit(monApp.exec_())
Dans cet exemple, on commence par importer les classes QApplication et QWidget du module PyQt5.QtWidgets. On définit ensuite une nouvelle application (monApp
), puis un « widget », que l'on place à 200 pixels du bord gauche de l'écran, à 300 pixels à partir du haut, avec une largeur de 400 pixels, et une hauteur de 500 pixels. On lui affecte ensuite un titre, puis on le montre. L'instruction 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.
Si l'on veut concevoir de manière un peu plus orientée objet (ce qui permet de réutiliser un widget), on écrira plutôt…
import sys
from PyQt5.QtWidgets import QApplication, QWidget
class
Fenetre(QWidget) :
def
__init__(self) :
super().__init__()
self.lanceUI()
def
lanceUI(self) :
self.setGeometry(200,300,400, 500)
self.setWindowTitle("Titre de fenêtre")
self.show()
monApp=QApplication(sys.argv)
w=Fenetre()
sys.exit(monApp.exec_())
La méthode super()
permet de remonter à la classe dont on hérite.
Créer un widget n'est pas créer une application, mais un composant d'une application. Les widgets sont positionnés à l'intérieur d'une fenêtre principale. En voici un exemple
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) #On définit une action à exécuter. Le & signifie qu'à l'affichage le E sera souligné et définit le raccourci clavier Alt-e
exitAction.setShortcut('Ctrl+Q') #Définition d'un raccourci clavier>
exitAction.setStatusTip("Quitter l'application") #Ce qui est affiché dans la barre de statut au survol par la souris
exitAction.triggered.connect(qApp.exit) #Quand l'action est activée (« triggered »), cela lance l'événement prédéfini qApp.exit qui quitte l'application
menu=self.menuBar() #La méthode prédéfinie menuBar permet de créer une barre de menu en haut de la fenêtre
fichierMenu=menu.addMenu("&Fichier") #Définition d'une entrée de menu, qui affiche le texte Fichier avec comme raccourci clavier At-F
fichierMenu.addAction(exitAction) #Rattachement de l'action définie plus haut sous cette entrée de menu
self.barreOutils=self.addToolBar('barre1') #Création d'une barre d'outils nommée barre1. Ce nom peut servir éventuellement ailleurs dans l'application pour désigner la barre
self.barreOutils.addAction(exitAction) #Rattachement de l'action définie plus haut dans cette barre d'outils
self.setGeometry(300,300,500,250)
self.setWindowTitle('Fenêtre principale')
self.statusBar().showMessage("Barre de statut") #Texte affiché dans la barre de statut au chargement de l'application
self.show()
monApp=QApplication(sys.argv)
fenetre=Principale()
sys.exit(monApp.exec_())
La classe QAction
permet de définir des actions, que l'on peut attacher soit à une barre de menu (activée grâce à la méthode menuBar
), soit à une barre d'outils (méthode addToolBar
).
La définition 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
Les widgets sont les composants de base de l'application. En voici quelques-uns…
import sys from PyQt5.QtWidgets import * class Principale(QMainWindow): def __init__(self): super().__init__() self.setUI() def setUI(self): zoneTexte=QTextEdit() #Zone de saisie de texte btnOK=QPushButton("OK", self) #Un bouton zoneLigne=QLineEdit() #Un champ de saisie de texte label=QLabel("Champ texte") #Une étiquette case=QCheckBox("Case", self) #Une case à cocher combo=QComboBox(self) #Un menu de sélection 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() #Mise en page 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.setStyleSheet("background-color: gray") self.show() monApp=QApplication(sys.argv) fenetre=Principale() sys.exit(monApp.exec_())
La plupart des noms sont explicites ; néanmoins voici quelques détails sur certaines méthodes :
La méthode resize
appliquée à un objet de type QPushButton
permet de le redimensionner
La méthode sizeHint
laisse le soin à Qt de déterminer la taille optimale du bouton
Nous allons revenir sur la mise en page, limitée ici.
QradioButton()
permet de créer des cases à cocher, mais si l'on souhaite obtenir un comportement semblable aux « boutons radio » dans une interface Web, à savoir des cases mutuellement exclusives (quand on en coche une, les autres sont automatiquement décochées), il faut les regrouper avec QButtonGroup
. Par exemple :
box = QHBoxLayout()
groupeCouleurs = QButtonGroup(self)
caseRouge = QRadioButton("Rouge")
caseVert = QRadioButton("Vert")
caseBleu = QRadioButton("Bleu")
groupeCouleurs.addButton(caseRouge)
groupeCouleurs.addButton(caseVert)
groupeCouleurs.addButton(caseBleu)
box.addWidget(caseRouge)
box.addWidget(caseVert)
box.addWidget(caseBleu)
On peut changer l'apparence de n'importe quel widget, y compris la fenêtre principale, avec la méthode setStyleSheet
, prenant en paramètre des propriétés CSS
. Par exemple…
bouton=QPushButton("Un Bouton", self)
bouton.setStyleSheet("color: blue; background-color: gray; font-style: italic; font-variant: small-caps; font-family: serif")
… permet d'obtenir un bouton où le texte « Un Bouton » est écrit en bleu sur fond gris, en italique, petites majuscules et en police de caractères avec empattement (« serif »).
Les classes QHBoxLayout
et QVBoxLayout
permettent d'aligner des contenus horizontalement ou verticalement. Combinés, cela permet une grande souplesse. Supposons que l'on veuille par exemple ajouter trois 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.
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_())
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)
Une personne utilisant le programme va pouvoir agir via l'interface de l'application. Cette dernière doit pouvoir permettre de capter et traiter les actions de l'utilisateur, par l'intermédiaire d'événements, mais aussi de modifier son état ou son affichage en réponse aux actions de l'utilisateur.
Il existe une multitude d'événements susceptibles de changer l'état d'une application ou des données qu'elle traite. Ces événements peuvent résulter d'une action de l'utilisateur (comme un clic), ou bien de l'activation d'une connexion Internet, du signal envoyé par une horloge, etc. Dans tous les cas, trois éléments sont concernés :
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 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) #Qt.Horizontal indique que le slider doit être placé horizontalement 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.
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): print("Message envoyé") 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 tout d'abord un message dans la console, puis des informations diverses dans une boîte…
Afficher en permanence les mêmes données ou les mêmes calculs ne serait pas très utile ; un minimum d'interactivité est requis pour créer une application.
Si l'on souhaite accéder à un widget depuis une fonction (par exemple un gestionnaire d'événements), il faut expliciter de quel objet il relève en le préfixant dans sa déclaration par self
. Par exemple…
import sys from PyQt5.QtWidgets import * class Principale (QMainWindow) : def __init__(self) : super().__init__() self.setUI() def afficheMessage(self, event): print(self.texte1.text()) def setUI(self): self.texte1=QLineEdit() bouton=QPushButton("OK", self) bouton.clicked.connect(self.afficheMessage) hbox=QHBoxLayout() hbox.addStretch() hbox.addWidget(self.texte1) hbox.addStretch() hbox.addWidget(bouton) hbox.addStretch() w=QWidget() w.setLayout(hbox) self.setCentralWidget(w) self.setGeometry(300,300,400,200) self.setWindowTitle('Affichage simple') self.show() monApp=QApplication(sys.argv) fenetre=Principale() sys.exit(monApp.exec_())
(voir le code source). La méthode text()
permet de lire la valeur saisie dans le champ. De manière similaire :
checkState()
et setCheckState()
permettent respectivement de vérifier si une case à cocher (QCheckBox
) a été cochée, et de changer son état (cochée ↔ décochée)
setText("Nouvelle valeur")
permet de changer la valeur d'une étiquette (QLabel
)
currentText()
renvoie la valeur actuellement sélectionnée dans un menu de sélection (QComboBox
)
La méthode checkedButton()
de la classe QGroupButton permet de retrouver le bouton radio cliqué. Pour reprendre un des exemples précécents, pour afficher dans la console la valeur du bouton radio sélectionné dans le groupe groupeCouleurs
on fera…
def afficheValeur() :
print(self.groupeCouleurs.checkedButton().text())
Cette création est mise à disposition par Gilles Chagnon sous un contrat Creative Commons.