I. La hiérarchie des classes de PyQt

PyQt se base complètement sur les concepts orientés objet, il est donc important de comprendre comme les classes sont liées les unes aux autres.

Presque toutes les classes GUI dérivent de leur classe abstraite, qui définit un comportement commun pour des widgets similaires. Ces classes abstraites (ou toute classe de widget) hérite de QWidget, la classe de base de tous les composants GUI affichables à l'écran. QWidget hérite elle-même de QObject, une classe qui n'a rien à voir avec les GUI, mais forme la classe de base de la majorité des classes de PyQt et aide à fournir certaines fonctionnalités du framework.

Le diagramme de hiérarchie suivant montre cela très clairement pour la classe QPushButton :

Image non disponible

La classe QPaintDevice aide à dessiner des choses à l'écran, elle est donc utilisée pour tout ce qui y est affichable.

Référence : QWidget.

II. Signaux et slots

Toutes les interactions entre un utilisateur et un widget peuvent s'effectuer avec le concept de signaux et de slots. Quand un utilisateur effectue une action sur un widget (un clic, un appui de touche ou le pointeur qui survole), un événement est généré. Ces événements de base peuvent être gérés avec le concept de signaux et de slots.

Très simplement, un signal est quelque chose qui est généré à chaque fois qu'un événement a lieu. Le signal est émis en interne par les classes de PyQt ou les vôtres. Pour gérer ces signaux, on utilise des slots. On donne à chaque signal un slot auquel il est connecté. Une fois connecté à un slot, à chaque fois qu'il est émis, le signal est capturé par le slot et exécute une fonction prédéfinie pour gérer l'événement.

En d'autres mots, un signal est quelque chose qui est émis à chaque événement, un slot est quelque chose qui reçoit ce signal avec ses arguments et exécute une routine.

Pour exemplifier ce concept, on va utiliser un QPushButton avec un QWidget pour la fenêtre. Le code suivant lance une application avec un bouton qui, lorsqu'il est cliqué, ferme l'application. On le fait en connectant le signal connected() du bouton au slot quit() de l'application.

Image non disponible
 
Sélectionnez
# Programme 02 - signaux
from PyQt4 import QtGui, QtCore
import sys
 
if __name__=='__main__':
 
    app = QtGui.QApplication(sys.argv)
 
    window = QtGui.QWidget()
    # On crée la fenêtre principale avec un QWidget.
    window.setWindowTitle("Signaux")
    # On en définit le titre : "Signaux".
    button = QtGui.QPushButton("Appuyez", window)
    # On crée un bouton enfant, avec "Appuyez" comme
	# texte, dans la fenêtre parente. En spécifiant un 
	# objet parent, ce nouveau widget y est automatiquement
	# ajouté. 
    button.resize(200, 40)
    # On redimensionne le bouton : (200, 40)
    button.connect(button, QtCore.SIGNAL("clicked()"),\
        app, QtCore.SLOT("quit()"))
	# On connecte le signal clicked du bouton au slot quit() 
	# de QApplication
    window.show()
    # On affiche la fenêtre. 
 
    app.exec_()

III. Connecter un signal à un slot

La connexion est effectuée pour que certains événements particuliers appellent leur slot respectif. Toutes les classes dérivant de QObject supportent ces connexions. La syntaxe de connect() est simple :

 
Sélectionnez
object.connect(Signal_Widget, Signal, Slot_Widget, Slot)

La fonction connect() effectue une connexion entre deux ensembles de paramètres :

  1. le signal :
    • il est constitué par l'objet qui émet le signal et le type de signal que l'on veut gérer (SIGNAL(QString)),
    • chaque widget émet un signal lors d'un événement, comme une interaction utilisateur ou une minuterie,
    • ces signaux ne doivent pas être confondus avec des signaux POSIX ou UNIX, c'est un concept très différent au niveau du système d'exploitation ;
  2. le slot :
    • l'objet receveur et le slot qu'il doit exécuter dès la réception du signal (SLOT(QString)),
    • chaque signal émis peut être géré par une fonction, un slot,
    • un slot est exécuté à chaque fois qu'un signal qui lui est connecté est émis par la classe émettrice.

QtCore.QObject est la classe qui fournit cette fonction de connexion. Puisque la classe en dérive, on peut l'utiliser depuis son instance. On peut donc remplacer la vingtième ligne par :

 
Sélectionnez
QtCore.QObject.connect(button, QtCore.SIGNAL("clicked()"),\
    app, QtCore.SLOT("quit()"))

Un widget PyQt dispose de nombreux signaux et slots. Par exemple, un QAbstractButton possède les signaux clicked(), pressed(), released() et toggled(), ainsi que les slots click(), setChecked(Bool) et toggle(). PyQt fournit la majorité des fonctionnalités GUI que l'on peut imaginer.

Références : QObject, signaux et slots, QPushButton et QApplication.

IV. Dispositions (layouts)

En tentant de redimensionner l'exemple précédent, on a l'impression que le bouton est collé simplement dans la fenêtre. Il ne change pas de position, le contenu de la fenêtre ne change pas en fonction de l'écran de l'utilisateur. Les dispositions peuvent aider à ce niveau - et pour bien d'autres choses aussi.

Comme le mot l'indique, les dispositions aident à disposer les widgets sur une fenêtre de manière propre. QLayout est la forme la plus basique de disposition et possède quatre sous-classes principales, de type Box, Grid, Form et Stacked.

Image non disponible

Utiliser une disposition est simple avec PyQt. On doit juste attacher la disposition voulue à une fenêtre ou un widget et on y ajoute des composants, qui seront automatiquement disposés. Le prochain exemple les utilise.

Références : QLayout, classes de disposition

V. Passer des arguments aux slots

Les signaux et slots peuvent aussi transmettre des paramètres. Ceci est utile quand il y a une entrée ou quand des données doivent être envoyées automatiquement à chaque fois qu'un événement se produit.

Prenons un autre exemple, avec des paramètres. On va y connecter un éditeur sur une ligne à un label. Quand on entre du texte dans l'éditeur, des événements sont automatiquement générés et un slot du label va être exécuté pour changer le texte affiché pour celui qui vient d'être tapé. Pour ceci, on utilise un QLabel pour le label et un QLineEdit pour l'éditeur.

Quelles sont les étapes à suivre pour obtenir le code ci-dessous ?

  1. Importer les classes nécessaires.
  2. Créer une instance de QApplication.
  3. Créer une fenêtre (QWidget).
  4. Y attacher un QHBoxLayout.
  5. Créer un widget d'entrée et l'ajouter à la disposition.
  6. Créer un widget d'affichage et l'ajouter à la disposition.
  7. Connecter le signal textChanged(QString) de l'éditeur au slot setText(QString) du label.
  8. Afficher la fenêtre.
  9. Lancer la boucle principale.
Image non disponible
 
Sélectionnez
# Programme 03 - signaux et slots avec arguments
from PyQt4.QtCore import SIGNAL, SLOT
from PyQt4.QtGui import QApplication, QWidget,\
    QLineEdit, QLabel, QHBoxLayout
import sys
 
if __name__=='__main__':
    App = QApplication(sys.argv)
    Window = QWidget()
    Window.setWindowTitle("Arguments")
    Layout = QHBoxLayout(Window)
    Line = QLineEdit()
    Layout.addWidget(Line)
    Label = QLabel()
    Layout.addWidget(Label)
    Line.connect(Line, SIGNAL("textChanged(QString)"),\
        Label, SLOT("setText(QString)"))
    Window.show()
    App.exec_()

Quand l'utilisateur entre ou modifie le texte du widget d'édition, un signal textChanged(QString) est émis avec la nouvelle chaîne en paramètre. On l'intercepte et on y connecte le slot (et fonction) setText(QString) du label. Ainsi, la chaîne modifiée voyage de l'éditeur au label et est affichée, le tout pour chaque changement, fût-ce un caractère.

La disposition possède une fonction intéressante, addWidget(), qui prend un widget et l'ajoute à la géométrie de la disposition. Cette méthode prend aussi un paramètre de facteur d'extension et un autre d'alignement. La disposition ajuste automatiquement les tailles des widgets, en se basant sur la taille de l'entrée dans cet exemple. Si on augmente la taille de la fenêtre, la disposition va s'adapter de même.

Si le concept de signaux et de slots semble encore confus, le graphique ci-dessous va l'éclairer un peu plus.

Image non disponible

Références : QLineEdit, QLabel, QHBoxLayout.

VI. Exercice

Utilisez un QPushButton dans une fenêtre QWidget et changez son texte à chaque fois qu'il est cliqué. Aussi, ajoutez une disposition pour la fenêtre avec le bouton.

Chaque fois que le bouton est cliqué, son texte doit changer. Par exemple, si le texte du bouton est Hello!, après un clic il devient PyQt et un dernier le fait revenir à Hello!. Assurez-vous qu'il s'agrandit en même temps que la fenêtre.

VII. Notes

On peut passer une fonction directement à connect() pour servir de slot. Elle ne doit pas forcément être un slot fourni par PyQt.

On peut aussi également écrire ses propres signaux et slots pour certains événements.

Tous les composants GUI n'ont pas une classe abstraite. Elle n'existe qu'en cas de widget ou d'item qui peut être étendu de plus d'une manière.

VIII. Remerciements

Merci à Harsh pour l'autorisation de traduire son article, PyQt – Signals, Slots and Layouts Tutorial !

Merci à Jean-Philippe André et à Claude Leloup pour leur relecture orthographique !