Une fenêtre Tkinter peut sembler simple à construire, puis devenir difficile à organiser dès que plusieurs boutons, champs et zones de contenu doivent rester lisibles quand l’utilisateur redimensionne l’écran. La méthode pack() offre une approche intuitive pour empiler ou aligner les widgets, à condition de bien comprendre le rôle du conteneur, de l’ordre d’insertion et des options d’expansion. Je présente ici une méthode pratique pour construire une interface propre, éviter les conflits avec grid() et choisir le bon gestionnaire selon la situation.
Les repères essentiels pour utiliser pack() efficacement
- pack() place les widgets contre un côté de leur conteneur.
- side choisit la direction, tandis que fill étire le widget.
- expand=True distribue l’espace libre quand la fenêtre grandit.
- Les Frame imbriquées permettent de créer des interfaces plus complexes sans perdre le contrôle.
- Il faut éviter d’utiliser pack et grid dans le même parent.
Comprendre ce que fait réellement pack()
Dans Tkinter, pack() est un gestionnaire de géométrie. Son travail consiste à déterminer la position et la taille des widgets à l’intérieur d’un conteneur, généralement la fenêtre principale ou un objet Frame. Le principe est simple, presque visuel : chaque élément prend place contre un côté disponible, puis l’espace restant est proposé au suivant.
Sans appel à un gestionnaire de géométrie, un widget créé existe bien en mémoire mais n’apparaît pas à l’écran. C’est l’une des premières erreurs que je vois chez les débutants. La création et l’affichage sont deux étapes différentes.
import tkinter as tk
fenetre = tk.Tk()
fenetre.title("Exemple pack")
message = tk.Label(fenetre, text="Bonjour depuis Tkinter")
message.pack()
fenetre.mainloop()
Dans cet exemple, le label est placé en haut de la fenêtre, car la valeur par défaut de side est "top". L’ordre compte également. Si je place un deuxième widget avec pack(), il prendra la prochaine zone disponible dans le même conteneur.
Le parent est tout aussi important que le widget lui-même. Un bouton placé directement dans la fenêtre principale participe au même agencement que les autres widgets de cette fenêtre. Un bouton placé dans un Frame obéit, lui, aux règles de ce cadre. Cette séparation est la base d’une interface qui reste facile à modifier.
Construire une interface claire avec des Frame
Pour une interface très courte, empiler les widgets directement fonctionne bien. Dès qu’il faut distinguer un en-tête, une zone centrale et une barre d’actions, je préfère créer plusieurs Frame. Chaque cadre devient une petite zone indépendante, avec son propre système d’alignement.
import tkinter as tk
from tkinter import ttk
fenetre = tk.Tk()
fenetre.title("Formulaire")
fenetre.geometry("420x220")
entete = ttk.Frame(fenetre, padding=12)
entete.pack(fill="x")
contenu = ttk.Frame(fenetre, padding=12)
contenu.pack(fill="both", expand=True)
actions = ttk.Frame(fenetre, padding=12)
actions.pack(fill="x")
ttk.Label(entete, text="Créer un compte").pack(anchor="w")
ttk.Label(contenu, text="Adresse e-mail").pack(anchor="w")
ttk.Entry(contenu).pack(fill="x", pady=(4, 12))
ttk.Button(actions, text="Annuler").pack(side="right", padx=4)
ttk.Button(actions, text="Valider").pack(side="right", padx=4)
fenetre.mainloop()
Ce modèle fonctionne parce que chaque zone a une responsabilité précise. L’en-tête et la barre d’actions s’étendent horizontalement avec fill="x", tandis que la zone centrale récupère l’espace vertical grâce à expand=True. Je trouve cette combinaison plus robuste que de donner des coordonnées fixes à chaque widget.
Les cadres imbriqués permettent aussi de mélanger les gestionnaires sans créer de conflit. Par exemple, je peux utiliser pack() pour placer trois grandes zones dans la fenêtre, puis utiliser grid() à l’intérieur du cadre qui contient un formulaire. La règle à retenir est stricte : un même parent ne doit pas mélanger pack et grid.
Maîtriser side, fill, expand et les marges
Les résultats deviennent beaucoup plus prévisibles quand on distingue les options qui choisissent une direction de celles qui contrôlent l’espace. Voici les réglages que j’utilise le plus souvent.
| Option | Rôle | Exemple utile |
|---|---|---|
side |
Place le widget contre un côté | side="left" |
fill |
Étire le widget sur un axe | fill="x" |
expand |
Attribue l’espace libre au widget | expand=True |
padx, pady
|
Ajoutent une marge extérieure | pady=8 |
ipadx, ipady
|
Ajoutent un espace intérieur | ipadx=10 |
anchor |
Positionne le widget dans l’espace disponible | anchor="w" |
La différence entre fill et expand
C’est le point qui provoque le plus de confusion. expand=True donne de l’espace au widget, mais ne l’oblige pas à remplir cet espace. fill="x" ou fill="both" contrôle ensuite l’étirement du widget dans la zone reçue.
zone.pack(fill="both", expand=True)
Avec cette instruction, le cadre suit la largeur et la hauteur disponibles. À l’inverse, expand=True sans fill peut laisser le contenu concentré au centre, avec une grande zone vide autour. Ce comportement n’est pas une anomalie, mais il surprend souvent au premier essai.
Gérer l’alignement horizontal
Pour afficher plusieurs boutons sur une même ligne, je choisis généralement side="left" ou side="right". L’ordre des appels devient alors important, car les widgets sont rangés selon leur ordre de packing et selon le côté choisi.
barre = tk.Frame(fenetre)
barre.pack(fill="x", padx=12, pady=12)
tk.Button(barre, text="Ouvrir").pack(side="left", padx=4)
tk.Button(barre, text="Enregistrer").pack(side="left", padx=4)
tk.Button(barre, text="Fermer").pack(side="right", padx=4)
Pour une interface plus équilibrée, j’ajoute souvent un cadre intermédiaire qui absorbe l’espace libre. Cette technique est plus lisible que d’empiler des paramètres jusqu’à obtenir un alignement approximatif.
Pack ou grid pour quel type d’écran
pack() est excellent pour les structures linéaires : une colonne de boutons, une barre supérieure, une zone de contenu et un pied de fenêtre. Il devient moins confortable quand les éléments doivent s’aligner précisément sur plusieurs lignes et colonnes.
| Besoin | Gestionnaire conseillé | Pourquoi |
|---|---|---|
| Empiler des éléments | pack() |
Syntaxe courte et ordre intuitif |
| Créer une barre horizontale | pack() |
side gère directement l’alignement |
| Construire un formulaire | grid() |
Les labels et champs restent alignés |
| Positionner à des coordonnées précises | place() |
Contrôle direct de la position |
| Créer une interface adaptable |
pack() ou grid()
|
Le choix dépend de la structure des zones |
Mon approche est pragmatique. Je commence par pack() pour les blocs principaux, puis je réserve grid() aux cadres qui contiennent une vraie grille de données. Le choix se fait parent par parent, pas nécessairement pour toute l’application.
J’évite en revanche place() pour les interfaces destinées à plusieurs tailles d’écran. Les coordonnées fixes peuvent sembler rapides à écrire, mais elles vieillissent mal lorsque la police, le système d’exploitation ou la taille de la fenêtre change.
Les erreurs qui donnent une interface bancale
Oublier de packer un widget
Créer un bouton ne suffit pas. Il faut appeler pack(), grid() ou place(). Si rien ne s’affiche, je vérifie d’abord ce point avant de chercher un problème dans la boucle principale.
Mélanger pack et grid dans le même parent
Un Frame ne doit pas recevoir certains widgets avec pack() et d’autres avec grid(). Tkinter peut lever une erreur de géométrie ou produire une organisation difficile à prévoir. La solution consiste à créer un nouveau cadre, puis à utiliser un gestionnaire différent à l’intérieur de ce cadre.
Utiliser expand sans fill
Quand un panneau reste petit au centre alors que la fenêtre s’agrandit, le problème vient souvent de cette combinaison. Pour une zone qui doit réellement suivre la fenêtre, j’emploie fill="both", expand=True. Pour une barre qui doit seulement s’étirer en largeur, fill="x" suffit.
Placer les widgets dans le mauvais parent
Un widget peut être correctement configuré et malgré tout apparaître au mauvais endroit si son parent n’est pas celui prévu. Je nomme donc les cadres selon leur rôle, comme entete, contenu et actions. Cette habitude réduit fortement les erreurs de structure dans les scripts plus longs.
Lire aussi : Python str.replace - Maîtrisez le remplacement de sous-chaînes
Bloquer la propagation de la taille
Par défaut, un Frame adapte généralement sa taille à son contenu. La méthode pack_propagate(False) permet de désactiver cette propagation lorsqu’un cadre doit conserver une dimension imposée. Je l’utilise avec modération, car une taille fixe peut créer des widgets coupés ou des espaces vides sur un autre écran.
La règle simple que j’applique pour les écrans Tkinter
Pour démarrer proprement, je découpe la fenêtre en quelques cadres fonctionnels, puis je règle d’abord side, fill et expand. Les marges et l’alignement viennent ensuite, car elles corrigent l’apparence sans changer la logique de la mise en page.
La méthode pack() n’est pas destinée à remplacer tous les autres outils. Elle est particulièrement efficace pour les layouts en bandes, les panneaux empilés et les interfaces simples qui doivent s’adapter au redimensionnement. Dès qu’un écran ressemble à un tableau, je passe à grid() dans un cadre dédié.
Avec cette organisation, le code reste lisible, les changements sont localisés et l’interface résiste mieux aux variations de contenu. C’est précisément là que Tkinter devient agréable à utiliser : non pas quand chaque widget est placé au pixel près, mais quand la structure permet à la fenêtre de s’adapter naturellement.