Comment Créer une Interface Graphique en Python ? Tutoriel Détaillé avec Exemples

Python Tuto Comment Créer une Interface Graphique en Python ? Tutoriel Détaillé avec Exemples

  •  Thread starter
  •  Admin
  • 1769203785470.webp


    Objectifs
    • Comprendre les options pour créer une interface (GUI) en Python.
    • Savoir choisir un toolkit (tkinter, PySide6/Qt, Kivy, etc.) selon votre besoin.
    • Construire des interfaces robustes (layouts, événements, threads, validation, styles).
    • Tester et empaqueter votre application pour la distribution.
    • Exploiter VS Code + Copilot Chat pour accélérer le développement.

    Prérequis : Notions de base en Python (fonctions, modules), Python 3.10+ recommandé, VS Code (ou IDE équivalent).


    1) Choisir son toolkit GUI en Python

    Décision rapide :
    • Débutant / standard / sans dépendances externestkinter (inclus avec Python).
    • Look & feel moderne, très puissant, multiplateformePySide6 (Qt for Python) (ou PyQt6).
    • Cibler desktop + mobile (Android/iOS)Kivy.
    • tkinter mais avec thèmes modernescustomtkinter (surcouche moderne de tkinter).

    Remarque licences :
    • tkinter : livré avec Python, très permissif.
    • PySide6 : LGPL (souvent plus simple pour des apps distribuées).
    • PyQt6 : GPL/commercial (bien vérifier l’adéquation à votre projet).
    • Kivy : MIT (très permissif).


    2) Préparer l’environnement

    Créer un environnement virtuel
    Python:
    python -m venv .venv
    Windows
    .venv\Scripts\activate
    macOS / Linux
    source .venv/bin/activate

    Installer selon votre choix
    Python:
    Tkinter : déjà inclus (sur macOS/Linux, il peut falloir installer Tk via le gestionnaire de paquets)
    pip install customtkinter  # (optionnel) pour des thèmes modernes
    pip install PySide6        # Qt for Python (recommandé)
    ou
    pip install PyQt6
    pip install kivy


    3) Créer une GUI avec tkinter (base standard)
    3.1 Hello, World (tkinter)
    Python:
    import tkinter as tk
    root = tk.Tk()
    root.title("Hello tkinter")
    root.geometry("320x150")
    label = tk.Label(root, text="Bonjour, tkinter !")
    label.pack(pady=10)
    def on_click():
    label.config(text="Bouton cliqué !")
    btn = tk.Button(root, text="Clique-moi", command=on_click)
    btn.pack(pady=5)
    root.mainloop()

    Explications :
    • Tk() : crée la fenêtre principale.
    • pack() : place les widgets en pile (simple). Alternatives : grid() (grille), place() (position absolue).
    • command= : lie un callback Python à un bouton.
    3.2 Layouts : pack vs grid
    Python:
    pack : simple empilement vertical/horizontal
    label.pack(side="top", fill="x", padx=10, pady=5)
    grid : gestion en lignes/colonnes
    label.grid(row=0, column=0, padx=10, pady=5, sticky="w")
    entry.grid(row=0, column=1, padx=10, pady=5, sticky="ew")
    root.grid_columnconfigure(1, weight=1)  # colonne qui s'étire

    3.3 Variables de contrôle et validation
    Python:
    import tkinter as tk
    root = tk.Tk()
    root.title("Validation simple")
    var = tk.StringVar(value="0")
    def inc():
    try:
    n = int(var.get())
    except ValueError:
    n = 0
    var.set(str(n + 1))
    tk.Label(root, text="Compteur :").grid(row=0, column=0, padx=5, pady=5, sticky="e")
    entry = tk.Entry(root, textvariable=var, width=10)
    entry.grid(row=0, column=1, padx=5, pady=5, sticky="w")
    tk.Button(root, text="+1", command=inc).grid(row=1, column=0, columnspan=2, pady=10)
    root.mainloop()

    3.4 Longues tâches : threading pour éviter le gel de l’UI
    Python:
    import tkinter as tk
    import threading, time
    root = tk.Tk()
    root.title("Threading avec tkinter")
    status = tk.StringVar(value="Prêt")
    tk.Label(root, textvariable=status).pack(pady=8)
    def long_task():
    status.set("Traitement en cours...")
    time.sleep(3)  # Simule une tâche lourde
    # Revenir au thread GUI via .after
    root.after(0, lambda: status.set("Terminé !"))
    def start_task():
    threading.Thread(target=long_task, daemon=True).start()
    tk.Button(root, text="Lancer", command=start_task).pack(pady=8)
    root.mainloop()

    Points clés : Ne bloquez jamais le thread principal (celui de mainloop). Pour mettre à jour l’UI depuis un thread, utilisez .after().


    4) Créer une GUI avec PySide6 (Qt for Python)
    4.1 Hello, World (PySide6)
    Python:
    from PySide6.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QVBoxLayout
    import sys
    app = QApplication(sys.argv)
    win = QWidget()
    win.setWindowTitle("Hello PySide6")
    label = QLabel("Bonjour, Qt !")
    btn = QPushButton("Clique-moi")
    def on_click():
    label.setText("Bouton cliqué !")
    btn.clicked.connect(on_click)
    layout = QVBoxLayout()
    layout.addWidget(label)
    layout.addWidget(btn)
    win.setLayout(layout)
    win.resize(320, 150)
    win.show()
    sys.exit(app.exec())

    Explications :
    • Signals/slots : clickedon_click.
    • Layouts (QVBoxLayout, QHBoxLayout, QGridLayout) gèrent proprement les redimensionnements.
    4.2 Exemple « Todo List » (PySide6)
    Python:
    from PySide6.QtWidgets import (QApplication, QWidget, QLineEdit, QPushButton,
    QListWidget, QVBoxLayout, QHBoxLayout)
    import sys
    class Todo(QWidget):
    def init(self):
    super().init()
    self.setWindowTitle("Todo (PySide6)")
        self.input = QLineEdit()
        self.input.setPlaceholderText("Nouvelle tâche...")
    
        self.btn_add = QPushButton("Ajouter")
        self.list = QListWidget()
    
        top = QHBoxLayout()
        top.addWidget(self.input)
        top.addWidget(self.btn_add)
    
        layout = QVBoxLayout(self)
        layout.addLayout(top)
        layout.addWidget(self.list)
    
        self.btn_add.clicked.connect(self.add_item)
        self.input.returnPressed.connect(self.add_item)
    
    def add_item(self):
        text = self.input.text().strip()
        if text:
            self.list.addItem(text)
            self.input.clear()
    
    if name == "main":
    app = QApplication(sys.argv)
    w = Todo()
    w.resize(400, 300)
    w.show()
    sys.exit(app.exec())

    4.3 Longues tâches : QThread + signaux
    Python:
    from PySide6.QtCore import QObject, QThread, Signal
    from PySide6.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QLabel, QProgressBar
    import sys, time
    class Worker(QObject):
    progress = Signal(int)
    finished = Signal()
    def run(self):
        for i in range(101):
            time.sleep(0.02)
            self.progress.emit(i)
        self.finished.emit()
    
    class Window(QWidget):
    def init(self):
    super().init()
    self.setWindowTitle("QThread & Progress")
    self.label = QLabel("Prêt")
    self.bar = QProgressBar()
    self.btn = QPushButton("Lancer")
    layout = QVBoxLayout(self)
    layout.addWidget(self.label)
    layout.addWidget(self.bar)
    layout.addWidget(self.btn)
    self.btn.clicked.connect(self.start)
    def start(self):
        self.thread = QThread()
        self.worker = Worker()
        self.worker.moveToThread(self.thread)
    
        self.thread.started.connect(self.worker.run)
        self.worker.progress.connect(self.bar.setValue)
        self.worker.finished.connect(self.on_finished)
        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(self.worker.deleteLater)
        self.thread.finished.connect(self.thread.deleteLater)
    
        self.label.setText("En cours…")
        self.thread.start()
    
    def on_finished(self):
        self.label.setText("Terminé !")
    
    if name == "main":
    app = QApplication(sys.argv)
    w = Window()
    w.resize(360, 180)
    w.show()
    sys.exit(app.exec())


    5) Créer une GUI avec Kivy (option mobile)

    5.1 Hello, World (Kivy)
    Python:
    from kivy.app import App
    from kivy.uix.boxlayout import BoxLayout
    from kivy.uix.label import Label
    from kivy.uix.button import Button
    class MyApp(App):
    def build(self):
    root = BoxLayout(orientation='vertical', padding=10, spacing=10)
    self.label = Label(text="Bonjour, Kivy !")
    btn = Button(text="Clique-moi")
    btn.bind(on_press=self.on_click)
    root.add_widget(self.label)
    root.add_widget(btn)
    return root
    def on_click(self, _):
        self.label.text = "Bouton cliqué !"
    
    MyApp().run()
    5.2 Séparer le style via le langage KV
    Python:
    fichier: myui.kv
    BoxLayout:
    orientation: "vertical"
    padding: 10
    spacing: 10
    Label:
    id: lbl
    text: "Bonjour, Kivy !"
    Button:
    text: "Clique-moi"
    on_press: app.on_click()
    Python:
    main.py
    from kivy.app import App
    from kivy.lang import Builder
    class MyApp(App):
    def build(self):
    return Builder.load_file("myui.kv")
    def on_click(self):
    self.root.ids.lbl.text = "Bouton cliqué !"
    MyApp().run()


    6) Architecture & bonnes pratiques

    6.1 Séparer l’UI et la logique (ex. MVC simplifié)
    Python:
    mon_app/
    app.py          # point d'entrée
    ui/             # vues
    main_window.py
    core/           # logique métier
    services.py
    data/           # accès aux données (fichiers, DB)
    repository.py
    assets/         # icônes, images
    Exemple (PySide6) : logique métier séparée
    Python:
    core/services.py
    def calc_tva(montant_ht: float, taux: float = 0.2) -> float:
    return round(montant_ht * (1 + taux), 2)
    Python:
    ui/main_window.py
    from PySide6.QtWidgets import QWidget, QVBoxLayout, QLineEdit, QLabel
    from core.services import calc_tva
    class MainWindow(QWidget):
    def init(self):
    super().init()
    self.setWindowTitle("Calcul TVA")
    self.inp = QLineEdit(placeholderText="Montant HT")
    self.out = QLabel("Résultat : -")
    layout = QVBoxLayout(self)
    layout.addWidget(self.inp)
    layout.addWidget(self.out)
    self.inp.textChanged.connect(self.on_change)
    def on_change(self, _):
        try:
            ht = float(self.inp.text())
            self.out.setText(f"Résultat : {calc_tva(ht)} €")
        except ValueError:
            self.out.setText("Résultat : -")
    Python:
    app.py
    from PySide6.QtWidgets import QApplication
    import sys
    from ui.main_window import MainWindow
    app = QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec())
    Conseils :
    • Évitez de mettre la logique métier dans les callbacks UI.
    • Centralisez la configuration (constantes, chemins, i18n).
    • Ajoutez des logs (module logging) pour diagnostiquer.


    7) Thèmes & Styles

    tkinter
    • Utilisez ttk pour des widgets plus modernes.
    • customtkinter apporte un look moderne (dark mode, sliders, etc.).
    Python:
    import customtkinter as ctk
    ctk.set_default_color_theme("dark-blue")
    ctk.set_appearance_mode("dark")
    app = ctk.CTk()
    app.title("Modern tkinter")
    ctk.CTkLabel(app, text="Bonjour !").pack(pady=10)
    ctk.CTkButton(app, text="Action").pack(pady=10)
    app.mainloop()
    Qt (PySide6)
    • Styles intégrés (Fusion, Windows, etc.) + Stylesheets (type CSS) pour personnaliser.
    Python:
    from PySide6.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton
    import sys
    app = QApplication(sys.argv)
    app.setStyle("Fusion")
    w = QWidget()
    btn = QPushButton("Accent")
    w.setStyleSheet("""
    QWidget { background: #1e1e1e; color: #ddd; }
    QPushButton { background: #3a86ff; color: white; border-radius: 6px; padding: 6px 12px; }
    QPushButton:hover { background: #2f6adf; }
    """)
    layout = QVBoxLayout(w)
    layout.addWidget(btn)
    w.resize(300,120)
    w.show()
    sys.exit(app.exec())


    8) Internationalisation & Accessibilité

    i18n (Qt) : utilisez QTranslator + fichiers .qm.
    tkinter : centralisez les chaînes dans un module et chargez-les selon la locale.
    Accessibilité :
    • Fournissez des labels explicites et des raccourcis clavier (Alt+Lettre).
    • Respectez le contraste, permettez le redimensionnement des polices.
    • Ordre de tabulation logique (Qt : setTabOrder).


    9) Tests (exemple rapide avec PySide6 + pytest-qt)

    Optionnel si vous utilisez Qt[/i]
    Python:
    test_todo.py
    def test_add_item(qtbot):
    from app import Todo  # votre classe QWidget
    w = Todo()
    qtbot.addWidget(w)
    w.show()
    w.input.setText("Tâche A")
    qtbot.mouseClick(w.btn_add, Qt.LeftButton)
    
    assert w.list.count() == 1
    assert w.list.item(0).text() == "Tâche A"


    10) Empaquetage (créer un .exe / .app / binaire)

    PyInstaller (générique)
    Python:
    pip install pyinstaller
    pyinstaller --name MonAppli --noconsole --onefile app.py
    ou en dossier avec ressources :
    pyinstaller --name MonAppli --noconsole --add-data "assets:assets" app.py
    Conseils :
    • Testez sur l’OS cible (Windows/macOS/Linux) — les binaires ne sont pas portables entre OS.
    • Sur macOS, signature et notarisation peuvent être nécessaires.
    • Pour Qt, incluez les plugins requis (PyInstaller le gère souvent automatiquement).


    11) VS Code + Copilot Chat : accélérer votre développement

    Configuration rapide
    • Installez l’extension Python (Microsoft) et sélectionnez l’interpréteur de votre venv.
    • Démarrez un Run & Debug simple (bouton ▶) pour tester votre app.
    • Ouvrez Copilot Chat (panneau latéral) pour converser avec l’IA dans votre contexte de code.
    Prompts utiles (exemples concrets)
    Objectif : Générer une fenêtre tkinter minimale avec un compteur et un bouton +1

    Prompt à Copilot Chat :
    « Crée une application tkinter avec une fenêtre 320x150, un label "Compteur : 0" et un bouton "+1" qui incrémente le compteur sans bloquer l’UI. Utilise grid et une StringVar. »
    Réponse attendue (extrait plausible) :
    Python:
    import tkinter as tk
    root = tk.Tk()
    root.title("Compteur")
    root.geometry("320x150")
    val = tk.StringVar(value="0")
    def incr():
    val.set(str(int(val.get()) + 1))
    tk.Label(root, text="Compteur :").grid(row=0, column=0, padx=8, pady=10, sticky="e")
    tk.Label(root, textvariable=val).grid(row=0, column=1, padx=8, pady=10, sticky="w")
    tk.Button(root, text="+1", command=incr).grid(row=1, column=0, columnspan=2, pady=10)
    root.mainloop()
    Objectif : Refactoriser en séparant logique & UI (PySide6)

    Prompt à Copilot Chat :
    « Refactorise ce fichier PySide6 pour séparer la logique de calcul de la TVA dans un module core/services.py et importe la fonction. Écris le code structuré (app.py, ui/main_window.py, core/services.py). »
    Objectif : Ajouter un thread pour une tâche longue (PySide6)
    Prompt à Copilot Chat :
    « Dans cette fenêtre PySide6, ajoute un Worker sur QThread qui met à jour une QProgressBar (0→100) et remet le label à 'Terminé !' à la fin. Donne le code complet et libère correctement le thread. »
    Objectif : Générer un style sombre (Qt Stylesheet)

    Prompt à Copilot Chat :
    « Propose un stylesheet Qt sombre avec bouton primaire bleu, hover plus foncé, texte contrasté et fond #1e1e1e. Applique-le à l’ensemble de la fenêtre. »
    Objectif : Aide au débogage

    Prompt à Copilot Chat :
    « Mon app PyInstaller sous Windows affiche 'failed to execute script' ou 'could not find Qt platform plugin'. Liste les causes probables et donne la commande PyInstaller corrigée avec --add-data/--hidden-import si nécessaire. »
    Astuce : Collez un extrait de votre code dans Copilot Chat et demandez « Explique ce que fait ce code », « Propose des tests unitaires », ou « Optimise ce layout sans changer le comportement ».


    12) Foire aux problèmes courants
    tkinter
    • L’UI « gèle » → une fonction lourde bloque le thread principal. Solution : threading + .after().
    • TclError: no display name (Linux/CI) → serveur X manquant. Lancez sur une machine avec affichage ou utilisez xvfb.
    • Plusieurs Tk() → n’avoir qu’un seul Tk(), utiliser Toplevel() pour des fenêtres supplémentaires.
    PySide6 / Qt
    • Qt platform plugin "xcb" non trouvé (Linux) → installer dépendances système (libxcb, etc.).
    • Crash à la fermeture → Destroy order : relier finishedthread.quit, deleteLater sur worker/thread.
    • Path des ressources → utilisez QResource ou chemins relatifs embarqués via PyInstaller --add-data.
    Kivy
    • Dépendances SDL2/GLEW manquantes → suivez la doc d’installation spécifique à l’OS.
    • Affichage vide → testez un exemple minimal pour vérifier l’accélération graphique.


    13) Check-list de publication
    • Désactivez les prints inutiles, activez des logs de niveau INFO/ERROR.
    • Ajoutez un écran « À propos » (version, licence, crédits).
    • Testez les tailles d’écran, thèmes clair/sombre, accessibilité (tab, contrastes).
    • Packager via PyInstaller (ou Briefcase/Flet selon la stack choisie) et testez sur l’OS cible.


    Conclusion

    Vous pouvez démarrer très vite avec tkinter pour un utilitaire simple, ou choisir PySide6 si vous souhaitez un rendu moderne et des fonctionnalités avancées. Kivy devient pertinent si vous visez desktop + mobile avec la même base.
    Vous voulez que je vous génère un projet squelette clé-en-main (structure de dossiers, code de base, scripts de lancement) pour tkinter ou PySide6 ? Dites-moi :
    • Le toolkit choisi (tkinter / PySide6 / Kivy / customtkinter)
    • Votre OS cible principal (Windows / macOS / Linux)
    • Un besoin concret (ex : « petit gestionnaire de tâches », « calcul TVA », « formulaire avec validation »)
     
    Similar content Most view View more
    Back
    Top