Créer des interfaces graphiques avec Python et Qt

Installation sur Windows

Avant de pouvoir utiliser PyQt il y'a un certain nombre d'installations à  faire. La section suivante va vous guider à travers ce processus :

 

Sur Windows, PyQt s'installe comme n'importe quelle application ou librairie. Si vous avez installé python (et son gestionnaire de paquets pip) faîtes :

 

pip3 install pyqt5

Installation sur MacOS

Sur MacOS, il va vous falloir installer Homebrew. Homebrew est un gestionnaire de paquets qui va vous simplifier l'installation d'applications en ligne de commande. Ouvrez l'invite de commandes et tapez :

 

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

Une fois que Homebrew est installé vous pouvez télécharger python3 et pyqt en exécutant les commandes:

 

brew install python3
brew install pyqt5 --with-python-3

Installation sur Linux (Ubuntu)

Sur Debian ou Ubuntu Linux (et leurs dérivés), installez pyqt en utilisant le gestionnaire de paquets apt. PyQt est disponible pour la plupart des distributions. La commande suivante marche donc pour Ubuntu Linux mais, gestionnaire de paquets mis à part, elle devrait être similaire dans les autres distributions :

 

apt-get install python3-pyqt5

Prêts ? Partez !

Une fois que vous avez tout installé, vous serez capables d'utiliser python3, python et d'importer pyqt en faisant import pyqt5.

Ma première application

Le coeur de votre application réside dans la classe QApplication. Toute application a besoin d'un objet de cette classe pour fonctionner. C'est dans cet objet que réside la boucle d'évènements (event loop) du programme. La boucle d'évènements gère toutes les interactions de votre interface.

Créer votre premier programme

Ouvrez un nouveau fichier, donnez lui le nom que vous voulez et tapez les instructions suivantes:

from PyQt5.QtWidgets import QApplication, QMainWindow

# On crée une instance d'application
app = QApplication([])

# On commence la boucle d'évènements, 
# c-à-d on démarre l'application
app.exec()

# Que se passe t-il? Rien.

Ma première application

Votre application tourne bel et bien en arrière plan... Mais elle n'a aucune fenêtre ! La boucle d'évènements tourne donc à vide dans l'attente d'évènements qui n'auront jamais lieu.

 

Appuyez sur Ctrl-c pour couper l'application ou fermez votre console python (ou l'invite de commandes). Ouvrez-la de nouveau pour repartir d'un bon pied. Il est temps de modifier le code précédent...

Ma première application (2)

Toute application a besoin d'au moins une fenêtre. En langage Qt on les appelle les QMainWindow. Une application peut même avoir plusieurs fenêtres. L'application s'arrête une fois que la dernière fenêtre est fermée.

from PyQt5.QtWidgets import QApplication, QMainWindow

# On passe une liste vide en argument
app = QApplication([])

# On crée notre fenêtre !
window = QMainWindow()

window.show() # IMPORTANT fenêtres invisibles par défaut

# On initialise l'application.
app.exec()

QMaindow

Menu Bar: La barre de menus.

Toolbars: La barre d'outils.

Dock Widgets: Conteneurs qui entourent l'élément principal.

Central Widget: La widget centrale.

Status Bar: La barre d'état.

Customiser la fenêtre

Pour personnaliser la fenêtre la meilleure approche consiste à créer une classe fille de QMainWindow.

 

On importe ensuite les attributs et comportements de la classe mère grâce à __init__. Définissons notre propre classe basée sur QMainWindow. Appelons là Fenetre.

Customiser la fenêtre (2)

Le code suivant résume cette opération

from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel
from PyQt5.QtCore import Qt

# La classe mère est QMainWindow. On la customize
class Fenetre(QMainWindow):
    def __init__(self):
        super().__init__()
        
        # On définit un titre à la fenêtre
        self.setWindowTitle("Première fenêtre")
        self.label = QLabel("INCROYABLE!!!")

        # Aligner le texte du label au centre
        # setAlignment est une méthode de la classe
        # widget. Pour en savoir plus: http://doc.qt.io/qt-5/qt.html
        self.label.setAlignment(Qt.AlignCenter)

        # Toute fenêtre vient avec son widget central.
        # Ce code vise à mettre notre label au centre de "Fenetre"
        self.setCentralWidget(self.label)

app = QApplication([])
window = Fenetre()
window.show()
app.exec()

Les signaux: introduction

Toute interaction de l'utilisateur envers l'interface graphique provoque un évènement. Il y'a plusieurs sortes d'évènements, chacun représente une interaction (avec la souris, le clavier, etc...).

 

Une fois qu'un évènement a eu lieu, la boucle d'évènements l'attache à une fonction particulière qui va définir le comportement du programme. On appelle cette fonction particulière un slot.

Les signaux: introduction

Les signaux de QMainWindow

Dans la page de documentation de la classe QWidget, un évènement concerne le changement de titre de la fenêtre il s'appelle: windowTitleChanged

Le code suivant attache l'évènement (windowTitleChanged: le titre de la fenêtre a changé) à une fonction qui affiche le nouveau titre dans la console; elle s'appelle: onWindowTitleChange.

Les signaux (1)

Pour attacher un évènement a une fonction on utilise la fonction connect:

from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel
from PyQt5.QtCore import Qt

class Fenetre(QMainWindow):
    def __init__(self):
        super().__init__()

        self.windowTitleChanged.connect(self.onWindowTitleChange)

        self.setWindowTitle("Mon incroyable App!")

        self.label = QLabel("C'EST GENIAL!!!")

        self.label.setAlignment(Qt.AlignCenter)

        self.setCentralWidget(self.label)

    def onWindowTitleChange(self, s):
	    print(s)
	

app = QApplication([])
window = Fenetre()
window.show()
app.exec()

Les signaux (2)

self.windowTitleChanged.connect(self.onWindowTitleChange)

Cette ligne relie le signal windowTitleChanged au slot onWindowTitleChange. Le code du slot est lui aussi détaillé dans la classe Fenêtre mais il est à l'extérieur de la fonction __init__

Les signaux (3)

from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel
from PyQt5.QtCore import Qt

class Fenetre(QMainWindow):
    def __init__(self):
        super().__init__()

        self.windowTitleChanged.connect(self.onWindowTitleChange)

        self.setWindowTitle("Mon incroyable App!")

        self.label = QLabel("C'EST GENIAL!!!")

        self.label.setAlignment(Qt.AlignCenter)

        self.setCentralWidget(self.label)

    def onWindowTitleChange(self, s):
            print("Le titre de la fenêtre a changé! ")
            print(s)
	

app = QApplication([])
window = Fenetre()
window.show()
app.exec()

Qwidget

Classe mère des composants graphiques de PyQt: champs de textes, barres de progressions, boutons, labels, fenêtres, etc...

setFixedWidth/setFixedHeight: Définir la largeur/la hauteur fixe

setMaximumSize/setMinimumSize: Définir la taille max/min

setLayout: Définir un layout

Plus d'infos sur: https://doc.qt.io/qt-5/qwidget.html

Attributs, méthodes notables

Les widgets

Les widgets sont les composants de PyQt avec lesquels un utilisateur peut interagir. Une interface utilisateur c'est plusieurs widgets assemblés à l'intérieur d'une fenêtre.

Qt vient avec beaucoup de widgets prédéfinis mais vous avez la possiblité de créer vos propres widgets.

La liste des widgets

Tous les éléments graphiques sont donc des widgets. En Qt ils sont organisés en différentes classes et sous-classes qui héritent les unes les autres.

 

Pour avoir une liste complète n'hésitez pas à consulter la documentation.

La liste des widgets

Le code de la page suivante affiche une liste de widgets...

Arborescence de classe des widgets

from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QLabel, QPushButton, QDial, QSlider, QSpinBox, QLineEdit


class Fenetre(QMainWindow):
	def __init__(self):
		super().__init__()
		self.setWindowTitle("Quelques widgets")
		self.layout = QVBoxLayout() # Crée un quadrillage vertical pour placer nos widgets
		
		# Liste de widgets prédéfinis
		self.widgets = [QLabel, QPushButton, QDial, QSlider, QSpinBox, QLineEdit]
		
		# On peut même boucler sur les widgets
		for w in self.widgets:
			self.layout.addWidget(w()) # Par contre ne pas oublier les parenthèses pour les instancier...
			
		self.widget = QWidget() # Widget Neutre
		self.widget.setLayout(self.layout)	# On lui met un calque (layout)
		
		self.setCentralWidget(self.widget)	# On définit ce calque en calque central
		
app = QApplication([])
window = Fenetre()
window.show() # IMPORTANT!
app.exec()

QLabel

Gère les composants textes. Cette classe hérite de Qwidget.

font: Instancier une police de caractères.

setFont: Définir une police instanciée.

setText: Définir un texte.

setAlignment: Aligner le texte (en haut, en bas, à droite, au centre).

Plus d'infos sur: https://doc.qt.io/archives/qt-4.8/qlabel.html.

Attributs, méthodes notables

QLabel

La widget QLabel représente un simple texte. On peut  définir le texte lors de la création de la widget ou plus tard à l'aide de la méthode setText.

widget = QLabel("Hello")
widget = QLabel("1") # Le label est crée avec le texte 1
widget.setText("2") # Bon finalement ça sera le texte 2

QLabel (2)

On peut même définir une police et un alignement pour le texte:

from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel
from PyQt5.QtCore import Qt


class Fenetre(QMainWindow):
	def __init__(self):
		super().__init__()
		self.setWindowTitle("Quelques widgets")
		
		self.label = QLabel(" Hello World! ")
		
		self.font = self.label.font()  # instancier une police
		self.font.setPointSize(30)  # lui donner une taille et...
		
		self.setFont(self.font)  # ...l'attacher au label  
		self.label.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
		
		self.setCentralWidget(self.label)
		
app = QApplication([])
window = Fenetre()
window.show() # IMPORTANT!
app.exec()

QLabel (3)

Pour aligner le texte (par exemple au centre) on utilise:

  • Qt.AlignLeft: Alignement à gauche
  • Qt.AlignRight: Alignement à droite
  • Qt.AlignJustify: Justifie le texte sur tout l'espace disponible
  • Qt.AlignTop: Alignement tout en haut
  • Qt.AlignBottom: Alignement tout en bas
# On peut cumuler les paramètres non contradictoires
# Par exemple alignement du texte tout en haut à gauche

widget.setAlignment(Qt.AlignLeft | Qt.AlignTop)
widget.setAlignment(Qt.AlignCenter)

Une image grâce à QLabel

On peut aussi afficher une image grâce à un QLabel. Pour ça, il faut faire appel à setPixmap. En paramètre on lui passe un objet QPixmap avec comme paramètre le nom de l'image à créer:

widget.setPixMap(QPixMap('no.jpg'))

Si vous voulez adapter l'image à la fenêtre utilisez la méthode setScaledContents avec le paramètre à True sur le QLabel:

widget.setScaledContents(True)

Afficher une image

Le code suivant permet d'afficher l'image no.png

from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel
from PyQt5.QtGui import QPixmap


class Fenetre(QMainWindow):
	def __init__(self):
		super().__init__()
		self.setWindowTitle("Paint")
		
		self.label = QLabel('Hello')
		self.label.setPixmap(QPixmap('no.png'))
		self.label.setScaledContents(True)
		
		self.setCentralWidget(self.label)
		
app = QApplication([])
window = Fenetre()
window.show() # IMPORTANT!
app.exec()

Qcheckbox

Widget qui désigne les cases à cocher

setCheckState: Coche la case ou là décoche.

Attributs, méthodes notables

 

Plus d'infos sur: https://doc.qt.io/archives/qt-4.8/qcheckbox.html

Signaux

stateChanged: Signal envoyé quand la case change de valeur.

QCheckBox

Le widget QcheckBox traite des cases à cocher par l'utilisateur. Pour savoir si une case est cochée ou non on utilise un signal stateChanged et un slot show_state :

from PyQt5.QtWidgets import QApplication, QMainWindow, QCheckBox
from PyQt5.QtCore import Qt

class Fenetre(QMainWindow):
	def __init__(self):
		super().__init__()
		self.setWindowTitle("Les cases à cocher")
		
		self.checkbox = QCheckBox()
		# On coche la case
		self.checkbox.setCheckState(Qt.Checked)
		
		self.checkbox.stateChanged.connect(self.show_state)
		
		self.setCentralWidget(self.checkbox)

	def show_state(self, s):
		print(s == Qt.Checked)  # True si c'est coché
		print(s) # 0 Unchecked, 1 Partially checked, 2 Checked
		
app = QApplication([])
window = Fenetre()
window.show() # IMPORTANT!
app.exec()

QCheckBox (2)

Quand on utilise setCheckState, il existe d'autres valeurs pour modifier l'état de la checkbox :

widget.setCheckState(Qt.Unchecked)  # La case sera décochée
widget.setCheckState(Qt.Checked)  # La case est cochée

Dès que l'utilisateur coche la case, la fonction show_state est appelée. Nous l'avons codée pour qu'elle affiche l'état de la checkbox dans la console.

QcomboBox

Widget qui désigne les listes déroulantes d'options

addItems: Ajoute des nouvelles options.

Attributs, méthodes notables

Plus d'infos sur: https://doc.qt.io/qt-5/qcombobox.html

Signaux

currentIndexChanged: Signal envoyé quand l'utilisateur change l'option.

QComboBox

La QComboBox est une liste déroulante. Elle correspond à la simulation de formulaires multi-options.
On ajoute des éléments grâce addItems.

 

Cette widget est liée au signal currentIndexChanged (l'utilisateur passe d'une option à l'autre). Dans la page suivante on associe ce signal à deux slots: index_changed et text_changed.

widget = QComboBox()
widget.addItems(["option 1", "option 2", "option 3"])
from PyQt5.QtWidgets import QApplication, QMainWindow, QComboBox

class Fenetre(QMainWindow):
	def __init__(self):
		super().__init__()
		self.setWindowTitle("Les Combo Box")
		
		self.combobox = QComboBox()
		self.combobox.addItems(["Option 1", "Option 2", "Option 3"])

		# Ce signal transmet l'index de l'option (pas son texte)
		self.combobox.currentIndexChanged.connect( self.index_changed )

		# Le même signal (un peu modifié) pour faire passer le texte
		self.combobox.currentIndexChanged[str].connect( self.text_changed )

		self.setCentralWidget(self.combobox)

	def index_changed(self, i): # i est un int
		print(i)  # Affiche l'index de l'élément selectionné

	def text_changed(self, s): # s est une string
		print(s)  # Affiche le texte de l'élément selectionné
		
app = QApplication([])
window = Fenetre()
window.show() # IMPORTANT!
app.exec()

Autre exemple

# coding: utf-8

from PyQt5.QtWidgets import QApplication, QMainWindow, QComboBox

class FenetrePrincipale(QMainWindow):
  def __init__(self):
    super().__init__()

    self.setWindowTitle('Un exemple de liste déroulante')
    self.show()

    # Une ComboBox permet de réaliser une liste déroulante
    self.listeDeroulante = QComboBox(self)
    self.listeDeroulante.addItem('Item 1')
    self.listeDeroulante.addItem('Item 2')
    self.listeDeroulante.addItem('Item 3')
    self.listeDeroulante.currentIndexChanged.connect(self.methodePourGererLaListe)

    self.setCentralWidget(self.listeDeroulante)

  def methodePourGererLaListe(self, entierRecuParLeSignal):
    print(self.sender().currentText())
    print('L\'item d\'indice {0} a été sélectionné.'.format(entierRecuParLeSignal))
    
app = QApplication([])
maFenetrePrincipale = FenetrePrincipale()
app.exec()

QLineEdit

Widget qui désigne les champs de texte

setMaxLength: Définit la longueur maximale.

setPlaceholderText: Définit un texte par défaut.

Attributs, méthodes notables

 

Plus d'infos sur: https://doc.qt.io/qt-5/qlineedit.html

Signaux

textChanged: Signal envoyé quand l'utilisateur change le texte

QLineEdit

Cette widget représente un simple champ de texte dans lequel l'utilisateur pourra entrer une valeur. Voici ses options :

widget = QLineEdit()  # Création du Widget

widget.setMaxLength(10)  # Longueur maximale
widget.setPlaceholderText("Enter your text")  # Texte par défaut

# Décommentez cette ligne pour que le texte soit en lecture seule
#widget.setReadOnly(True)

Dans la page suivante on connecte ce widget à un slot à travers le signal textChanged.

from PyQt5.QtWidgets import QApplication, QMainWindow, QLineEdit
from PyQt5.QtCore import Qt

class Fenetre(QMainWindow):
	def __init__(self):
		super().__init__()
		self.setWindowTitle("Champ de texte")
		
		self.lineEdit = QLineEdit()
		self.lineEdit.setMaxLength(10)
		self.lineEdit.setPlaceholderText("Entrez votre texte")
		
		self.lineEdit.textChanged.connect(self.text_changed)
		
		self.setCentralWidget(self.lineEdit)
		
	def text_changed(self, s):
		print("Le texte a changé !")
		print(s)
		
app = QApplication([])
window = Fenetre()
window.show() # IMPORTANT!
app.exec()

QSpinBox

Widget qui désigne un champ numérique cliquable.

setMinimum: Définit la valeur minimale.

setMaximum: Définit la valeur max.

Attributs, méthodes notables

Plus d'infos sur: https://doc.qt.io/qt-5/qspinbox.html

Signaux

valueChanged: Signal envoyé quand la valeur change.

QSpinBox

Cette widget représente un simple champ numérique dont la valeur peut être augmentée ou baissée par le biais d'un clic sur des flèches.

widget = QSpinBox()

widget.setMinimum(-10)  # Valeur minimale
widget.setMaximum(3)  # Valeur maximale

Dans la page suivante on connecte ce widget à un slot à travers le signal valueChanged.

QSpinBox

from PyQt5.QtWidgets import QApplication, QMainWindow, QSpinBox

class Fenetre(QMainWindow):
	def __init__(self):
		super().__init__()
		self.setWindowTitle("Champ de texte")
		
		self.spinbox = QSpinBox()  # QDoubleSpinBox pour les réels...
		self.spinbox.setMinimum(-10)
		self.spinbox.setMaximum(3)
		self.spinbox.setSingleStep(3) # Le pas pour augmenter ou baisser
		
		self.spinbox.valueChanged.connect(self.value_changed)
		
		self.setCentralWidget(self.spinbox)
		
	def value_changed(self, i):
		print(i)
		
app = QApplication([])
window = Fenetre()
window.show() # IMPORTANT!
app.exec()

QSlider

Widget qui représente un curseur.

setMinimum: Définit la valeur minimale.

setMaximum: Définit la valeur max.

Attributs, méthodes notables

Plus d'infos sur: https://doc.qt.io/qt-5/qspinbox.html

Signaux

valueChanged: Signal envoyé quand la valeur change.

QSlider

Ce widget représente un curseur. Plus on avance vers le haut (ou la droite) et plus la valeur est élevée :

widget = QSlider()

widget.setMinimum(-20)  # Valeur minimale
widget.setMaximum(20)  # Valeur maximale
widget.setSingleStep(2)  # Le pas pour augmenter ou baisser

Dans la page suivante on connecte ce widget à deux slots à travers les signaux sliderMoved (l'utilisateur a bougé le curseur) et sliderPressed (l'utilisateur a pressé le curseur).

from PyQt5.QtWidgets import QApplication, QMainWindow, QSlider
from PyQt5.QtCore import Qt

class Fenetre(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Slider")
		
        self.slider = QSlider()
        # self.slider = QSlider(Qt.Horizontal)
        # Décommentez ligne du haut pour slider horizontal

        self.slider.setMinimum(-20)
        self.slider.setMaximum(20)
                
        # Le pas pour le changement de valeur, ici de 2 en 2
        self.slider.setSingleStep(2) 
        self.slider.sliderMoved.connect(self.slider_position)
        self.setCentralWidget(self.slider)
		
    def slider_position(self, p):
        print("position: ", p)
		
app = QApplication([])
window = Fenetre()
window.show() # IMPORTANT!
app.exec()

Autre exemple

from PyQt5.QtWidgets import QApplication, QMainWindow, QHBoxLayout, QSlider, QWidget

class FenetrePrincipale(QMainWindow):
  def __init__(self):
    super().__init__()

    self.setWindowTitle('Un exemple de curseurs')
    self.show()

    self.widgetPourContenirLesCurseurs = QWidget(self)
    self.disposition = QHBoxLayout()
    self.widgetPourContenirLesCurseurs.setLayout(self.disposition)

    self.curseur1 = QSlider()
    self.curseur2 = QSlider()

    self.curseur1.valueChanged.connect(self.methodePourGererUnCurseur)
    self.curseur2.valueChanged.connect(self.methodePourGererUnCurseur)

    self.disposition.addWidget(self.curseur1)
    self.disposition.addWidget(self.curseur2)

    self.setCentralWidget(self.widgetPourContenirLesCurseurs)

  def methodePourGererUnCurseur(self, entierRecuParLeSignal):
    print(self.sender().__dir__())
    print('Le curseur a envoyé {0}.'.format(entierRecuParLeSignal))
    
app = QApplication([])
maFenetrePrincipale = FenetrePrincipale()
app.exec()

QPushButton

Widget qui désigne un bouton.

Plus d'infos sur: https://doc.qt.io/qt-5/qpushbutton.html

Signaux

Clicked: Signal envoyé quand l'utilisateur clique sur le bouton

QPushButton

Cette widget représente un bouton. C'est typiquement le genre d'éléments dont on attend une interaction...

# Création d'un bouton
widget = QPushButton('Clic !')

Du coup la connexion à un slot vient évidemment à l'esprit. Le signal choisi sera tout simplement clicked comme l'illustre le code de la page suivante.

QPushButton

from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton

class Fenetre(QMainWindow):
	def __init__(self):
		super().__init__()
		self.setWindowTitle("Boutons")
		
		self.button = QPushButton('Clic !')
		self.button.clicked.connect(self.on_click)
		self.setCentralWidget(self.button)
		
	def on_click(self):
		print("clic!")
		
app = QApplication([])
window = Fenetre()
window.show() # IMPORTANT!
app.exec()

QPushButton et QMessageBox

Plutôt que d'afficher les clics dans la console on veut voir s'afficher une boîte de dialogue. Pour cela on fait appel au widget QMessageBox:

# 1er paramètre: Type, 2ème: Titre, 3ème: Message
self.popup = QMessageBox(QMessageBox.Information,'Message','Bouton cliqué')

# Montrer la boîte de dialogue
self.popup.show()

D'autres types de boîtes de dialogues sont possibles: QMessageBox.Question, QMessageBox.Warning, QMessageBox.Critical.

QPushButton et QMessageBox (2)

from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QMessageBox

class Fenetre(QMainWindow):
	def __init__(self):
		super().__init__()
		self.setWindowTitle("Bouton et boîte de dialogue")
		
		self.button = QPushButton('Clic !')
		self.button.clicked.connect(self.on_click)
		self.setCentralWidget(self.button)
		
	def on_click(self):
            self.popup = QMessageBox(QMessageBox.Information,'Message','Bouton cliqué')
            self.popup.show()
		
app = QApplication([])
window = Fenetre()
window.show() # IMPORTANT!
app.exec()

QPalette

Ajouter des couleurs à une fenêtre ou à un texte

setColor: Définit une couleur pour la palette.

Attributs, méthodes notables

Plus d'infos sur: https://doc.qt.io/qt-5/qpalette.html

Un peu de couleur

On peut colorer deux types d'éléments. Les labels et les fenêtres. On utiliser deux méthodes similaires pour parvenir à ce résultat, grâce à la classe QColor:

 

# On définit une couleur en paramètres ("blue", "red", "green", etc...)
color = "red"

Pour colorer une fenêtre on utilise la constante QPalette.Window:

palette.setColor(QPalette.Window, QColor(color))

Pour colorer un texte on utilise la constante QPalette.WindowText:

palette.setColor(QPalette.WindowText, QColor(color))

Un peu de couleur (2)

Illustrons la définition d'une couleur dans la classe:

class Fenetre(QMainWindow):
	def __init__(self):
		super().__init__()
		self.setWindowTitle("Couleurs")
		
		self.color = "green"
		
		# On crée un nouvelle palette de couleurs
		self.myPalette = self.palette()

                # La palette devient la couleur passée en paramètres
		self.myPalette.setColor(QPalette.Window, QColor(self.color))

                # On associe notre palette au widget
		self.setPalette(self.myPalette)
		

La commande optionnelle suivante vous assure de remplir la fenêtre:

 self.setAutoFillBackground(True)  # Pour remplir la fenêtre
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget
from PyQt5.QtGui import QColor, QPalette
from PyQt5.QtCore import Qt

class Fenetre(QMainWindow):
	def __init__(self):
		super().__init__()
		self.setWindowTitle("Couleurs")
		
		self.color = "red"
		
		# On crée un nouvelle palette de couleurs
                self.myPalette = self.palette()

                # La palette devient la couleur passée en paramètres
                self.myPalette.setColor(QPalette.Window, QColor(self.color))

                # On associe notre palette au widget
                self.setPalette(self.myPalette)
		
app = QApplication([])
window = Fenetre()
window.show() # IMPORTANT!
app.exec()

Colorer le texte

On peut colorer le texte en utilisant la même technique. On peut même colorer le texte en même temps que l'on colore le fond d'écran de la fenêtre.

 

Dans le code suivant on vous présente une technique qui utilise la méthode setStylesheet qui sert à personnaliser le style d'une widget

from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QLabel
from PyQt5.QtGui import QColor, QPalette

class Fenetre(QMainWindow):
	def __init__(self):
		super().__init__()
		self.setWindowTitle("Couleur Label")
		
		self.label = QLabel(self)
		self.label.setText('Bonjour')
		self.color = "blue"
		
		self.palette = self.label.palette() # Palette de Label !
		self.palette.setColor(QPalette.WindowText, QColor(self.color))
		self.label.setPalette(self.palette)
		
		# Modification de la couleur de fond à l'aide d'une feuille de style
		self.setStyleSheet('background-color: yellow')
		
		self.setCentralWidget(self.label)
		self.show()
		
		
app = QApplication([])
window = Fenetre()
window.show() # IMPORTANT!
app.exec()

Layouts

Nous avons appris à nous servir de beaucoup de widgets, maintenant on aimerait être capables de les placer les uns par rapport aux autres dans la fenêtre.

 

Pour faire ça on utilise les layouts.

Layouts (2)

Nous allons nous servir de trois types de layout :

QHBoxLayout: Pour placer les éléments horizontalement.

QVBoxLayout: Pour placer les éléments verticalement.

QGridLayout: Pour placer les éléments avec une grille et des coordonnées.

QStackedLayout: Pour placer les éléments les uns devant les autres.

 

QVBoxLayout

La QVBoxLayout permet d'aligner les éléments de manière verticale. A chaque ajout de widget, celui-ci se place tout en bas de la file:

QVBoxLayout (2)

L'ajout se passe de la manière suivante:

  • On crée un widget vierge
  • On lui associe un layout
  • On ajoute les éléments dans ce layout
  • On définit ce layout dans le widget central de la fenêtre grâce à setCentralWidget()

Layouts et couleurs

Pour illustrer l'utilisation des layouts, on réunit toutes les fonctionnalités dont nous avons parlé pour gérer les couleurs, dans une classe Color. Cette classe sert à définir des "rectangles de couleur" grâce à une QPalette.


class Color(QWidget):
    def __init__(self, color):
        super().__init__()
        self.setAutoFillBackground(True)  # Couleur d'arrière-plan

        # On crée un nouvelle palette de couleurs
        palette = self.palette()

        # On ajoute la couleur passée en paramètres
        palette.setColor(QPalette.Window, QColor(color))

        # On associe notre palette au widget
        self.setPalette(palette)

Layouts et couleurs (2)

Pour créer un widget de couleur on a plus qu'à appeler notre classe avec comme paramètre la couleur que l'on souhaite:

# Rectangle rouge
Color('red')

# Rectangle bleu
Color('blue')

QVBoxLayout (3)

from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout
from PyQt5.QtGui import QColor, QPalette

class Color(QWidget):
    def __init__(self, color):
        super().__init__()
        self.setAutoFillBackground(True)  # Couleur d'arrière-plan

        # On crée un nouvelle palette de couleurs
        self.myPalette = self.palette()

        # On ajoute la couleur passée en paramètres
        self.myPalette.setColor(QPalette.Window, QColor(color))

        # On associe notre palette au widget
        self.setPalette(self.myPalette)

class Fenetre(QMainWindow):
	def __init__(self):
		super().__init__()
		
		self.setWindowTitle("Les Vertical Box Layout")
		self.layout = QVBoxLayout()
		
		self.layout.addWidget(Color('red'))
		self.layout.addWidget(Color('blue'))
		
		self.widget = QWidget()
		self.widget.setLayout(self.layout)
		self.setCentralWidget(self.widget)
		
app = QApplication([])
window = Fenetre()
window.show() 
app.exec()

Espacement

On peut paramétrer la manière dont on espace les éléments grâce aux méthodes setSpacing et setContentMargins associées au layout.

# Marge en haut, à droite, en bas, à gauche
layout1.setContentsMargins(0,0,0,0)

# Marge entre les éléments
layout1.setSpacing(20)

QHBoxLayout

Les QHBoxLayout servent à organiser les widgets de manière horizontale:

from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QHBoxLayout
from PyQt5.QtGui import QColor, QPalette

class Color(QWidget):
    def __init__(self, color):
        super().__init__()
        self.setAutoFillBackground(True)  # Couleur d'arrière-plan

        # On crée un nouvelle palette de couleurs
        self.myPalette = self.palette()

        # On ajoute la couleur passée en paramètres
        self.myPalette.setColor(QPalette.Window, QColor(color))

        # On associe notre palette au widget
        self.setPalette(self.myPalette)

class Fenetre(QMainWindow):
	def __init__(self):
		super().__init__()
		
		self.setWindowTitle("Les Horizontal Box Layout")
		self.layout = QHBoxLayout()
		
		self.layout.addWidget(Color('red'))
		self.layout.addWidget(Color('green'))
		self.layout.addWidget(Color('blue'))
		self.layout.setSpacing(0)
		
		self.widget = QWidget()
		self.widget.setLayout(self.layout)
		self.setCentralWidget(self.widget)
		
app = QApplication([])
window = Fenetre()
window.show() 
app.exec()

QGridLayout

Le grid layout arrange vos éléments dans une grille. Chaque widget a ses coordonnées exprimées en ligne, colonne.

Vous n'êtes pas obligé de remplir toutes les cases de la grille...

from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QGridLayout
from PyQt5.QtGui import QColor, QPalette

class Color(QWidget):
    def __init__(self, color):
        super().__init__()
        self.setAutoFillBackground(True)  # Couleur d'arrière-plan

        # On crée un nouvelle palette de couleurs
        self.myPalette = self.palette()

        # On ajoute la couleur passée en paramètres
        self.myPalette.setColor(QPalette.Window, QColor(color))

        # On associe notre palette au widget
        self.setPalette(self.myPalette)

class Fenetre(QMainWindow):
	def __init__(self):
		super().__init__()
		
		self.setWindowTitle("Les Grid Layout")
		self.layout = QGridLayout()
		
		self.layout.addWidget(Color('red'),0,0)
		self.layout.addWidget(Color('green'),1,0)
		self.layout.addWidget(Color('blue'),1,1)
		self.layout.addWidget(Color('purple'),2,1)
		
		self.widget = QWidget()
		self.widget.setLayout(self.layout)
		self.setCentralWidget(self.widget)
		
app = QApplication([])
window = Fenetre()
window.show() 
app.exec()

Exercice

Insérez trois boutons en diagonale. Un clic  sur un bouton provoque l'affichage d'une boîte de dialogue avec le numéro du bouton cliqué.

from PyQt5.QtWidgets import QApplication, QMainWindow, QGridLayout, QWidget, QPushButton, QMessageBox

class Fenetre(QMainWindow):
	def __init__(self):
		super().__init__()
		
		self.setWindowTitle('Boutons réactifs')
		self.layout = QGridLayout()
		
		for i in range(1,4):
			self.bouton = QPushButton('B' + str(i), self)
			self.bouton.clicked.connect(self.afficherNumeroBouton)
			self.layout.addWidget(self.bouton, i, i)
		
		self.widget = QWidget()
		self.widget.setLayout(self.layout)
		self.setCentralWidget(self.widget)


	def afficherNumeroBouton(self):
		texte = self.sender().text()
		self.popup = QMessageBox(QMessageBox.Information, 'Message', 'Bouton ' + texte + ' cliqué.')
		self.popup.show()
	
app = QApplication([])
window = Fenetre()
window.show()
app.exec()

QStackedLayout

Le QStackedLayout vous autorise à placer les éléments les uns devant les autres. Vous pouvez ensuite sélectionner le widget que vous voulez afficher. Vous pouvez vous servir de ce genre de fonctionnalités pour gérer une application graphique ou pour avoir un fonctionnement de type onglets.

QStackedWidget: les index

Les index correspondent à l'ordre dans lequel les widgets ont été ajoutés dans la pile des widgets. Plus on en met, plus les derniers seront au premier plan.

On commence à compter à partir de zéro.

self.layout = QStackedLayout()

self.layout.addWidget(Color('red'))  # 0
self.layout.addWidget(Color('green'))  # 1
self.layout.addWidget(Color('blue'))  # 2
self.layout.addWidget(Color('yellow'))  # 3

QStackedLayout: fonctions

On contrôle quelle widget on veut afficher grâce à setCurrentIndex.

self.layout.addWidget(Color('blue'))    # Correspond à l'indice 0
self.layout.addWidget(Color('yellow'))    # Correspond à l'indice 1
		        
self.layout.setCurrentIndex(1)    # Celui qui est devant sera 1 c-a-d yellow

On accède à l'index de celui qui est affiché grâce à la fonction currentIndex.

monIndex = self.stackedLayout.currentIndex()
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QStackedLayout
from PyQt5.QtGui import QColor, QPalette

class Color(QWidget):
    def __init__(self, color):
        super().__init__()
        self.setAutoFillBackground(True)  # Couleur d'arrière-plan

        # On crée un nouvelle palette de couleurs
        self.myPalette = self.palette()

        # On ajoute la couleur passée en paramètres
        self.myPalette.setColor(QPalette.Window, QColor(color))

        # On associe notre palette au widget
        self.setPalette(self.myPalette)

class Fenetre(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Les QStackedLayout")
        
        self.layout = QStackedLayout()
        self.layout.addWidget(Color('red'))
        self.layout.addWidget(Color('green'))
        self.layout.addWidget(Color('blue'))
        self.layout.addWidget(Color('yellow'))
		
        
        self.layout.setCurrentIndex(2)
		
        self.widget = QWidget()
        self.widget.setMaximumSize(150,150)
        self.widget.setLayout(self.layout)
        self.setCentralWidget(self.widget)
		
app = QApplication([])
window = Fenetre()
window.show() 
app.exec()

Visibilité des widgets

Il n'est pas toujours possible de voir les widgets qui sont "derrière" dans une QStackedLayout. Le code de la page suivante utilise un QPushButton pour passer d'un widget à l'autre...

Dans ce code on a omis la classe Color mais elle est toutefois nécessaire si vous voulez voir un rectangle coloré (n'oubliez pas de l'inclure).

from PyQt5.QtWidgets import QApplication, QMainWindow, QHBoxLayout, QVBoxLayout, QWidget, QStackedLayout, QPushButton
from PyQt5.QtGui import QColor, QPalette

class Color(QWidget):
    ...    # Copiez-collez le contenu de la classe Color ici

class Fenetre(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QStackedLayout avec boutons")
        
        self.pageLayout = QVBoxLayout()
        self.buttonLayout = QHBoxLayout()
        self.stackedLayout = QStackedLayout()
        
        self.pageLayout.addLayout(self.buttonLayout)    # Les boutons horizontaux
        self.pageLayout.addLayout(self.stackedLayout)       # Le stacked layout
        
        self.colors = ['red','green','blue','yellow']
        n = 0
        
        for color in self.colors:
            btn = QPushButton( str(color) )
            btn.clicked.connect(self.changeColor)
            self.buttonLayout.addWidget(btn)    # On ajoute le bouton
            self.stackedLayout.addWidget(Color(color))    # On ajoute son widget
            n += 1
            
        self.widget = QWidget()
        self.widget.setLayout(self.pageLayout)
        self.setCentralWidget(self.widget)
        
    def changeColor(self):
        color = self.sender().text()
        index = self.colors.index(color)
        self.stackedLayout.setCurrentIndex(index)
		
app = QApplication([])
window = Fenetre()
window.show() 
app.exec()

Des onglets avec QTabWidget

Il existe aussi un widget spécial pour afficher les applications en "mode onglets". Il s'agit du QTabWidget.

 

Dans le code suivant nous avons utilisé la constante QTabWidget.East . Elle nous permet d'orienter les onglets selon les points cardinaux (ici ils seront à droite).

 

Grâce au paramètre setMoveable on peut déterminer si l'utilisateur est apte à déplacer les onglets ou non.

 

Et enfin le mode document permet d'avoir des onglets un peu plus fins pour ceux qui utilisent des ordinateurs macs.

from PyQt5.QtWidgets import QApplication, QMainWindow, QTabWidget, QWidget
from PyQt5.QtGui import QColor, QPalette

class Color(QWidget):
    def __init__(self, color):
        ...    # Copiez-collez le contenu de la classe Color ici

class Fenetre(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Les onglets en Qt")
        self.tabs = QTabWidget()
        self.tabs.setDocumentMode(True)
        self.tabs.setTabPosition(QTabWidget.East)
        self.tabs.setMovable(True)
        
        self.colors = ['red','green','blue','yellow']
        
        for color in self.colors:
            # On ajoute le widget directement AVEC son élément de tableau
            self.tabs.addTab( Color(color), color)
            
        self.setCentralWidget(self.tabs)
		
app = QApplication([])
window = Fenetre()
window.show() 
app.exec()

Exercice:

Question 1: A l'aide de trois fichiers .py distincts faîtes trois fenêtres qui affichent les interfaces suivantes:

 

Question 2: Grâce à une QStackedWidget ou à un QStackedLayout, réunissez les trois interfaces dans une seule. Les boutons "previous" et "next" vous permettant de passer de l'un à l'autre

 

Exercice 2:

Question 1: Créer un fichier python qui affiche l'interface suivante:

 

Question 2: Grâce à une QPixmap, créer une interface qui affiche une photo avec en bas un bouton 'previous' et un bouton 'next'. Les boutons sont inertes pour l'instant...

Question 3: Faîtes en sorte que

l'appui sur 'previous' et 'next' permette de passer d'une image à l'autre (Slideshow).

Bonus: Maintenir le ratio des images de différentes tailles...

Interfaces graphiques avec Pyton et Qt

By Henri H

Interfaces graphiques avec Pyton et Qt

Créer des interfaces graphiques en python à l'aide de la librairie Qt

  • 3,489