# Fichier: python_cheats/cheatsheets/tkinter.txt
# Cheatsheet Tkinter - Guide Ultra-Détaillé pour Grands Débutants

```
[OK] CONCEPTS FONDAMENTAUX (EXPLICATIONS TRÈS DÉTAILLÉES)

# === QU'EST-CE QUE TKINTER ? ===

# Imagine que tu veux créer une application avec des boutons, des fenêtres, des zones de texte
# Comme une calculatrice, un éditeur de texte, ou un jeu simple
# Comment faire pour que Python affiche des fenêtres graphiques?

# Solution classique (compliquée):
# 1. Apprendre un framework lourd comme PyQt, Kivy
# 2. Installer des dépendances externes complexes
# 3. Écrire beaucoup de code pour des choses simples
# = BEAUCOUP DE TRAVAIL! C'est très technique.

# TKINTER = Bibliothèque graphique intégrée à Python!
# Tu ne fais que:
# 1. Importer tkinter (déjà installé avec Python)
# 2. Créer une fenêtre
# 3. Ajouter des widgets (boutons, labels, etc.)
# 4. BOOM! Ton interface graphique fonctionne

# Tkinter = Tk interface
# = Interface Python pour Tk (bibliothèque graphique Tcl/Tk)
# = Standard depuis Python 1.1 (1994!)
# = Disponible sur Windows, macOS, Linux

# === VOCABULAIRE TKINTER (TRÈS IMPORTANT!) ===

# WIDGET (Composant graphique)
# = Tout élément visible dans une fenêtre
# = Bouton, zone de texte, label, case à cocher, etc.
# Analogie: Si la fenêtre est une maison, les widgets sont les meubles
# Types de widgets:
#   - Button: bouton cliquable
#   - Label: texte ou image non modifiable
#   - Entry: champ de saisie d'une ligne
#   - Text: zone de texte multi-lignes
#   - Frame: conteneur pour organiser d'autres widgets
#   - Canvas: zone de dessin

# ROOT / MAIN WINDOW (Fenêtre principale)
# = La fenêtre de base de ton application
# = Créée avec tk.Tk()
# = Tout le reste doit être à l'intérieur
# = Une application a toujours UNE fenêtre root

# GEOMETRY MANAGER (Gestionnaire de placement)
# = Système pour positionner les widgets dans la fenêtre
# = 3 types principaux:
#   - pack(): empile les widgets (simple mais limité)
#   - grid(): grille ligne/colonne (flexible)
#   - place(): positionnement absolu x/y (précis mais rigide)
# = Tu DOIS utiliser un geometry manager sinon le widget est invisible!

# EVENT (Événement)
# = Action de l'utilisateur ou du système
# Exemples:
#   - Clic sur un bouton
#   - Pression d'une touche clavier
#   - Déplacement de la souris
#   - Fermeture de fenêtre
# = Tu peux "lier" du code à un événement avec .bind()

# CALLBACK (Fonction de rappel)
# = Fonction qui s'exécute quand un événement se produit
# Exemple:
#   def on_click():
#       print("Bouton cliqué!")
#   button = tk.Button(root, text="Cliquez-moi", command=on_click)
# = "command" est le callback du bouton

# MAINLOOP (Boucle principale)
# = Boucle infinie qui attend les événements
# = Lance l'interface graphique
# = OBLIGATOIRE à la fin de ton code!
# = Syntaxe: root.mainloop()
# Sans mainloop, la fenêtre s'ouvre et se ferme instantanément


# === COMMENT ÇA MARCHE? (FLUX COMPLET) ===

# 1. Tu importes tkinter
import tkinter as tk

# 2. Tu crées la fenêtre principale (root)
root = tk.Tk()
root.title("Mon Application")

# 3. Tu crées des widgets
label = tk.Label(root, text="Bonjour!")
button = tk.Button(root, text="Cliquez-moi")

# 4. Tu les places avec un geometry manager
label.pack()
button.pack()

# 5. Tu lances la boucle principale
root.mainloop()
# La fenêtre reste ouverte et attend les interactions

# Quand l'utilisateur clique sur [X]:
# - mainloop() se termine
# - Le programme Python continue (ou se termine)


[OK] INSTALLATION ET VÉRIFICATION

# === TKINTER EST-IL INSTALLÉ? ===

# Sur la plupart des installations Python, Tkinter est déjà là!

# Vérifier si Tkinter est disponible:
python -m tkinter
# ou
python3 -m tkinter

# Si une petite fenêtre s'ouvre avec "Click me!" -> Tkinter fonctionne!
# Si erreur "No module named tkinter" -> il faut l'installer

# === INSTALLATION PAR OS ===

# ========== Windows ==========
# Tkinter est inclus avec Python par défaut
# Si manquant, réinstalle Python depuis python.org
# Coche "tcl/tk and IDLE" pendant l'installation

# ========== macOS ==========
# Inclus avec Python système
# Si tu utilises Homebrew:
brew install python-tk

# ========== Linux (Ubuntu/Debian) ==========
sudo apt-get update
sudo apt-get install python3-tk

# ========== Linux (Fedora) ==========
sudo dnf install python3-tkinter

# ========== Arch Linux ==========
sudo pacman -S tk

# === TESTER TKINTER DANS PYTHON ===

# Ouvre Python:
python
# ou
python3

# Puis tape:
import tkinter
tkinter._test()

# Une fenêtre doit s'ouvrir!
# Si erreur, Tkinter n'est pas installé correctement


[OK] PREMIÈRE APPLICATION (SUPER DÉTAILLÉE)

# === ÉTAPE 1: CRÉER LE FICHIER ===

# Crée un fichier "hello_tkinter.py"

# === ÉTAPE 2: IMPORTER TKINTER ===

import tkinter as tk
# "as tk" = alias pour écrire moins
# Sinon tu devrais écrire "tkinter.Button" au lieu de "tk.Button"

# Alternative (moins recommandée):
from tkinter import *
# Importe tout directement
# Problème: pollution du namespace (noms en conflit)


# === ÉTAPE 3: CRÉER LA FENÊTRE PRINCIPALE ===

root = tk.Tk()
# tk.Tk() = constructeur de la fenêtre principale
# root = nom conventionnel (tu peux choisir autre chose)

# Explications:
# - Crée une fenêtre vide
# - Titre par défaut: "tk"
# - Taille par défaut: petite fenêtre


# === ÉTAPE 4: CONFIGURER LA FENÊTRE ===

root.title("Ma Première App")
# Change le titre de la fenêtre

root.geometry("400x300")
# Définit largeur x hauteur en pixels
# Format: "LARGEURxHAUTEUR"
# Exemple: "800x600", "1024x768"

root.resizable(False, False)
# (largeur, hauteur)
# False = l'utilisateur ne peut pas redimensionner
# True = redimensionnable (par défaut)

root.config(bg="lightblue")
# bg = background color (couleur de fond)
# Couleurs: nom anglais ou code hexa (#FF0000)


# === ÉTAPE 5: AJOUTER UN WIDGET SIMPLE ===

label = tk.Label(root, text="Bonjour, Tkinter!")
# tk.Label() = widget pour afficher du texte
# root = parent (où placer le widget)
# text = texte à afficher

label.pack()
# .pack() = geometry manager simple
# Place le widget dans la fenêtre


# === ÉTAPE 6: LANCER L'APPLICATION ===

root.mainloop()
# Démarre la boucle d'événements
# La fenêtre reste ouverte
# DOIT être la dernière ligne!


# === CODE COMPLET ===

import tkinter as tk

# Créer la fenêtre principale
root = tk.Tk()
root.title("Ma Première App")
root.geometry("400x300")
root.config(bg="lightblue")

# Créer un label
label = tk.Label(root, text="Bonjour, Tkinter!", font=("Arial", 20))
label.pack(pady=20)  # pady = padding vertical

# Lancer la boucle
root.mainloop()


# === EXÉCUTER LE PROGRAMME ===

# Dans le terminal:
python hello_tkinter.py

# Une fenêtre s'ouvre avec:
# - Titre: "Ma Première App"
# - Taille: 400x300 pixels
# - Fond: bleu clair
# - Texte: "Bonjour, Tkinter!" en Arial 20

# Pour fermer: cliquer sur [X]


[OK] WIDGETS FONDAMENTAUX (EXPLICATIONS DÉTAILLÉES)

# === WIDGET #1: LABEL (ÉTIQUETTE) ===

# POURQUOI?
# Pour afficher du texte ou des images
# Non modifiable par l'utilisateur
# Utilisé pour: titres, descriptions, icônes

# COMMENT?
label = tk.Label(root, text="Texte ici")
label.pack()

# OPTIONS IMPORTANTES:

# text = contenu texte
label = tk.Label(root, text="Bonjour")

# font = police (famille, taille, style)
label = tk.Label(root, text="Gros texte", font=("Arial", 24))
label = tk.Label(root, text="Texte gras", font=("Helvetica", 16, "bold"))
# Styles: "bold", "italic", "underline"

# fg = foreground color (couleur du texte)
label = tk.Label(root, text="Rouge", fg="red")

# bg = background color (couleur de fond)
label = tk.Label(root, text="Fond jaune", bg="yellow")

# width et height = largeur et hauteur en caractères (pas pixels!)
label = tk.Label(root, text="Large", width=30, height=5)

# anchor = alignement du texte
# Valeurs: "n", "s", "e", "w", "ne", "nw", "se", "sw", "center"
label = tk.Label(root, text="Gauche", anchor="w", width=20)
# "w" = west = gauche
# "e" = east = droite
# "n" = north = haut

# justify = alignement multi-lignes
label = tk.Label(root, text="Ligne 1\nLigne 2\nLigne 3", justify="left")
# Valeurs: "left", "center", "right"

# wraplength = largeur avant retour à la ligne (pixels)
label = tk.Label(root, text="Texte très long qui va se découper", wraplength=100)

# relief = style de bordure
# Valeurs: "flat", "raised", "sunken", "groove", "ridge"
label = tk.Label(root, text="Relief", relief="raised", borderwidth=5)

# image = afficher une image
from tkinter import PhotoImage
img = PhotoImage(file="image.png")
label = tk.Label(root, image=img)
label.image = img  # Garde une référence (important!)

# compound = position image + texte
label = tk.Label(root, text="Icône", image=img, compound="left")
# Valeurs: "left", "right", "top", "bottom", "center"


# === WIDGET #2: BUTTON (BOUTON) ===

# POURQUOI?
# Pour déclencher une action quand l'utilisateur clique
# Élément interactif principal

# COMMENT?
def on_click():
    print("Bouton cliqué!")

button = tk.Button(root, text="Cliquez-moi", command=on_click)
button.pack()

# OPTIONS IMPORTANTES:

# command = fonction à appeler au clic
def dire_bonjour():
    print("Bonjour!")

button = tk.Button(root, text="Dire bonjour", command=dire_bonjour)
# ATTENTION: command=dire_bonjour (sans parenthèses!)
# Si tu mets command=dire_bonjour(), la fonction s'exécute immédiatement

# text = texte du bouton
button = tk.Button(root, text="Cliquez ici")

# font, fg, bg = même chose que Label
button = tk.Button(root, text="Gros bouton", font=("Arial", 20), fg="white", bg="blue")

# width et height = taille en caractères
button = tk.Button(root, text="Large", width=20, height=3)

# state = état du bouton
button = tk.Button(root, text="Désactivé", state="disabled")
# Valeurs: "normal", "disabled", "active"
# "disabled" = grisé, non cliquable

# relief = style visuel
button = tk.Button(root, text="Relief", relief="raised")

# cursor = curseur de souris au survol
button = tk.Button(root, text="Main", cursor="hand2")
# Valeurs: "arrow", "hand2", "cross", "watch", etc.

# activebackground et activeforeground = couleurs au clic
button = tk.Button(root, text="Actif", activebackground="red", activeforeground="white")

# image = bouton avec icône
img = PhotoImage(file="icon.png")
button = tk.Button(root, image=img, command=on_click)
button.image = img


# === PASSER DES ARGUMENTS À UN CALLBACK ===

# PROBLÈME:
# Tu veux passer des valeurs à ta fonction callback

# Solution 1: lambda
def afficher(message):
    print(message)

button = tk.Button(root, text="Lambda", command=lambda: afficher("Bonjour!"))
# lambda: crée une fonction anonyme
# Elle appelle afficher("Bonjour!") quand le bouton est cliqué

# Solution 2: functools.partial
from functools import partial

button = tk.Button(root, text="Partial", command=partial(afficher, "Salut!"))


# === WIDGET #3: ENTRY (CHAMP DE SAISIE) ===

# POURQUOI?
# Pour que l'utilisateur entre du texte (une ligne)
# Exemples: nom, email, mot de passe

# COMMENT?
entry = tk.Entry(root)
entry.pack()

# Récupérer la valeur entrée:
valeur = entry.get()
print(valeur)

# OPTIONS IMPORTANTES:

# width = largeur en caractères
entry = tk.Entry(root, width=30)

# font = police
entry = tk.Entry(root, font=("Courier", 14))

# show = masquer le texte (pour mots de passe)
entry = tk.Entry(root, show="*")
# Chaque caractère apparaît comme "*"

# textvariable = lier à une variable Tkinter
var = tk.StringVar()
entry = tk.Entry(root, textvariable=var)
# Maintenant var.get() récupère la valeur
# var.set("texte") met à jour l'Entry

# state = état
entry = tk.Entry(root, state="disabled")
# "disabled" = grisé, non modifiable

# justify = alignement du texte
entry = tk.Entry(root, justify="center")
# Valeurs: "left", "center", "right"

# fg et bg = couleurs
entry = tk.Entry(root, fg="blue", bg="lightyellow")


# === MÉTHODES ENTRY ===

# .get() = récupérer le texte
texte = entry.get()

# .insert(index, string) = insérer du texte
entry.insert(0, "Texte initial")
# 0 = début, tk.END = fin

# .delete(start, end) = supprimer du texte
entry.delete(0, tk.END)  # Tout supprimer

# .icursor(index) = positionner le curseur
entry.icursor(5)  # Curseur au 5e caractère


# === EXEMPLE COMPLET: SAISIE NOM ===

import tkinter as tk

def afficher_nom():
    nom = entry.get()
    label_resultat.config(text=f"Bonjour, {nom}!")

root = tk.Tk()
root.title("Saisie nom")

label = tk.Label(root, text="Entrez votre nom:")
label.pack(pady=10)

entry = tk.Entry(root, width=30)
entry.pack(pady=5)

button = tk.Button(root, text="Valider", command=afficher_nom)
button.pack(pady=5)

label_resultat = tk.Label(root, text="", fg="blue")
label_resultat.pack(pady=10)

root.mainloop()


# === WIDGET #4: TEXT (ZONE DE TEXTE MULTI-LIGNES) ===

# POURQUOI?
# Pour du texte long, plusieurs lignes
# Éditeur de texte, affichage de logs, notes

# COMMENT?
text = tk.Text(root, width=40, height=10)
text.pack()

# OPTIONS IMPORTANTES:

# width et height = taille en caractères
text = tk.Text(root, width=50, height=15)

# wrap = retour à la ligne automatique
text = tk.Text(root, wrap="word")
# Valeurs:
#   - "word" = coupe aux mots
#   - "char" = coupe aux caractères
#   - "none" = pas de retour automatique

# font = police
text = tk.Text(root, font=("Courier", 12))

# fg et bg = couleurs
text = tk.Text(root, fg="darkgreen", bg="lightyellow")

# state = état
text = tk.Text(root, state="disabled")
# "disabled" = lecture seule


# === MÉTHODES TEXT ===

# .insert(index, string) = insérer du texte
text.insert("1.0", "Première ligne\n")
# Index format: "ligne.colonne"
# "1.0" = ligne 1, colonne 0 (début)
text.insert(tk.END, "Dernière ligne")
# tk.END = fin du texte

# .get(start, end) = récupérer du texte
contenu = text.get("1.0", tk.END)
# Récupère tout le texte

# .delete(start, end) = supprimer du texte
text.delete("1.0", tk.END)  # Tout supprimer

# .see(index) = scroller vers une position
text.see("10.0")  # Scroller vers ligne 10

# .mark_set(name, index) = placer un marqueur
text.mark_set("insert", "5.0")  # Curseur à ligne 5

# .tag_add(tag, start, end) = ajouter un tag (style)
text.tag_add("highlight", "1.0", "1.10")
text.tag_config("highlight", background="yellow", foreground="red")
# Met en surbrillance les 10 premiers caractères de la ligne 1


# === EXEMPLE: ÉDITEUR SIMPLE ===

import tkinter as tk

def sauvegarder():
    contenu = text.get("1.0", tk.END)
    with open("note.txt", "w") as f:
        f.write(contenu)
    label_status.config(text="Sauvegardé!")

def charger():
    try:
        with open("note.txt", "r") as f:
            contenu = f.read()
        text.delete("1.0", tk.END)
        text.insert("1.0", contenu)
        label_status.config(text="Chargé!")
    except FileNotFoundError:
        label_status.config(text="Fichier introuvable")

root = tk.Tk()
root.title("Éditeur de texte")

frame_buttons = tk.Frame(root)
frame_buttons.pack(pady=5)

btn_save = tk.Button(frame_buttons, text="Sauvegarder", command=sauvegarder)
btn_save.pack(side="left", padx=5)

btn_load = tk.Button(frame_buttons, text="Charger", command=charger)
btn_load.pack(side="left", padx=5)

text = tk.Text(root, width=60, height=20, wrap="word")
text.pack(padx=10, pady=10)

label_status = tk.Label(root, text="", fg="green")
label_status.pack(pady=5)

root.mainloop()


# === WIDGET #5: FRAME (CONTENEUR) ===

# POURQUOI?
# Pour organiser les widgets en groupes
# Créer des sections dans l'interface
# Faciliter le layout complexe

# COMMENT?
frame = tk.Frame(root)
frame.pack()

# Puis ajouter des widgets dans le frame:
label = tk.Label(frame, text="Dans le frame")
label.pack()

# OPTIONS IMPORTANTES:

# width et height = taille en pixels
frame = tk.Frame(root, width=300, height=200)

# bg = couleur de fond
frame = tk.Frame(root, bg="lightgray")

# relief et borderwidth = bordure
frame = tk.Frame(root, relief="ridge", borderwidth=5)

# padx et pady = espacement interne
frame = tk.Frame(root, padx=20, pady=10)


# === EXEMPLE: FRAMES POUR LAYOUT ===

import tkinter as tk

root = tk.Tk()
root.title("Layout avec Frames")

# Frame du haut
frame_top = tk.Frame(root, bg="lightblue", height=100)
frame_top.pack(fill="x")  # Remplit horizontalement

label_title = tk.Label(frame_top, text="TITRE", font=("Arial", 24), bg="lightblue")
label_title.pack(pady=20)

# Frame du milieu
frame_middle = tk.Frame(root, bg="white")
frame_middle.pack(fill="both", expand=True)

label_content = tk.Label(frame_middle, text="Contenu principal", bg="white")
label_content.pack(pady=50)

# Frame du bas
frame_bottom = tk.Frame(root, bg="lightgray", height=50)
frame_bottom.pack(fill="x")

button_quit = tk.Button(frame_bottom, text="Quitter", command=root.quit)
button_quit.pack(pady=10)

root.mainloop()


# === WIDGET #6: CHECKBUTTON (CASE À COCHER) ===

# POURQUOI?
# Pour des options binaires (oui/non, vrai/faux)
# Exemples: accepter les conditions, activer une fonctionnalité

# COMMENT?
var = tk.BooleanVar()
check = tk.Checkbutton(root, text="Accepter", variable=var)
check.pack()

# Récupérer l'état:
etat = var.get()  # True ou False

# OPTIONS IMPORTANTES:

# text = texte à côté de la case
check = tk.Checkbutton(root, text="Je suis d'accord")

# variable = variable liée
var = tk.BooleanVar()
check = tk.Checkbutton(root, text="Option", variable=var)
# var.get() renvoie True si coché, False sinon

# onvalue et offvalue = valeurs personnalisées
var = tk.IntVar()
check = tk.Checkbutton(root, text="Option", variable=var, onvalue=1, offvalue=0)
# var.get() renvoie 1 si coché, 0 sinon

# command = fonction callback
def on_toggle():
    if var.get():
        print("Coché")
    else:
        print("Décoché")

check = tk.Checkbutton(root, text="Toggle", variable=var, command=on_toggle)

# state = état
check = tk.Checkbutton(root, text="Désactivé", state="disabled")


# === MÉTHODES CHECKBUTTON ===

# .select() = cocher
check.select()

# .deselect() = décocher
check.deselect()

# .toggle() = inverser l'état
check.toggle()


# === EXEMPLE: OPTIONS ===

import tkinter as tk

def afficher_options():
    options = []
    if var1.get():
        options.append("Fromage")
    if var2.get():
        options.append("Jambon")
    if var3.get():
        options.append("Tomate")
    
    if options:
        label_result.config(text=f"Sélectionné: {', '.join(options)}")
    else:
        label_result.config(text="Aucune option")

root = tk.Tk()
root.title("Options pizza")

label = tk.Label(root, text="Choisissez vos ingrédients:")
label.pack(pady=10)

var1 = tk.BooleanVar()
var2 = tk.BooleanVar()
var3 = tk.BooleanVar()

check1 = tk.Checkbutton(root, text="Fromage", variable=var1)
check1.pack(anchor="w", padx=20)

check2 = tk.Checkbutton(root, text="Jambon", variable=var2)
check2.pack(anchor="w", padx=20)

check3 = tk.Checkbutton(root, text="Tomate", variable=var3)
check3.pack(anchor="w", padx=20)

button = tk.Button(root, text="Valider", command=afficher_options)
button.pack(pady=10)

label_result = tk.Label(root, text="", fg="blue")
label_result.pack(pady=10)

root.mainloop()


# === WIDGET #7: RADIOBUTTON (BOUTON RADIO) ===

# POURQUOI?
# Pour choisir UNE option parmi plusieurs
# Exemples: sexe (H/F), niveau (débutant/intermédiaire/expert)

# COMMENT?
var = tk.StringVar(value="option1")

radio1 = tk.Radiobutton(root, text="Option 1", variable=var, value="option1")
radio1.pack()

radio2 = tk.Radiobutton(root, text="Option 2", variable=var, value="option2")
radio2.pack()

# Récupérer le choix:
choix = var.get()  # "option1" ou "option2"

# OPTIONS IMPORTANTES:

# text = texte du bouton radio
radio = tk.Radiobutton(root, text="Choix A")

# variable = variable partagée par tous les radio buttons d'un groupe
var = tk.StringVar()
radio1 = tk.Radiobutton(root, text="A", variable=var, value="a")
radio2 = tk.Radiobutton(root, text="B", variable=var, value="b")
# Tous les radios avec la même variable forment un groupe

# value = valeur associée à ce bouton
radio = tk.Radiobutton(root, text="Oui", variable=var, value="yes")
# Quand sélectionné, var.get() renvoie "yes"

# command = fonction callback
def on_select():
    print(f"Sélectionné: {var.get()}")

radio = tk.Radiobutton(root, text="Option", variable=var, value="opt", command=on_select)

# indicatoron = afficher le cercle radio (par défaut True)
radio = tk.Radiobutton(root, text="Bouton", variable=var, value="btn", indicatoron=False)
# Ressemble à un bouton normal au lieu d'un cercle


# === EXEMPLE: QUESTIONNAIRE ===

import tkinter as tk

def valider():
    reponse = var.get()
    if reponse == "paris":
        label_result.config(text="[OK] Correct!", fg="green")
    else:
        label_result.config(text="[X] Incorrect", fg="red")

root = tk.Tk()
root.title("Quiz")

label_question = tk.Label(root, text="Quelle est la capitale de la France?", font=("Arial", 14))
label_question.pack(pady=20)

var = tk.StringVar()

radio1 = tk.Radiobutton(root, text="Paris", variable=var, value="paris", font=("Arial", 12))
radio1.pack(anchor="w", padx=30)

radio2 = tk.Radiobutton(root, text="Lyon", variable=var, value="lyon", font=("Arial", 12))
radio2.pack(anchor="w", padx=30)

radio3 = tk.Radiobutton(root, text="Marseille", variable=var, value="marseille", font=("Arial", 12))
radio3.pack(anchor="w", padx=30)

button = tk.Button(root, text="Valider", command=valider, font=("Arial", 12))
button.pack(pady=20)

label_result = tk.Label(root, text="", font=("Arial", 14))
label_result.pack(pady=10)

root.mainloop()


# === WIDGET #8: LISTBOX (LISTE DÉROULANTE) ===

# POURQUOI?
# Pour afficher une liste d'éléments
# L'utilisateur peut sélectionner un ou plusieurs éléments

# COMMENT?
listbox = tk.Listbox(root)
listbox.pack()

# Ajouter des éléments:
listbox.insert(tk.END, "Élément 1")
listbox.insert(tk.END, "Élément 2")

# OPTIONS IMPORTANTES:

# width et height = taille en caractères
listbox = tk.Listbox(root, width=30, height=10)

# selectmode = mode de sélection
# Valeurs:
#   - "single" = un seul élément (défaut)
#   - "multiple" = plusieurs éléments avec Ctrl+clic
#   - "extended" = plusieurs éléments avec Shift/Ctrl
#   - "browse" = comme single, mais peut défiler avec touches
listbox = tk.Listbox(root, selectmode="multiple")

# font = police
listbox = tk.Listbox(root, font=("Courier", 12))

# fg et bg = couleurs
listbox = tk.Listbox(root, fg="blue", bg="lightyellow")

# activestyle = style de l'élément actif
# Valeurs: "underline", "dotbox", "none"
listbox = tk.Listbox(root, activestyle="dotbox")


# === MÉTHODES LISTBOX ===

# .insert(index, *items) = insérer des éléments
listbox.insert(0, "Premier")  # Insérer au début
listbox.insert(tk.END, "Dernier")  # Insérer à la fin
listbox.insert(tk.END, "Item1", "Item2", "Item3")  # Plusieurs à la fois

# .delete(start, end) = supprimer des éléments
listbox.delete(0)  # Supprimer
le premier
listbox.delete(0, tk.END)  # Tout supprimer

# .get(index) = récupérer un élément
item = listbox.get(0)  # Premier élément

# .curselection() = indices des éléments sélectionnés
selection = listbox.curselection()
# Retourne un tuple d'indices, ex: (0, 2, 5)

# .size() = nombre d'éléments
nb = listbox.size()


# === EXEMPLE: LISTE DE COURSES ===

import tkinter as tk

def ajouter():
    item = entry.get()
    if item:
        listbox.insert(tk.END, item)
        entry.delete(0, tk.END)

def supprimer():
    selection = listbox.curselection()
    for index in reversed(selection):
        listbox.delete(index)

def effacer_tout():
    listbox.delete(0, tk.END)

root = tk.Tk()
root.title("Liste de courses")

frame_top = tk.Frame(root)
frame_top.pack(pady=10)

entry = tk.Entry(frame_top, width=30)
entry.pack(side="left", padx=5)

button_add = tk.Button(frame_top, text="Ajouter", command=ajouter)
button_add.pack(side="left")

listbox = tk.Listbox(root, width=40, height=15, selectmode="multiple")
listbox.pack(padx=10, pady=10)

frame_bottom = tk.Frame(root)
frame_bottom.pack(pady=5)

button_delete = tk.Button(frame_bottom, text="Supprimer sélection", command=supprimer)
button_delete.pack(side="left", padx=5)

button_clear = tk.Button(frame_bottom, text="Tout effacer", command=effacer_tout)
button_clear.pack(side="left", padx=5)

root.mainloop()


# === WIDGET #9: COMBOBOX (LISTE DÉROULANTE) ===

# POURQUOI?
# Menu déroulant pour choisir une option
# Plus compact qu'une Listbox

# COMMENT?
from tkinter import ttk

combo = ttk.Combobox(root, values=["Option 1", "Option 2", "Option 3"])
combo.pack()

# Récupérer la sélection:
choix = combo.get()

# OPTIONS IMPORTANTES:

# values = liste des options
combo = ttk.Combobox(root, values=["Rouge", "Vert", "Bleu"])

# state = état
# Valeurs:
#   - "normal" = modifiable et déroulable
#   - "readonly" = non modifiable, seulement déroulable
#   - "disabled" = grisé
combo = ttk.Combobox(root, values=["A", "B"], state="readonly")

# width = largeur en caractères
combo = ttk.Combobox(root, values=["A", "B", "C"], width=20)


# === MÉTHODES COMBOBOX ===

# .get() = récupérer la valeur
valeur = combo.get()

# .set(value) = définir la valeur
combo.set("Option 2")

# .current() = index de la sélection
index = combo.current()  # Retourne -1 si rien sélectionné

# .current(index) = sélectionner par index
combo.current(0)  # Sélectionner le premier

# <<ComboboxSelected>> = événement de sélection
def on_select(event):
    print(f"Sélectionné: {combo.get()}")

combo.bind("<<ComboboxSelected>>", on_select)


# === EXEMPLE: SÉLECTION PAYS ===

import tkinter as tk
from tkinter import ttk

def afficher_choix():
    pays = combo.get()
    label_result.config(text=f"Vous avez choisi: {pays}")

root = tk.Tk()
root.title("Sélection pays")

label = tk.Label(root, text="Choisissez un pays:", font=("Arial", 12))
label.pack(pady=10)

pays_list = ["France", "Allemagne", "Italie", "Espagne", "Portugal"]
combo = ttk.Combobox(root, values=pays_list, state="readonly", width=20)
combo.pack(pady=10)
combo.current(0)  # Sélectionner par défaut

button = tk.Button(root, text="Valider", command=afficher_choix)
button.pack(pady=10)

label_result = tk.Label(root, text="", fg="blue", font=("Arial", 12))
label_result.pack(pady=10)

root.mainloop()


# === WIDGET #10: SCALE (CURSEUR) ===

# POURQUOI?
# Pour sélectionner une valeur dans une plage
# Exemples: volume, luminosité, zoom

# COMMENT?
scale = tk.Scale(root, from_=0, to=100, orient="horizontal")
scale.pack()

# Récupérer la valeur:
valeur = scale.get()

# OPTIONS IMPORTANTES:

# from_ et to = plage de valeurs (début et fin)
scale = tk.Scale(root, from_=0, to=100)
# ATTENTION: "from_" avec underscore (car "from" est un mot-clé Python)

# orient = orientation
# Valeurs: "horizontal", "vertical"
scale = tk.Scale(root, from_=0, to=100, orient="horizontal")

# length = longueur du curseur (pixels)
scale = tk.Scale(root, from_=0, to=100, length=300)

# label = texte au-dessus
scale = tk.Scale(root, from_=0, to=100, label="Volume")

# tickinterval = intervalle des graduations
scale = tk.Scale(root, from_=0, to=100, tickinterval=10)
# Affiche des marques tous les 10

# resolution = pas de progression
scale = tk.Scale(root, from_=0, to=100, resolution=5)
# Valeurs: 0, 5, 10, 15, ... (saute par 5)

# command = fonction callback (appelée à chaque changement)
def on_change(value):
    print(f"Valeur: {value}")

scale = tk.Scale(root, from_=0, to=100, command=on_change)

# variable = lier à une variable
var = tk.IntVar()
scale = tk.Scale(root, from_=0, to=100, variable=var)
# var.get() renvoie la valeur actuelle

# showvalue = afficher la valeur (True par défaut)
scale = tk.Scale(root, from_=0, to=100, showvalue=False)


# === MÉTHODES SCALE ===

# .get() = récupérer la valeur
valeur = scale.get()

# .set(value) = définir la valeur
scale.set(50)


# === EXEMPLE: CONTRÔLE VOLUME ===

import tkinter as tk

def update_volume(value):
    label_volume.config(text=f"Volume: {value}%")

root = tk.Tk()
root.title("Contrôle volume")

label_title = tk.Label(root, text="Ajustez le volume", font=("Arial", 14))
label_title.pack(pady=10)

scale = tk.Scale(root, from_=0, to=100, orient="horizontal", length=300,
                 label="Volume", command=update_volume, tickinterval=10)
scale.set(50)  # Valeur par défaut
scale.pack(pady=20)

label_volume = tk.Label(root, text="Volume: 50%", font=("Arial", 12), fg="blue")
label_volume.pack(pady=10)

root.mainloop()


# === WIDGET #11: SPINBOX (SÉLECTEUR NUMÉRIQUE) ===

# POURQUOI?
# Pour choisir une valeur numérique avec boutons +/-
# Plus précis qu'un Scale pour des valeurs exactes

# COMMENT?
spinbox = tk.Spinbox(root, from_=0, to=100)
spinbox.pack()

# Récupérer la valeur:
valeur = spinbox.get()

# OPTIONS IMPORTANTES:

# from_ et to = plage
spinbox = tk.Spinbox(root, from_=1, to=10)

# increment = pas d'incrémentation
spinbox = tk.Spinbox(root, from_=0, to=100, increment=5)
# Valeurs: 0, 5, 10, 15, ...

# values = liste de valeurs personnalisées
spinbox = tk.Spinbox(root, values=("Petit", "Moyen", "Grand"))

# width = largeur en caractères
spinbox = tk.Spinbox(root, from_=0, to=100, width=10)

# state = état
spinbox = tk.Spinbox(root, from_=0, to=100, state="readonly")

# wrap = boucler les valeurs (revenir au début après la fin)
spinbox = tk.Spinbox(root, from_=0, to=10, wrap=True)
# Si on dépasse 10, revient à 0

# command = fonction callback
def on_change():
    print(f"Valeur: {spinbox.get()}")

spinbox = tk.Spinbox(root, from_=0, to=100, command=on_change)


# === EXEMPLE: SÉLECTION ÂGE ===

import tkinter as tk

def valider():
    age = spinbox.get()
    label_result.config(text=f"Votre âge: {age} ans")

root = tk.Tk()
root.title("Sélection âge")

label = tk.Label(root, text="Entrez votre âge:", font=("Arial", 12))
label.pack(pady=10)

spinbox = tk.Spinbox(root, from_=0, to=120, width=10, font=("Arial", 12))
spinbox.pack(pady=10)

button = tk.Button(root, text="Valider", command=valider)
button.pack(pady=10)

label_result = tk.Label(root, text="", fg="blue", font=("Arial", 12))
label_result.pack(pady=10)

root.mainloop()


# === WIDGET #12: SCROLLBAR (BARRE DE DÉFILEMENT) ===

# POURQUOI?
# Pour faire défiler du contenu trop grand pour la fenêtre
# Utilisé avec Text, Listbox, Canvas

# COMMENT?
scrollbar = tk.Scrollbar(root)
scrollbar.pack(side="right", fill="y")

text = tk.Text(root, yscrollcommand=scrollbar.set)
text.pack(side="left", fill="both", expand=True)

scrollbar.config(command=text.yview)

# OPTIONS IMPORTANTES:

# orient = orientation
# Valeurs: "vertical" (défaut), "horizontal"
scrollbar = tk.Scrollbar(root, orient="vertical")


# === LIER SCROLLBAR À UN WIDGET ===

# Pour Text:
scrollbar = tk.Scrollbar(root)
scrollbar.pack(side="right", fill="y")

text = tk.Text(root, yscrollcommand=scrollbar.set)
text.pack(side="left", fill="both", expand=True)

scrollbar.config(command=text.yview)

# Pour Listbox:
scrollbar = tk.Scrollbar(root)
scrollbar.pack(side="right", fill="y")

listbox = tk.Listbox(root, yscrollcommand=scrollbar.set)
listbox.pack(side="left", fill="both", expand=True)

scrollbar.config(command=listbox.yview)


# === EXEMPLE: TEXTE AVEC SCROLLBAR ===

import tkinter as tk

root = tk.Tk()
root.title("Texte défilable")

frame = tk.Frame(root)
frame.pack(padx=10, pady=10, fill="both", expand=True)

scrollbar = tk.Scrollbar(frame)
scrollbar.pack(side="right", fill="y")

text = tk.Text(frame, yscrollcommand=scrollbar.set, wrap="word", width=50, height=20)
text.pack(side="left", fill="both", expand=True)

scrollbar.config(command=text.yview)

# Ajouter du contenu
for i in range(100):
    text.insert(tk.END, f"Ligne {i+1}: Ceci est une ligne de texte.\n")

root.mainloop()


# === WIDGET #13: CANVAS (ZONE DE DESSIN) ===

# POURQUOI?
# Pour dessiner des formes, des images, créer des jeux
# Zone graphique libre

# COMMENT?
canvas = tk.Canvas(root, width=400, height=300)
canvas.pack()

# Dessiner un rectangle:
canvas.create_rectangle(50, 50, 150, 100, fill="blue")

# OPTIONS IMPORTANTES:

# width et height = taille en pixels
canvas = tk.Canvas(root, width=500, height=400)

# bg = couleur de fond
canvas = tk.Canvas(root, width=400, height=300, bg="white")

# bd et relief = bordure
canvas = tk.Canvas(root, width=400, height=300, bd=5, relief="sunken")


# === MÉTHODES DE DESSIN ===

# .create_line(x1, y1, x2, y2, ..., options) = ligne
canvas.create_line(0, 0, 200, 100, fill="red", width=3)

# .create_rectangle(x1, y1, x2, y2, options) = rectangle
canvas.create_rectangle(50, 50, 150, 100, fill="blue", outline="black")

# .create_oval(x1, y1, x2, y2, options) = cercle/ellipse
canvas.create_oval(100, 100, 200, 150, fill="green")

# .create_polygon(x1, y1, x2, y2, ..., options) = polygone
canvas.create_polygon(100, 50, 150, 100, 50, 100, fill="yellow", outline="black")

# .create_arc(x1, y1, x2, y2, options) = arc
canvas.create_arc(50, 50, 150, 150, start=0, extent=180, fill="orange")
# start = angle de début (degrés)
# extent = angle de fin

# .create_text(x, y, text, options) = texte
canvas.create_text(200, 150, text="Bonjour!", font=("Arial", 20), fill="purple")

# .create_image(x, y, image, options) = image
img = tk.PhotoImage(file="image.png")
canvas.create_image(100, 100, image=img)

# .delete(item_id) = supprimer un élément
item = canvas.create_rectangle(50, 50, 100, 100, fill="red")
canvas.delete(item)

# .delete("all") = tout supprimer
canvas.delete("all")

# .move(item_id, dx, dy) = déplacer un élément
item = canvas.create_oval(50, 50, 100, 100, fill="blue")
canvas.move(item, 50, 30)  # Déplace de 50px droite, 30px bas

# .coords(item_id) = obtenir les coordonnées
coords = canvas.coords(item)

# .coords(item_id, x1, y1, x2, y2) = modifier les coordonnées
canvas.coords(item, 100, 100, 150, 150)


# === EXEMPLE: DESSIN SIMPLE ===

import tkinter as tk

def dessiner_cercle(event):
    x, y = event.x, event.y
    canvas.create_oval(x-10, y-10, x+10, y+10, fill="blue")

def effacer():
    canvas.delete("all")

root = tk.Tk()
root.title("Dessin")

canvas = tk.Canvas(root, width=600, height=400, bg="white")
canvas.pack()

canvas.bind("<Button-1>", dessiner_cercle)  # Clic gauche

button_clear = tk.Button(root, text="Effacer", command=effacer)
button_clear.pack(pady=10)

root.mainloop()


# === WIDGET #14: MENUBAR (BARRE DE MENU) ===

# POURQUOI?
# Pour créer un menu en haut de la fenêtre
# Comme dans les applications classiques (Fichier, Édition, Aide)

# COMMENT?
menubar = tk.Menu(root)
root.config(menu=menubar)

# Ajouter un menu:
file_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="Fichier", menu=file_menu)

# Ajouter des commandes:
file_menu.add_command(label="Ouvrir", command=ouvrir)
file_menu.add_command(label="Sauvegarder", command=sauvegarder)
file_menu.add_separator()  # Ligne de séparation
file_menu.add_command(label="Quitter", command=root.quit)


# === OPTIONS IMPORTANTES ===

# tearoff = détacher le menu (0 = non, 1 = oui)
menu = tk.Menu(menubar, tearoff=0)

# add_cascade = ajouter un menu déroulant
menubar.add_cascade(label="Fichier", menu=file_menu)

# add_command = ajouter une commande
file_menu.add_command(label="Nouveau", command=nouveau)

# add_checkbutton = ajouter une case à cocher
var = tk.BooleanVar()
view_menu.add_checkbutton(label="Barre d'outils", variable=var)

# add_radiobutton = ajouter un bouton radio
var = tk.StringVar()
view_menu.add_radiobutton(label="Mode clair", variable=var, value="light")
view_menu.add_radiobutton(label="Mode sombre", variable=var, value="dark")

# add_separator = ajouter une ligne de séparation
file_menu.add_separator()


# === EXEMPLE: APPLICATION AVEC MENU ===

import tkinter as tk
from tkinter import messagebox

def nouveau():
    text.delete("1.0", tk.END)
    messagebox.showinfo("Nouveau", "Nouveau document créé")

def ouvrir():
    messagebox.showinfo("Ouvrir", "Fonctionnalité non implémentée")

def sauvegarder():
    contenu = text.get("1.0", tk.END)
    messagebox.showinfo("Sauvegarder", f"Contenu sauvegardé:\n{contenu[:50]}...")

def a_propos():
    messagebox.showinfo("À propos", "Éditeur de texte v1.0")

root = tk.Tk()
root.title("Éditeur avec menu")
root.geometry("600x400")

# Barre de menu
menubar = tk.Menu(root)
root.config(menu=menubar)

# Menu Fichier
file_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="Fichier", menu=file_menu)
file_menu.add_command(label="Nouveau", command=nouveau)
file_menu.add_command(label="Ouvrir", command=ouvrir)
file_menu.add_command(label="Sauvegarder", command=sauvegarder)
file_menu.add_separator()
file_menu.add_command(label="Quitter", command=root.quit)

# Menu Aide
help_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="Aide", menu=help_menu)
help_menu.add_command(label="À propos", command=a_propos)

# Zone de texte
text = tk.Text(root, wrap="word")
text.pack(fill="both", expand=True, padx=5, pady=5)

root.mainloop()


# === WIDGET #15: MESSAGEBOX (BOÎTE DE DIALOGUE) ===

# POURQUOI?
# Pour afficher des messages, demander confirmation
# Alertes, erreurs, informations

# COMMENT?
from tkinter import messagebox

messagebox.showinfo("Titre", "Message d'information")

# TYPES DE MESSAGEBOX:

# showinfo = information
messagebox.showinfo("Info", "Opération réussie")

# showwarning = avertissement
messagebox.showwarning("Attention", "Fichier non sauvegardé")

# showerror = erreur
messagebox.showerror("Erreur", "Fichier introuvable")

# askquestion = question (retourne "yes" ou "no")
reponse = messagebox.askquestion("Question", "Voulez-vous continuer?")
if reponse == "yes":
    print("Oui")

# askyesno = oui/non (retourne True ou False)
reponse = messagebox.askyesno("Confirmation", "Êtes-vous sûr?")
if reponse:
    print("Oui")

# askokcancel = OK/Annuler (retourne True ou False)
reponse = messagebox.askokcancel("Action", "Supprimer le fichier?")

# askretrycancel = Réessayer/Annuler
reponse = messagebox.askretrycancel("Erreur", "Connexion échouée")


# === EXEMPLE: CONFIRMATION QUITTER ===

import tkinter as tk
from tkinter import messagebox

def confirmer_quitter():
    reponse = messagebox.askyesno("Quitter", "Voulez-vous vraiment quitter?")
    if reponse:
        root.destroy()

root = tk.Tk()
root.title("Confirmation")

button = tk.Button(root, text="Quitter", command=confirmer_quitter, width=20, height=3)
button.pack(pady=50)

root.protocol("WM_DELETE_WINDOW", confirmer_quitter)  # Intercepter [X]

root.mainloop()


[OK] GEOMETRY MANAGERS (GESTIONNAIRES DE PLACEMENT)

# === POURQUOI LES GEOMETRY MANAGERS? ===

# Par défaut, les widgets ne s'affichent PAS!
# Tu dois dire à Tkinter OÙ les placer
# 3 méthodes: pack, grid, place

# RÈGLE IMPORTANTE:
# Ne JAMAIS mélanger pack() et grid() dans le MÊME parent!
# Sinon: erreur ou comportement bizarre


# === PACK (EMPILAGE SIMPLE) ===

# POURQUOI?
# Le plus simple, empile les widgets
# Bon pour layouts simples (vertical/horizontal)

# COMMENT?
widget.pack()

# OPTIONS IMPORTANTES:

# side = côté où placer le widget
# Valeurs: "top" (défaut), "bottom", "left", "right"
button1.pack(side="left")
button2.pack(side="left")
button3.pack(side="left")
# Les 3 boutons côte à côte horizontalement

# fill = remplir l'espace disponible
# Valeurs: "none" (défaut), "x" (horizontal), "y" (vertical), "both"
label.pack(fill="x")  # S'étire horizontalement

# expand = prendre tout l'espace disponible (True/False)
frame.pack(fill="both", expand=True)
# Prend tout l'espace de la fenêtre

# padx et pady = espacement externe (pixels)
button.pack(padx=10, pady=20)
# 10px à gauche/droite, 20px en haut/bas

# ipadx et ipady = espacement interne (padding du widget)
button.pack(ipadx=20, ipady=10)
# Agrandit le bouton lui-même

# anchor = ancrage dans l'espace disponible
# Valeurs: "n", "s", "e", "w", "ne", "nw", "se", "sw", "center"
label.pack(anchor="w")  # Aligner à gauche


# === EXEMPLE: LAYOUT AVEC PACK ===

import tkinter as tk

root = tk.Tk()
root.title("Layout Pack")
root.geometry("400x300")

# Frame du haut
frame_top = tk.Frame(root, bg="lightblue", height=50)
frame_top.pack(side="top", fill="x")

label_title = tk.Label(frame_top, text="TITRE", bg="lightblue", font=("Arial", 18))
label_title.pack(pady=10)

# Frame du milieu
frame_middle = tk.Frame(root, bg="white")
frame_middle.pack(side="top", fill="both", expand=True)

label_content = tk.Label(frame_middle, text="Contenu", bg="white")
label_content.pack(pady=50)

# Frame du bas avec boutons
frame_bottom = tk.Frame(root, bg="lightgray", height=50)
frame_bottom.pack(side="bottom", fill="x")

button1 = tk.Button(frame_bottom, text="Bouton 1")
button1.pack(side="left", padx=10, pady=10)

button2 = tk.Button(frame_bottom, text="Bouton 2")
button2.pack(side="left", padx=10, pady=10)

button3 = tk.Button(frame_bottom, text="Bouton 3")
button3.pack(side="left", padx=10, pady=10)

root.mainloop()


# === GRID (GRILLE) ===

# POURQUOI?
# Placement précis en lignes et colonnes
# Idéal pour formulaires, calculatrices

# COMMENT?
widget.grid(row=0, column=0)

# OPTIONS IMPORTANTES:

# row et column = position (commence à 0)
label.grid(row=0, column=0)
entry.grid(row=0, column=1)

# rowspan et columnspan = nombre de lignes/colonnes à occuper
button.grid(row=2, column=0, columnspan=2)
# Occupe 2 colonnes

# sticky = alignement dans la cellule
# Valeurs: "n", "s", "e", "w", ou combinaisons "nsew"
label.grid(row=0, column=0, sticky="w")  # Aligner à gauche
entry.grid(row=0, column=1, sticky="ew")  # S'étirer horizontalement

# padx et pady = espacement externe
widget.grid(row=0, column=0, padx=10, pady=5)

# ipadx et ipady = espacement interne
widget.grid(row=0, column=0, ipadx=20, ipady=10)


# === CONFIGURER LES LIGNES/COLONNES ===

# .rowconfigure(index, weight) = configurer une ligne
root.rowconfigure(0, weight=1)
# weight = poids relatif (0 = taille fixe, >0 = extensible)

# .columnconfigure(index, weight) = configurer une colonne
root.columnconfigure(0, weight=1)
root.columnconfigure(1, weight=2)
# Colonne 1 prend 2 fois plus d'espace que colonne 0


# === EXEMPLE: FORMULAIRE AVEC GRID ===

import tkinter as tk

root = tk.Tk()
root.title("Formulaire")

# Labels
tk.Label(root, text="Nom:").grid(row=0, column=0, sticky="w", padx=10, pady=5)
tk.Label(root, text="Email:").grid(row=1, column=0, sticky="w", padx=10, pady=5)
tk.Label(root, text="Message:").grid(row=2, column=0, sticky="nw", padx=10, pady=5)

# Entries
entry_nom = tk.Entry(root, width=30)
entry_nom.grid(row=0, column=1, padx=10, pady=5)

entry_email = tk.Entry(root, width=30)
entry_email.grid(row=1, column=1, padx=10, pady=5)

text_message = tk.Text(root, width=30, height=5)
text_message.grid(row=2, column=1, padx=10, pady=5)

# Bouton
button_submit = tk.Button(root, text="Envoyer", width=20)
button_submit.grid(row=3, column=0, columnspan=2, pady=10)

root.mainloop()


# === PLACE (POSITIONNEMENT ABSOLU) ===

# POURQUOI?
# Positionnement exact en pixels ou pourcentages
# Utilisé rarement (moins flexible)

# COMMENT?
widget.place(x=50, y=100)

# OPTIONS IMPORTANTES:

# x et y = coordonnées absolues (pixels)
button.place(x=100, y=50)

# relx et rely = coordonnées relatives (0.0 à 1.0)
button.place(relx=0.5, rely=0.5)
# 0.5 = 50% = centre

# width et height = taille absolue
widget.place(x=0, y=0, width=200, height=100)

# relwidth et relheight = taille relative
widget.place(relx=0, rely=0, relwidth=0.5, relheight=0.5)
# 50% de la largeur/hauteur du parent

# anchor = point d'ancrage
# Valeurs: "nw" (par défaut), "n", "ne", "w", "center", "e", "sw", "s", "se"
button.place(relx=0.5, rely=0.5, anchor="center")
# Centre parfaitement le bouton


# === EXEMPLE: POSITIONNEMENT ABSOLU ===

import tkinter as tk

root = tk.Tk()
root.title("Place")
root.geometry("400x300")

# Bouton en haut à gauche
button1 = tk.Button(root, text="Haut gauche")
button1.place(x=10, y=10)

# Bouton au centre
button2 = tk.Button(root, text="Centre")
button2.place(relx=0.5, rely=0.5, anchor="center")

# Label en bas à droite
label = tk.Label(root, text="Bas droite", bg="yellow")
label.place(relx=1.0, rely=1.0, anchor="se", x=-10, y=-10)
# relx=1.0, rely=1.0 = coin bas-droite
# x=-10, y=-10 = offset pour ne pas toucher le bord

root.mainloop()


[OK] ÉVÉNEMENTS ET BINDINGS (INTERACTIONS)

# === QU'EST-CE QU'UN ÉVÉNEMENT? ===

# Événement = action de l'utilisateur ou du système
# Exemples:
#   - Clic souris
#   - Touche clavier
#   - Déplacement souris
#   - Redimensionnement fenêtre

# Binding = lier un événement à une fonction
# Syntaxe: widget.bind("<événement>", fonction_callback)


# === ÉVÉNEMENTS SOURIS ===

# <Button-1> = clic gauche
def clic_gauche(event):
    print(f"Clic gauche à ({event.x}, {event.y})")
    canvas.bind("<Button-1>", clic_gauche)

# <Button-2> = clic molette (ou bouton milieu)
# <Button-3> = clic droit
# <Double-Button-1> = double-clic gauche
# <Triple-Button-1> = triple-clic gauche

# <ButtonRelease-1> = relâchement bouton gauche
def relacher(event):
    print("Bouton relâché")

canvas.bind("<ButtonRelease-1>", relacher)

# <Motion> = déplacement souris
def mouvement(event):
    label.config(text=f"Position: ({event.x}, {event.y})")

canvas.bind("<Motion>", mouvement)

# <Enter> = souris entre dans le widget
# <Leave> = souris quitte le widget
def entrer(event):
    button.config(bg="lightblue")

def sortir(event):
    button.config(bg="lightgray")

button.bind("<Enter>", entrer)
button.bind("<Leave>", sortir)

# <B1-Motion> = glisser avec bouton gauche enfoncé
def glisser(event):
    canvas.create_oval(event.x-2, event.y-2, event.x+2, event.y+2, fill="black")

canvas.bind("<B1-Motion>", glisser)

# <MouseWheel> = molette souris
def molette(event):
    print(f"Molette: {event.delta}")

canvas.bind("<MouseWheel>", molette)


# === ÉVÉNEMENTS CLAVIER ===

# <Key> = n'importe quelle touche
def touche(event):
    print(f"Touche pressée: {event.char}")

root.bind("<Key>", touche)

# <KeyPress> = touche enfoncée (alias de <Key>)
# <KeyRelease> = touche relâchée

# Touches spécifiques:
# <Return> = Entrée
# <Escape> = Échap
# <space> = Espace
# <BackSpace> = Retour arrière
# <Delete> = Suppr
# <Tab> = Tab
# <Up>, <Down>, <Left>, <Right> = Flèches
# <F1>, <F2>, ..., <F12> = Touches fonction
# <Shift_L>, <Shift_R> = Shift gauche/droite
# <Control_L>, <Control_R> = Ctrl gauche/droite
# <Alt_L>, <Alt_R> = Alt gauche/droite

def entree(event):
    print("Entrée pressée")

entry.bind("<Return>", entree)

# Lettres: <a>, <b>, ..., <z>
def touche_a(event):
    print("Touche A")

root.bind("<a>", touche_a)

# Combinaisons:
# <Control-s> = Ctrl+S
# <Alt-F4> = Alt+F4
# <Shift-Return> = Shift+Entrée

def sauvegarder(event):
    print("Ctrl+S pressé")

root.bind("<Control-s>", sauvegarder)


# === PROPRIÉTÉS DE L'OBJET EVENT ===

# event.x, event.y = coordonnées souris (relatives au widget)
# event.x_root, event.y_root = coordonnées absolues (écran)
# event.char = caractère de la touche
# event.keysym = nom symbolique de la touche ("Return", "Escape", etc.)
# event.keycode = code numérique de la touche
# event.num = numéro du bouton souris (1, 2, 3)
# event.delta = valeur molette
# event.widget = widget qui a déclenché l'événement


# === UNBIND (DÉLIER UN ÉVÉNEMENT) ===

# widget.unbind("<événement>")
canvas.unbind("<Button-1>")


# === EXEMPLE: DESSIN AVEC SOURIS ===

import tkinter as tk

class DrawingApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Dessin")
        
        self.canvas = tk.Canvas(root, width=600, height=400, bg="white")
        self.canvas.pack()
        
        self.old_x = None
        self.old_y = None
        
        self.canvas.bind("<Button-1>", self.start_draw)
        self.canvas.bind("<B1-Motion>", self.draw)
        self.canvas.bind("<ButtonRelease-1>", self.stop_draw)
    
    def start_draw(self, event):
        self.old_x = event.x
        self.old_y = event.y
    
    def draw(self, event):
        if self.old_x and self.old_y:
            self.canvas.create_line(self.old_x, self.old_y, event.x, event.y, width=2)
            self.old_x = event.x
            self.old_y = event.y
    
    def stop_draw(self, event):
        self.old_x = None
        self.old_y = None

root = tk.Tk()
app = DrawingApp(root)
root.mainloop()


# === EXEMPLE: CAPTURE TOUCHES CLAVIER ===

import tkinter as tk

def touche_pressee(event):
    label.config(text=f"Touche: {event.keysym} (code: {event.keycode})")

root = tk.Tk()
root.title("Capture clavier")
root.geometry("400x200")

label = tk.Label(root, text="Pressez une touche", font=("Arial", 16))
label.pack(pady=50)

root.bind("<Key>", touche_pressee)

root.mainloop()


[OK] VARIABLES TKINTER (LIAISON DYNAMIQUE)

# === POURQUOI LES VARIABLES TKINTER? ===

# Problème avec variables Python normales:
x = 5
label = tk.Label(root, text=x)
x = 10  # label NE se met PAS à jour!

# Solution: Variables Tkinter
var = tk.IntVar(value=5)
label = tk.Label(root, textvariable=var)
var.set(10)  # label SE met à jour automatiquement!


# === TYPES DE VARIABLES ===

# StringVar = chaîne de caractères
var = tk.StringVar(value="Bonjour")

# IntVar = entier
var = tk.IntVar(value=42)

# DoubleVar = nombre décimal
var = tk.DoubleVar(value=3.14)

# BooleanVar = booléen
var = tk.BooleanVar(value=True)


# === MÉTHODES DES VARIABLES ===

# .get() = récupérer la valeur
valeur = var.get()

# .set(value) = définir la valeur
var.set("Nouvelle valeur")

# .trace("w", callback) = observer les changements
def on_change(*args):
    print(f"Nouvelle valeur: {var.get()}")

var.trace("w", on_change)
# Appelé automatiquement quand var change


# === UTILISATION AVEC WIDGETS ===

# Label avec textvariable:
var = tk.StringVar(value="Texte initial")
label = tk.Label(root, textvariable=var)

# Entry avec textvariable:
var = tk.StringVar()
entry = tk.Entry(root, textvariable=var)
# Maintenant entry et var sont synchronisés

# Checkbutton avec variable:
var = tk.BooleanVar()
check = tk.Checkbutton(root, text="Option", variable=var)

# Radiobutton avec variable:
var = tk.StringVar()
radio1 = tk.Radiobutton(root, text="A", variable=var, value="a")
radio2 = tk.Radiobutton(root, text="B", variable=var, value="b")


# === EXEMPLE: CONVERTISSEUR °C / °F ===

import tkinter as tk

def celsius_to_fahrenheit(*args):
    try:
        celsius = float(var_celsius.get())
        fahrenheit = (celsius * 9/5) + 32
        var_fahrenheit.set(f"{fahrenheit:.2f}")
    except ValueError:
        var_fahrenheit.set("")

root = tk.Tk()
root.title("Convertisseur Température")

tk.Label(root, text="Celsius:").grid(row=0, column=0, padx=10, pady=10)

var_celsius = tk.StringVar()
entry_celsius = tk.Entry(root, textvariable=var_celsius, width=15)
entry_celsius.grid(row=0, column=1, padx=10, pady=10)

var_celsius.trace("w", celsius_to_fahrenheit)

tk.Label(root, text="Fahrenheit:").grid(row=1, column=0, padx=10, pady=10)

var_fahrenheit = tk.StringVar()
label_fahrenheit = tk.Label(root, textvariable=var_fahrenheit, font=("Arial", 12, "bold"))
label_fahrenheit.grid(row=1, column=1, padx=10, pady=10)

root.mainloop()


[OK] DIALOGUES FICHIERS (FILE DIALOGS)

# === POURQUOI? ===

# Pour ouvrir/sauvegarder des fichiers
# Dialogue natif du système d'exploitation

# COMMENT?
from tkinter import filedialog

# Ouvrir un fichier:
fichier = filedialog.askopenfilename()
print(fichier)  # Chemin du fichier


# === TYPES DE DIALOGUES ===

# askopenfilename() = ouvrir UN fichier
fichier = filedialog.askopenfilename(
    title="Ouvrir un fichier",
    filetypes=[("Fichiers texte", "*.txt"), ("Tous les fichiers", "*.*")]
)

# askopenfilenames() = ouvrir PLUSIEURS fichiers
fichiers = filedialog.askopenfilenames()
# Retourne un tuple de chemins

# asksaveasfilename() = sauvegarder un fichier
fichier = filedialog.asksaveasfilename(
    title="Sauvegarder sous",
    defaultextension=".txt",
    filetypes=[("Fichiers texte", "*.txt"), ("Tous les fichiers", "*.*")]
)

# askdirectory() = choisir un dossier
dossier = filedialog.askdirectory(title="Choisir un dossier")


# === OPTIONS IMPORTANTES ===

# title = titre de la fenêtre
# filetypes = types de fichiers acceptés (liste de tuples)
# defaultextension = extension par défaut
# initialdir = dossier initial
fichier = filedialog.askopenfilename(initialdir="/home/user/documents")

# initialfile = nom de fichier initial (pour sauvegarder)
fichier = filedialog.asksaveasfilename(initialfile="document.txt")


# === EXEMPLE: ÉDITEUR AVEC OPEN/SAVE ===

import tkinter as tk
from tkinter import filedialog, messagebox

class Editor:
    def __init__(self, root):
        self.root = root
        self.root.title("Éditeur")
        self.fichier_actuel = None
        
        # Menu
        menubar = tk.Menu(root)
        root.config(menu=menubar)
        
        file_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="Fichier", menu=file_menu)
        file_menu.add_command(label="Ouvrir", command=self.ouvrir)
        file_menu.add_command(label="Sauvegarder", command=self.sauvegarder)
        file_menu.add_command(label="Sauvegarder sous", command=self.sauvegarder_sous)
        file_menu.add_separator()
        file_menu.add_command(label="Quitter", command=root.quit)
        
        # Zone de texte
        self.text = tk.Text(root, wrap="word")
        self.text.pack(fill="both", expand=True)
    
    def ouvrir(self):
        fichier = filedialog.askopenfilename(
            title="Ouvrir",
            filetypes=[("Fichiers texte", "*.txt"), ("Tous", "*.*")]
        )
        
        if fichier:
            try:
                with open(fichier, "r", encoding="utf-8") as f:
                    contenu = f.read()
                self.text.delete("1.0", tk.END)
                self.text.insert("1.0", contenu)
                self.fichier_actuel = fichier
                self.root.title(f"Éditeur - {fichier}")
            except Exception as e:
                messagebox.showerror("Erreur", f"Impossible d'ouvrir:\n{e}")
    
    def sauvegarder(self):
        if self.fichier_actuel:
            self._sauvegarder(self.fichier_actuel)
        else:
            self.sauvegarder_sous()
    
    def sauvegarder_sous(self):
        fichier = filedialog.asksaveasfilename(
            title="Sauvegarder sous",
            defaultextension=".txt",
            filetypes=[("Fichiers texte", "*.txt"), ("Tous", "*.*")]
        )
        
        if fichier:
            self._sauvegarder(fichier)
            self.fichier_actuel = fichier
            self.root.title(f"Éditeur - {fichier}")
    
    def _sauvegarder(self, fichier):
        try:
            contenu = self.text.get("1.0", tk.END)
            with open(fichier, "w", encoding="utf-8") as f:
                f.write(contenu)
            messagebox.showinfo("Succès", "Fichier sauvegardé")
        except Exception as e:
            messagebox.showerror("Erreur", f"Impossible de sauvegarder:\n{e}")

root = tk.Tk()
app = Editor(root)
root.mainloop()


[OK] FENÊTRES SECONDAIRES (TOPLEVEL)

# === POURQUOI? ===

# Pour créer des fenêtres supplémentaires
# Boîtes de dialogue personnalisées, préférences, etc.

# COMMENT?
toplevel = tk.Toplevel(root)
toplevel.title("Fenêtre secondaire")


# === OPTIONS IMPORTANTES ===

# title = titre
toplevel.title("Paramètres")

# geometry = taille et position
toplevel.geometry("300x200+100+100")
# Format: "LARGEURxHAUTEUR+X+Y"
# +X+Y = position sur l'écran

# resizable = redimensionnable
toplevel.resizable(False, False)

# transient = fenêtre modale (liée à une fenêtre parent)
toplevel.transient(root)
# La fenêtre reste au-dessus du parent

# grab_set = bloquer l'interaction avec les autres fenêtres
toplevel.grab_set()
# L'utilisateur doit fermer cette fenêtre avant de continuer


# === MÉTHODES ===

# .destroy() = fermer la fenêtre
toplevel.destroy()

# .withdraw() = cacher la fenêtre
toplevel.withdraw()

# .deiconify() = réafficher la fenêtre
toplevel.deiconify()

# .lift() = mettre au premier plan
toplevel.lift()


# === EXEMPLE: DIALOGUE PERSONNALISÉ ===

import tkinter as tk

def ouvrir_dialogue():
    dialogue = tk.Toplevel(root)
    dialogue.title("Connexion")
    dialogue.geometry("300x150")
    dialogue.resizable(False, False)
    dialogue.transient(root)
    dialogue.grab_set()
    
    tk.Label(dialogue, text="Nom d'utilisateur:").grid(row=0, column=0, padx=10, pady=10, sticky="w")
    entry_user = tk.Entry(dialogue, width=20)
    entry_user.grid(row=0, column=1, padx=10, pady=10)
    
    tk.Label(dialogue, text="Mot de passe:").grid(row=1, column=0, padx=10, pady=10, sticky="w")
    entry_pass = tk.Entry(dialogue, width=20, show="*")
    entry_pass.grid(row=1, column=1, padx=10, pady=10)
    
    def valider():
        user = entry_user.get()
        password = entry_pass.get()
        label_result.config(text=f"Connecté: {user}")
        dialogue.destroy()
    
    button = tk.Button(dialogue, text="Valider", command=valider, width=15)
    button.grid(row=2, column=0, columnspan=2, pady=10)

root = tk.Tk()
root.title("Fenêtre principale")
root.geometry("400x300")

button_open = tk.Button(root, text="Ouvrir dialogue", command=ouvrir_dialogue, width=20, height=2)
button_open.pack(pady=50)

label_result = tk.Label(root, text="", font=("Arial", 12), fg="blue")
label_result.pack(pady=20)

root.mainloop()


[OK] FRAMES AVANCÉS (TTK)

# === TTK = THEMED TK ===

# POURQUOI?
# Widgets avec un style moderne
# Meilleur rendu visuel sur tous les OS

# COMMENT?
from tkinter import ttk

button = ttk.Button(root, text="Bouton moderne")
button.pack()


# === WIDGETS TTK PRINCIPAUX ===

# Button, Label, Entry, Combobox, Checkbutton, Radiobutton
# Progressbar, Separator, Notebook, Treeview

# Utilisation identique aux widgets tk classiques
# Mais avec un rendu plus moderne


# === PROGRESSBAR (BARRE DE PROGRESSION) ===

# POURQUOI?
# Afficher la progression d'une tâche

# COMMENT?
progress = ttk.Progressbar(root, length=300, mode="determinate")
progress.pack()

# Définir la valeur:
progress["value"] = 50  # 50%

# OPTIONS:

# mode = mode de progression
# Valeurs:
#   - "determinate" = pourcentage connu
#   - "indeterminate" = progression inconnue (animation)

# length = longueur (pixels)
# maximum = valeur maximale (100 par défaut)

# EXEMPLE: Barre de chargement

import tkinter as tk
from tkinter import ttk
import time

def charger():
    progress["value"] = 0
    for i in range(101):
        progress["value"] = i
        root.update_idletasks()
        time.sleep(0.02)
    label.config(text="Chargement terminé!")

root = tk.Tk()
root.title("Progressbar")

progress = ttk.Progressbar(root, length=400, mode="determinate")
progress.pack(pady=20)

button = tk.Button(root, text="Charger", command=charger)
button.pack(pady=10)

label = tk.Label(root, text="", fg="green")
label.pack(pady=10)

root.mainloop()


# === NOTEBOOK (ONGLETS) ===

# POURQUOI?
# Organiser le contenu en onglets
# Comme dans un navigateur web

# COMMENT?
from tkinter import ttk

notebook = ttk.Notebook(root)
notebook.pack(fill="both", expand=True)

# Créer des frames pour chaque onglet:
tab1 = tk.Frame(notebook)
tab2 = tk.Frame(notebook)

notebook.add(tab1, text="Onglet 1")
notebook.add(tab2, text="Onglet 2")

# Ajouter du contenu dans les tabs:
label1 = tk.Label(tab1, text="Contenu onglet 1")
label1.pack(pady=50)


# === EXEMPLE: APPLICATION AVEC ONGLETS ===

import tkinter as tk
from tkinter import ttk

root = tk.Tk()
root.title("Application à onglets")
root.geometry("500x400")

notebook = ttk.Notebook(root)
notebook.pack(fill="both", expand=True, padx=10, pady=10)

# Onglet Accueil
tab_home = tk.Frame(notebook)
notebook.add(tab_home, text="Accueil")

label_home = tk.Label(tab_home, text="Bienvenue!", font=("Arial", 24))
label_home.pack(pady=100)

# Onglet Paramètres
tab_settings = tk.Frame(notebook)
notebook.add(tab_settings, text="Paramètres")

tk.Label(tab_settings, text="Nom:").grid(row=0, column=0, padx=10, pady=10, sticky="w")
entry_name = tk.Entry(tab_settings)
entry_name.grid(row=0, column=1, padx=10, pady=10)

var_notif = tk.BooleanVar()
check_notif = tk.Checkbutton(tab_settings, text="Activer les notifications", variable=var_notif)
check_notif.grid(row=1, column=0, columnspan=2, padx=10, pady=10, sticky="w")

# Onglet À propos
tab_about = tk.Frame(notebook)
notebook.add(tab_about, text="À propos")

label_about = tk.Label(tab_about, text="Application v1.0\nCréée avec Tkinter", font=("Arial", 14))
label_about.pack(pady=50)

root.mainloop()


# === TREEVIEW (TABLEAU/ARBORESCENCE) ===

# POURQUOI?
# Afficher des données tabulaires
# Listes avec colonnes, arbres hiérarchiques

# COMMENT?
from tkinter import ttk

tree = ttk.Treeview(root, columns=("col1", "col2"), show="headings")
tree.pack()

# Définir les en-têtes:
tree.heading("col1", text="Colonne 1")
tree.heading("col2", text="Colonne 2")

# Ajouter des lignes:
tree.insert("", "end", values=("Valeur1", "Valeur2"))


# === EXEMPLE: TABLEAU DE DONNÉES ===

import tkinter as tk
from tkinter import ttk

root = tk.Tk()
root.title("Tableau")

# Créer Treeview
columns = ("nom", "age", "ville")
tree = ttk.Treeview(root, columns=columns, show="headings", height=10)

# Définir les en-têtes
tree.heading("nom", text="Nom")
tree.heading("age", text="Âge")
tree.heading("ville", text="Ville")

# Définir les largeurs de colonnes
tree.column("nom", width=150)
tree.column("age", width=80)
tree.column("ville", width=150)

# Ajouter des données
donnees = [
    ("Alice", 25, "Paris"),
    ("Bob", 30, "Lyon"),
    ("Charlie", 22, "Marseille"),
    ("David", 28, "Toulouse"),
]

for row in donnees:
    tree.insert("", "end", values=row)

tree.pack(padx=10, pady=10)

# Scrollbar
scrollbar = ttk.Scrollbar(root, orient="vertical", command=tree.yview)
scrollbar.pack(side="right", fill="y")
tree.config(yscrollcommand=scrollbar.set)

root.mainloop()


[OK] STYLES ET THÈMES (TTK STYLING)

# === POURQUOI? ===

# Personnaliser l'apparence des widgets TTK
# Couleurs, polices, bordures, etc.

# COMMENT?
from tkinter import ttk

style = ttk.Style()
style.theme_use("clam")  # Changer le thème


# === THÈMES DISPONIBLES ===

# .theme_names() = liste des thèmes
themes = style.theme_names()
print(themes)
# ('winnative', 'clam', 'alt', 'default', 'classic', 'vista', 'xpnative')

# .theme_use(name) = utiliser un thème
style.theme_use("clam")


# === PERSONNALISER UN STYLE ===

# .configure(widget_style, option=value)
style.configure("TButton", foreground="blue", background="lightgray", font=("Arial", 12))

# Appliquer à un widget spécifique:
button = ttk.Button(root, text="Bouton stylé", style="TButton")


# === CRÉER UN STYLE PERSONNALISÉ ===

# Créer un nouveau style basé sur un existant:
style.configure("Custom.TButton", 
                foreground="white", 
                background="darkblue", 
                font=("Arial", 14, "bold"),
                borderwidth=0)

button = ttk.Button(root, text="Custom", style="Custom.TButton")


# === EXEMPLE: APPLICATION STYLÉE ===

import tkinter as tk
from tkinter import ttk

root = tk.Tk()
root.title("Application stylée")
root.geometry("400x300")

style = ttk.Style()
style.theme_use("clam")

# Style pour les boutons
style.configure("Accent.TButton",
                foreground="white",
                background="#007bff",
                font=("Arial", 12, "bold"),
                borderwidth=0,
                focuscolor="none")

style.map("Accent.TButton",
          background=[("active", "#0056b3")])

# Contenu
label = tk.Label(root, text="Application Moderne", font=("Arial", 20), bg="white")
label.pack(pady=30)

frame = tk.Frame(root, bg="white")
frame.pack(pady=20)

button1 = ttk.Button(frame, text="Action 1", style="Accent.TButton", width=15)
button1.pack(side="left", padx=10)

button2 = ttk.Button(frame, text="Action 2", style="Accent.TButton", width=15)
button2.pack(side="left", padx=10)

root.config(bg="white")
root.mainloop()


[OK] TIMER ET TÂCHES ASYNCHRONES

# === AFTER (EXÉCUTION DIFFÉRÉE) ===

# POURQUOI?
# Exécuter du code après un délai
# Créer des animations, minuteries

# COMMENT?
# root.after(delay_ms, function)

def hello():
    print("Hello!")

root.after(1000, hello)  # Exécute après 1 seconde (1000 ms)


# === AFTER RÉPÉTÉ (BOUCLE) ===

def horloge():
    import time
    heure = time.strftime("%H:%M:%S")
    label.config(text=heure)
    root.after(1000, horloge)  # Rappeler après 1 sec

horloge()  # Démarrer la boucle


# === ANNULER UN AFTER ===

# .after() retourne un ID
timer_id = root.after(5000, fonction)

# Annuler:
root.after_cancel(timer_id)


# === EXEMPLE: CHRONOMÈTRE ===

import tkinter as tk

class Chronometre:
    def __init__(self, root):
        self.root = root
        self.root.title("Chronomètre")
        
        self.secondes = 0
        self.actif = False
        self.timer_id = None
        
        self.label = tk.Label(root, text="00:00:00", font=("Arial", 48))
        self.label.pack(pady=30)
        
        frame = tk.Frame(root)
        frame.pack(pady=20)
        
        self.button_start = tk.Button(frame, text="Démarrer", command=self.demarrer, width=10)
        self.button_start.pack(side="left", padx=5)
        
        self.button_stop = tk.Button(frame, text="Arrêter", command=self.arreter, width=10)
        self.button_stop.pack(side="left", padx=5)
        
        self.button_reset = tk.Button(frame, text="Réinitialiser", command=self.reinitialiser, width=10)
        self.button_reset.pack(side="left", padx=5)
    
    def demarrer(self):
        if not self.actif:
            self.actif = True
            self.tick()
    
    def arreter(self):
        self.actif = False
        if self.timer_id:
            self.root.after_cancel(self.timer_id)
    
    def reinitialiser(self):
        self.arreter()
        self.secondes = 0
        self.afficher()
    
    def tick(self):
        if self.actif:
            self.secondes += 1
            self.afficher()
            self.timer_id = self.root.after(1000, self.tick)
    
    def afficher(self):
        h = self.secondes // 3600
        m = (self.secondes % 3600) // 60
        s = self.secondes % 60
        temps = f"{h:02d}:{m:02d}:{s:02d}"
        self.label.config(text=temps)

root = tk.Tk()
app = Chronometre(root)
root.mainloop()


[OK] GESTION DES IMAGES (PHOTOIMAGE)

# === PHOTOIMAGE ===

# POURQUOI?
# Afficher des images dans Tkinter
# Supporte: GIF, PNG (formats limités)

# COMMENT?
from tkinter import PhotoImage

img = PhotoImage(file="image.png")
label = tk.Label(root, image=img)
label.pack()

# IMPORTANT: Garder une référence!
label.image = img
# Sinon l'image est supprimée par le garbage collector


# === REDIMENSIONNER UNE IMAGE ===

# .subsample(x, y) = réduire la taille
img = PhotoImage(file="grande_image.png")
img_small = img.subsample(2, 2)  # Divise par 2

# .zoom(x, y) = agrandir
img_big = img.zoom(2, 2)  # Multiplie par 2


# === UTILISER PILLOW (PIL) POUR PLUS DE FORMATS ===

# Pillow supporte: JPEG, PNG, BMP, GIF, TIFF, etc.

# Installation:
# pip install pillow

from PIL import Image, ImageTk

img_pil = Image.open("image.jpg")
img_tk = ImageTk.PhotoImage(img_pil)

label = tk.Label(root, image=img_tk)
label.image = img_tk  # Référence!
label.pack()


# === REDIMENSIONNER AVEC PIL ===

img_pil = Image.open("image.jpg")
img_resized = img_pil.resize((200, 150))  # (largeur, hauteur)
img_tk = ImageTk.PhotoImage(img_resized)


# === EXEMPLE: VISIONNEUSE D'IMAGES ===

import tkinter as tk
from tkinter import filedialog
from PIL import Image, ImageTk

class ImageViewer:
    def __init__(self, root):
        self.root = root
        self.root.title("Visionneuse")
        self.root.geometry("800x600")
        
        self.label_img = tk.Label(root, text="Aucune image", bg="gray")
        self.label_img.pack(fill="both", expand=True)
        
        button_open = tk.Button(root, text="Ouvrir image", command=self.ouvrir)
        button_open.pack(pady=10)
    
    def ouvrir(self):
        fichier = filedialog.askopenfilename(
            title="Choisir une image",
            filetypes=[("Images", "*.png *.jpg *.jpeg *.gif *.bmp"), ("Tous", "*.*")]
        )
        
        if fichier:
            img_pil = Image.open(fichier)
            
            # Redimensionner pour tenir dans la fenêtre
            max_size = (700, 500)
            img_pil.thumbnail(max_size, Image.Resampling.LANCZOS)
            
            img_tk = ImageTk.PhotoImage(img_pil)
            self.label_img.config(image=img_tk, text="")
            self.label_img.image = img_tk

root = tk.Tk()
app = ImageViewer(root)
root.mainloop()


[OK] ANIMATIONS ET MOUVEMENT

# === ANIMER UN WIDGET ===

# Principe: déplacer progressivement avec .place() et .after()

def animer():
    x = 0
    def bouger():
        nonlocal x
        if x < 400:
            canvas.coords(cercle, x, 100, x+50, 150)
            x += 5
            root.after(30, bouger)
    bouger()


# === EXEMPLE: BALLE QUI REBONDIT ===

import tkinter as tk

class BalleRebond:
    def __init__(self, root):
        self.root = root
        self.root.title("Balle rebondissante")
        
        self.canvas = tk.Canvas(root, width=600, height=400, bg="white")
        self.canvas.pack()
        
        self.balle = self.canvas.create_oval(275, 175, 325, 225, fill="red")
        
        self.dx = 5  # Vitesse X
        self.dy = 3  # Vitesse Y
        
        self.animer()
    
    def animer(self):
        # Obtenir position actuelle
        coords = self.canvas.coords(self.balle)
        x1, y1, x2, y2 = coords
        
        # Déplacer
        self.canvas.move(self.balle, self.dx, self.dy)
        
        # Rebondir sur les bords
        if x2 + self.dx >= 600 or x1 + self.dx <= 0:
            self.dx = -self.dx
        if y2 + self.dy >= 400 or y1 + self.dy <= 0:
            self.dy = -self.dy
        
        # Continuer l'animation
        self.root.after(20, self.animer)

root = tk.Tk()
app = BalleRebond(root)
root.mainloop()


[OK] DRAG AND DROP (GLISSER-DÉPOSER)

# === PRINCIPE ===

# 1. Capturer <Button-1> (clic)
# 2. Capturer <B1-Motion> (glisser)
# 3. Capturer <ButtonRelease-1> (relâcher)

# === EXEMPLE: DÉPLACER UN WIDGET ===

import tkinter as tk

class DraggableWidget:
    def __init__(self, root):
        self.root = root
        self.root.title("Drag and Drop")
        self.root.geometry("600x400")
        
        self.label = tk.Label(root, text="Déplacez-moi!", bg="lightblue", 
                              font=("Arial", 16), width=15, height=3)
        self.label.place(x=250, y=150)
        
        self.label.bind("<Button-1>", self.start_drag)
        self.label.bind("<B1-Motion>", self.drag)
        
        self.offset_x = 0
        self.offset_y = 0
    
    def start_drag(self, event):
        self.offset_x = event.x
        self.offset_y = event.y
    
    def drag(self, event):
        x = self.label.winfo_x() + event.x - self.offset_x
        y = self.label.winfo_y() + event.y - self.offset_y
        self.label.place(x=x, y=y)

root = tk.Tk()
app = DraggableWidget(root)
root.mainloop()


[OK] EXEMPLES COMPLETS D'APPLICATIONS

# === CALCULATRICE ===

import tkinter as tk

class Calculatrice:
    def __init__(self, root):
        self.root = root
        self.root.title("Calculatrice")
        self.root.resizable(False, False)
        
        self.expression = ""
        
        # Écran
        self.display = tk.Entry(root, font=("Arial", 20), bd=10, justify="right")
        self.display.grid(row=0, column=0, columnspan=4, sticky="ew")
        
        # Boutons
        boutons = [
            '7', '8', '9', '/',
            '4', '5', '6', '*',
            '1', '2', '3', '-',
            'C', '0', '=', '+'
        ]
        
        row = 1
        col = 0
        for bouton in boutons:
            cmd = lambda x=bouton: self.clic(x)
            tk.Button(root, text=bouton, font=("Arial", 18), width=5, height=2,
                      command=cmd).grid(row=row, column=col, sticky="nsew")
            col += 1
            if col > 3:
                col = 0
                row += 1
        
        # Configuration grille
        for i in range(4):
            root.grid_columnconfigure(i, weight=1)
        for i in range(5):
            root.grid_rowconfigure(i, weight=1)
    
    def clic(self, char):
        if char == 'C':
            self.expression = ""
        elif char == '=':
            try:
                self.expression = str(eval(self.expression))
            except:
                self.expression = "Erreur"
        else:
            self.expression += str(char)
        
        self.display.delete(0, tk.END)
        self.display.insert(0, self.expression)

root = tk.Tk()
app = Calculatrice(root)
root.mainloop()


# === JEU DE MORPION (TIC-TAC-TOE) ===

import tkinter as tk
from tkinter import messagebox

class Morpion:
    def __init__(self, root):
        self.root = root
        self.root.title("Morpion")
        
        self.joueur = "X"
        self.boutons = []
        self.plateau = [["" for _ in range(3)] for _ in range(3)]
        
        for i in range(3):
            row = []
            for j in range(3):
                btn = tk.Button(root, text="", font=("Arial", 30), width=5, height=2,
                                command=lambda r=i, c=j: self.jouer(r, c))
                btn.grid(row=i, column=j)
                row.append(btn)
            self.boutons.append(row)
    
    def jouer(self, row, col):
        if self.plateau[row][col] == "":
            self.plateau[row][col] = self.joueur
            self.boutons[row][col].config(text=self.joueur, state="disabled")
            
            if self.verifier_victoire():
                messagebox.showinfo("Victoire", f"Joueur {self.joueur} a gagné!")
                self.reinitialiser()
            elif self.plateau_plein():
                messagebox.showinfo("Match nul", "Match nul!")
                self.reinitialiser()
            else:
                self.joueur = "O" if self.joueur == "X" else "X"
    
    def verifier_victoire(self):
        # Lignes
        for i in range(3):
            if self.plateau[i][0] == self.plateau[i][1] == self.plateau[i][2] != "":
                return True
        
        # Colonnes
        for j in range(3):
            if self.plateau[0][j] == self.plateau[1][j] == self.plateau[2][j] != "":
                return True
        
        # Diagonales
        if self.plateau[0][0] == self.plateau[1][1] == self.plateau[2][2] != "":
            return True
        if self.plateau[0][2] == self.plateau[1][1] == self.plateau[2][0] != "":
            return True
        
        return False
    
    def plateau_plein(self):
        for row in self.plateau:
            if "" in row:
                return False
        return True
    
    def reinitialiser(self):
        self.joueur = "X"
        self.plateau = [["" for _ in range(3)] for _ in range(3)]
        for i in range(3):
            for j in range(3):
                self.boutons[i][j].config(text="", state="normal")

root = tk.Tk()
app = Morpion(root)
root.mainloop()


[OK] BONNES PRATIQUES

# === 1. STRUCTURER LE CODE AVEC DES CLASSES ===

# MAUVAIS:
import tkinter as tk

root = tk.Tk()
label = tk.Label(root, text="Hello")
label.pack()
# ... code mélangé

# BON:
class MonApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Mon App")
        self.creer_widgets()
    
    def creer_widgets(self):
        self.label = tk.Label(self.root, text="Hello")
        self.label.pack()

root = tk.Tk()
app = MonApp(root)
root.mainloop()


# === 2. SÉPARER LA LOGIQUE DE L'INTERFACE ===

# Garder la logique métier séparée de l'UI

class Calculatrice:
    def __init__(self):
        # Logique uniquement
        pass
    
    def calculer(self, expression):
        return eval(expression)

class CalculatriceUI:
    def __init__(self, root):
        self.calc = Calculatrice()
        # Interface uniquement
        pass


# === 3. UTILISER DES CONSTANTES ===

# MAUVAIS:
button = tk.Button(root, text="OK", bg="blue", fg="white", font=("Arial", 12))

# BON:
COULEUR_PRIMAIRE = "blue"
COULEUR_TEXTE = "white"
POLICE = ("Arial", 12)

button = tk.Button(root, text="OK", bg=COULEUR_PRIMAIRE, 
                   fg=COULEUR_TEXTE, font=POLICE)


# === 4. GÉRER LES ERREURS ===

def ouvrir_fichier():
    try:
        with open("fichier.txt", "r") as f:
            contenu = f.read()
    except FileNotFoundError:
        messagebox.showerror("Erreur", "Fichier introuvable")
    except Exception as e:
        messagebox.showerror("Erreur", f"Erreur: {e}")


# === 5. LIBÉRER LES RESSOURCES ===

def on_closing():
    # Sauvegarder les données
    # Fermer les fichiers/connexions
    root.destroy()

root.protocol("WM_DELETE_WINDOW", on_closing)


[OK] PROBLÈMES COURANTS ET SOLUTIONS

# === PROBLÈME: Widget ne s'affiche pas ===

# CAUSE: Oublier .pack(), .grid() ou .place()

# MAUVAIS:
label = tk.Label(root, text="Hello")
# Label existe mais invisible!

# BON:
label = tk.Label(root, text="Hello")
label.pack()


# === PROBLÈME: Image disparaît ===

# CAUSE: Pas de référence à l'image

# MAUVAIS:
img = PhotoImage(file="image.png")
label = tk.Label(root, image=img)
label.pack()
# img est supprimé par le garbage collector!

# BON:
img = PhotoImage(file="image.png")
label = tk.Label(root, image=img)
label.image = img  # Garder référence
label.pack()


# === PROBLÈME: Fenêtre se ferme immédiatement ===

# CAUSE: Oublier mainloop()

# MAUVAIS:
root = tk.Tk()
label = tk.Label(root, text="Hello")
label.pack()
# Fenêtre se ferme!

# BON:
root = tk.Tk()
label = tk.Label(root, text="Hello")
label.pack()
root.mainloop()


# === PROBLÈME: Application se fige ===

# CAUSE: Opération longue dans le thread principal

# MAUVAIS:
def traitement_long():
    time.sleep(10)  # Bloque l'interface!

button = tk.Button(root, text="Traiter", command=traitement_long)

# BON: Utiliser threading
import threading

def traitement_long():
    time.sleep(10)
    # Mettre à jour l'UI avec root.after()

def lancer():
    thread = threading.Thread(target=traitement_long)
    thread.start()

button = tk.Button(root, text="Traiter", command=lancer)


# === PROBLÈME: Mélanger pack et grid ===

# MAUVAIS:
label.pack()
button.grid(row=0, column=0)  # ERREUR!

# BON: Utiliser uniquement l'un ou l'autre dans le même parent
frame1 = tk.Frame(root)
frame1.pack()
label.pack()  # pack dans frame1

frame2 = tk.Frame(root)
frame2.pack()
button.grid(row=0, column=0)  # grid dans frame2


[OK] RESSOURCES ET DOCUMENTATION

# Documentation officielle Tkinter:
# https://docs.python.org/3/library/tkinter.html

# Tkinter 8.5 reference (très complet):
# https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/index.html

# Tutoriels:
# https://realpython.com/python-gui-tkinter/
# https://www.geeksforgeeks.org/python-tkinter-tutorial/

# Widgets TTK:
# https://docs.python.org/3/library/tkinter.ttk.html

# Exemples de code:
# https://github.com/python/cpython/tree/main/Lib/tkinter

# Stack Overflow (questions/réponses):
# https://stackoverflow.com/questions/tagged/tkinter


# === FIN DU CHEATSHEET TKINTER ===
# Tu as maintenant TOUTES les connaissances pour créer des applications GUI avec Tkinter!
# De la fenêtre simple à l'application complexe avec menus, onglets, animations, etc.
```