16 - GUI (ouverture)

Ouverture hors programme. Les interfaces graphiques ne sont pas au programme de NSI, mais elles rendent les projets plus attrayants. Cette fiche présente trois bibliothèques : tkinter, fournie avec Python, pygame pour les jeux et streamlit pour les applications web. Les deux dernières s’installent avec pip install pygame et pip install streamlit.

Une interface graphique (GUI, graphical user interface) se programme toujours de la même façon : on crée une fenêtre et ses composants (boutons, champs de saisie, textes), on associe des fonctions aux actions de l’utilisateur, puis on lance une boucle d’événements qui attend ces actions.

Tkinter

Tkinter, exemple 1 : saisie et affichage

L’exemple suivant crée une fenêtre avec un texte, un champ de saisie et un bouton. La fonction afficher est appelée quand on valide la saisie avec la touche Entrée.

import tkinter as tk


def afficher(event):
    """Affiche un message de bienvenue avec le prénom saisi."""
    accueil.configure(text="Bonjour " + champ.get() + " !")


# Fenêtre principale
fenetre = tk.Tk()
fenetre.title("Ma première fenêtre")
fenetre.minsize(width=300, height=80)

# Un texte, un champ de saisie et un bouton, empilés dans l'ordre
consigne = tk.Label(fenetre, text="Entrez votre prénom puis validez :")
consigne.pack()

champ = tk.Entry(fenetre, width=30)
champ.bind("<Return>", afficher)      # touche Entrée : appel de afficher
champ.pack()

accueil = tk.Label(fenetre, text="")
accueil.pack()

bouton_quitter = tk.Button(fenetre, text="Quitter", command=fenetre.destroy)
bouton_quitter.pack()

# Boucle d'événements, interrompue quand on ferme la fenêtre
fenetre.mainloop()

Les composants de l’interface (widgets) sont placés les uns sous les autres, dans l’ordre des appels à pack(). La méthode grid() de l’exemple suivant permet de les disposer dans une grille.

Tkinter, exemple 2 : conversion Fahrenheit vers Celsius

import tkinter as tk


def convertir(event=None):
    """Convertit la température saisie, en degrés Fahrenheit, en degrés Celsius."""
    fahrenheit = float(temperature.get())
    celsius = 5 / 9 * (fahrenheit - 32)
    resultat.config(text=f"{celsius:.2f} °C")


fenetre = tk.Tk()
fenetre.title("Conversion de Fahrenheit en Celsius")

consigne = tk.Label(fenetre, text="Température en degrés Fahrenheit :")
temperature = tk.Entry(fenetre)
temperature.bind("<Return>", convertir)
resultat = tk.Label(fenetre, text="")
bouton_convertir = tk.Button(fenetre, text="Convertir", command=convertir)
bouton_quitter = tk.Button(fenetre, text="Quitter", command=fenetre.destroy)

# Disposition en grille : ligne (row) et colonne (column)
consigne.grid(row=0, column=0, sticky="w", padx=5, pady=5)
temperature.grid(row=0, column=1, padx=5, pady=5)
bouton_convertir.grid(row=1, column=1, padx=5, pady=5)
resultat.grid(row=1, column=0, sticky="w", padx=5, pady=5)
bouton_quitter.grid(row=2, column=1, padx=5, pady=5)

fenetre.mainloop()

Le paramètre event=None permet d’appeler convertir aussi bien depuis le bouton (sans argument) que depuis la touche Entrée (avec un argument décrivant l’événement).

Pygame

Pygame dessine dans une fenêtre et gère le clavier, la souris et le temps. Un programme Pygame est une boucle qui, à chaque tour, lit les événements, met à jour l’état du jeu, dessine l’écran et attend le tour suivant.

Pygame, exemple 1 : un rectangle qui rebondit

import pygame

# Couleurs (rouge, vert, bleu)
NOIR = (0, 0, 0)
BLANC = (255, 255, 255)
ROUGE = (255, 0, 0)

pygame.init()
ecran = pygame.display.set_mode((700, 500))
pygame.display.set_caption("Rectangle qui rebondit")
horloge = pygame.time.Clock()

# Position et vitesse du rectangle (en pixels par image)
x, y = 50, 50
vitesse_x, vitesse_y = 2, 2

fini = False
while not fini:
    # 1. Lecture des événements : fermeture de la fenêtre
    for evenement in pygame.event.get():
        if evenement.type == pygame.QUIT:
            fini = True

    # 2. Mise à jour : déplacement et rebond sur les bords
    x = x + vitesse_x
    y = y + vitesse_y
    if y > 450 or y < 0:
        vitesse_y = -vitesse_y
    if x > 650 or x < 0:
        vitesse_x = -vitesse_x

    # 3. Dessin : fond, puis rectangle blanc et carré rouge à l'intérieur
    ecran.fill(NOIR)
    pygame.draw.rect(ecran, BLANC, [x, y, 50, 50])
    pygame.draw.rect(ecran, ROUGE, [x + 10, y + 10, 30, 30])
    pygame.display.flip()

    # 4. Attente : au plus 60 images par seconde
    horloge.tick(60)

pygame.quit()

Pygame, exemple 2 : du texte qui tourne

import pygame

NOIR = (0, 0, 0)
BLANC = (255, 255, 255)

pygame.init()
ecran = pygame.display.set_mode((400, 400))
pygame.display.set_caption("Texte en rotation")
horloge = pygame.time.Clock()
police = pygame.font.SysFont("Arial", 28, bold=True)

angle = 0
fini = False
while not fini:
    for evenement in pygame.event.get():
        if evenement.type == pygame.QUIT:
            fini = True

    ecran.fill(BLANC)

    # Texte fixe, puis le même texte tourné de « angle » degrés
    image = police.render("NSI", True, NOIR)
    ecran.blit(image, [20, 20])
    image_tournee = pygame.transform.rotate(image, angle)
    rectangle = image_tournee.get_rect(center=(200, 220))
    ecran.blit(image_tournee, rectangle)
    angle = (angle + 1) % 360

    pygame.display.flip()
    horloge.tick(60)

pygame.quit()

Pour aller plus loin : le site Program Arcade Games (en anglais) propose un cours complet avec Pygame.

Streamlit

Streamlit transforme un script Python en application web : chaque appel à st.… ajoute un composant à la page. On exécute le script avec la commande streamlit run mon_fichier.py, qui ouvre l’application dans le navigateur ; le script est réexécuté à chaque action de l’utilisateur.

import streamlit as st

st.title("Convertisseur de températures")

fahrenheit = st.slider("Température en degrés Fahrenheit", -40, 120, 68)
celsius = 5 / 9 * (fahrenheit - 32)
st.write(f"{fahrenheit} °F correspondent à {celsius:.1f} °C.")

gauche, droite = st.columns(2)
if gauche.button("Eau qui gèle"):
    droite.write("0 °C, soit 32 °F")
if gauche.button("Eau qui bout"):
    droite.write("100 °C, soit 212 °F")

with st.expander("Comment est faite la conversion ?"):
    st.write("On retire 32, puis on multiplie par 5/9.")
    st.code("celsius = 5 / 9 * (fahrenheit - 32)")