Participer au site avec un Tip
Rechercher
 

Améliorations / Corrections

Vous avez des améliorations (ou des corrections) à proposer pour ce document : je vous remerçie par avance de m'en faire part, cela m'aide à améliorer le site.

Emplacement :

Description des améliorations :

Positionnement de widgets avec la méthode grid

Positionnement avec la méthode pack Le widget Canvas


Accès rapide :
Présentation de la stratégie grid
Mise en oeuvre d'une grille simple
Peaufinons notre grille
L'exemple de la calculatrice

Présentation de la stratégie grid

Cette stratégie permet de positionner chacun de vos widgets dans une ou plusieurs cellules d'une grille. La grille est organisée en lignes et en colonnes : vous pouvez, bien entendu, contrôler le nombre de lignes et de colonnes. Il est aussi à noter qu'il est possible qu'un widget occupe plusieurs cellules de la grille. Voici une capture d'écran montrant une grille un peu sophistiquée.

Positionnement des widgets via la méthode grid.

Mise en oeuvre d'une grille simple

Nous allons commencer par un exemple très simple. Nous allons chercher à afficher 4 boutons (2 lignes et 2 colonnes) dans la fenêtre. Voici le code à produire pour réaliser cela.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
#!/usr/bin/python3

from tkinter import Tk, Button


# On définit une classe qui dérive de la classe Tk (la classe de fenêtre).
class MyWindow(Tk):

    def __init__(self):
        # On appelle le constructeur parent
        super().__init__()

        button1 = Button(self, text="Button 1")
        button1.grid(column=0, row=0)

        button2 = Button(self, text="Button 2")
        button2.grid(column=1, row=0)

        button3 = Button(self, text="Button 3")
        button3.grid(column=0, row=1)

        button4 = Button(self, text="Button 4")
        button4.grid(column=1, row=1)

        # On dimensionne la fenêtre (400 pixels de large par 400 de haut).
        self.geometry("400x400")

        # On ajoute un titre à la fenêtre
        self.title("Positionnement de widgets via grid")


# On crée notre fenêtre et on l'affiche
window = MyWindow()
window.mainloop()
Une première grille très basique (2 lignes et 2 colonnes).
la méthode grid est disponible sur n'importe quel widget. Mais en réalité, c'est la classe Grid qui porte cette méthode et par le jeu subtil de l'héritage cette méthode est transmise à toutes les classes de widgets.

Et voici le résultat obtenu en exécutant ce programme.

Une première grille très basique (2 lignes et 2 colonnes).

Comme vous le constater, par défaut, la grille ne cherche pas à occuper 100% de la superficie de la fenêtre. Nous allons tenter de remédier à ce point. Pour cela, il faut indiquer à Tkinter les dimensions de la grille : nombre de lignes et nombre de colonnes. On réalise cela en invoquant les méthodes grid_rowconfigure et grid_columnconfigure.

Afin qu'une colonne (ou une ligne) ne prenne pas l'avantage sur une autre, il est nécessaire de jouer avec la notion de poids (weight). Dans l'exemple suivant, toutes les lignes ont le même poids (1) et idem pour les colonnes. N'oubliez pas qu'en Python, les indices commencent souvent à 0.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
#!/usr/bin/python3

from tkinter import Tk, Button


# On définit une classe qui dérive de la classe Tk (la classe de fenêtre).
class MyWindow(Tk):

    def __init__(self):
        # On appelle le constructeur parent
        super().__init__()

        # Pour que la grille occupe 100% de la fenêtre, il faut que tkinter
        # connaisse à l'avance le nombre de lignes et le nombre de colonnes.
        # Le paramètre weight indique que chaque colonne (et chaque ligne)
        # à le même poids. L'une ne prendra pas l'avantage sur l'autre.
        self.grid_rowconfigure(0, weight=1)
        self.grid_rowconfigure(1, weight=1)
        self.grid_columnconfigure(0, weight=1)
        self.grid_columnconfigure(1, weight=1)

        # On place les 4 boutons dans chacune des cases
        button1 = Button(self, text="Button 1")
        button1.grid(column=0, row=0)

        button2 = Button(self, text="Button 2")
        button2.grid(column=1, row=0)

        button3 = Button(self, text="Button 3")
        button3.grid(column=0, row=1)

        button4 = Button(self, text="Button 4")
        button4.grid(column=1, row=1)

        # On dimensionne la fenêtre (400 pixels de large par 400 de haut).
        self.geometry("400x400")

        # On ajoute un titre à la fenêtre
        self.title("Positionnement de widgets via grid")


# On crée notre fenêtre et on l'affiche
window = MyWindow()
window.mainloop()
La grille s'étend sur toute la fenêtre.

Et voici l'affichage produit par l'exemple ci-dessus.

La grille occupe maintenant toute la fenêtre.

Par défaut, un widget occupe le minimum de place dans sa cellule et il est centré en plein milieu de cette cellule. On peut changer cela grâce au paramètre sticky. Ce paramètre doit contenir une chaîne de caractères composée des caractères n, s, e et w. Ces caractères correspondent au quatre points cardinaux (north, south, east et west).

Selon la valeur du paramètre sticky, le widget sera attaché sur un ou plusieurs côtés de la cellule qui lui est associée. Ainsi, la valeur sticky="n" collera le widget au nord de la cellule. De même, la valeur sticky="w" collera le widget à l'ouest de la cellule.

Plus subtil, la valeur sticky="we" attachera le widget à l'ouest et à l'est de la cellule : il prendra donc toute la largeur de la cellule. La valeur sticky="ns" permettra au widget de prendre toute la hauteur. Enfin, la valeur sticky="nsew" lui permettra de prendre toute la place disponible dans la cellule. Voici un exemple d'utilisation.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
#!/usr/bin/python3

from tkinter import Tk, Button


# On définit une classe qui dérive de la classe Tk (la classe de fenêtre).
class MyWindow(Tk):

    def __init__(self):
        # On appelle le constructeur parent
        super().__init__()

        # Pour que la grille occupe 100% de la fenêtre, il faut que tkinter
        # connaisse à l'avance le nombre de lignes et le nombre de colonnes.
        # Le paramètre weight indique que chaque colonne (et chaque ligne)
        # à le même poids. L'une ne prendra pas l'avantage sur l'autre.
        self.grid_rowconfigure(0, weight=1)
        self.grid_rowconfigure(1, weight=1)
        self.grid_columnconfigure(0, weight=1)
        self.grid_columnconfigure(1, weight=1)

        # On place les 4 boutons dans chacune des cases
        button1 = Button(self, text="Button 1")
        button1.grid(column=0, row=0)

        button2 = Button(self, text="Button 2")
        button2.grid(column=1, row=0, sticky="w")

        button3 = Button(self, text="Button 3")
        button3.grid(column=0, row=1, sticky="ns")

        button4 = Button(self, text="Button 4")
        button4.grid(column=1, row=1, sticky="nswe")

        # On dimensionne la fenêtre (400 pixels de large par 400 de haut).
        self.geometry("400x400")

        # On ajoute un titre à la fenêtre
        self.title("Positionnement de widgets via grid")


# On crée notre fenêtre et on l'affiche
window = MyWindow()
window.mainloop()
Utilisation du paramètre sticky
l'ordre des caractères dans une chaîne sticky n'a aucune importance. Vous pouvez l'écrire comme vous le souhaitez. Le paramètre sticky="nsew" aura donc le même effet que le paramètre sticky="wens".

Et voici l'affichage produit par cet exemple.

La grille occupe maintenant toute la fenêtre.

Peaufinons notre grille

Nous allons maintenant chercher à peaufiner notre grille. Pour ce faire, chaque bouton va recevoir le paramètre sticky="nsew" comme vu précédemment, mais nous allons aussi y rajouter des marges autour de chaque bouton. Les paramètres padx et pady permettent justement de contrôler ces marges.

Dans le même ordre d'idée, nous allons aussi chercher à décorer nos boutons. Voici un petit exemple d'utilisation.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
#!/usr/bin/python3

from tkinter import Tk, Button


# On définit une classe qui dérive de la classe Tk (la classe de fenêtre).
class MyWindow(Tk):

    def __init__(self):
        # On appelle le constructeur parent
        super().__init__()

        # Pour que la grille occupe 100% de la fenêtre, il faut que tkinter
        # connaisse à l'avance le nombre de lignes et le nombre de colonnes.
        # Le paramètre weight indique que chaque colonne (et chaque ligne)
        # à le même poids. L'une ne prendra pas l'avantage sur l'autre.
        self.grid_rowconfigure(0, weight=1)
        self.grid_rowconfigure(1, weight=1)
        self.grid_columnconfigure(0, weight=1)
        self.grid_columnconfigure(1, weight=1)

        # On place les 4 boutons dans chacune des cases
        button1 = Button(self, text="Button 1", bg="#22cbff", fg="white")
        button1.grid(column=0, row=0, sticky="nswe", padx=10, pady=10)

        button2 = Button(self, text="Button 2", bg="#22cbff", fg="white")
        button2.grid(column=1, row=0, sticky="nswe", padx=10, pady=10)

        button3 = Button(self, text="Button 3", bg="#22cbff", fg="white")
        button3.grid(column=0, row=1, sticky="nswe", padx=10, pady=10)

        button4 = Button(self, text="Button 4", bg="#22cbff", fg="white")
        button4.grid(column=1, row=1, sticky="nswe", padx=10, pady=10)

        # On dimensionne la fenêtre (400 pixels de large par 400 de haut).
        self.geometry("400x400")

        # On ajoute un titre à la fenêtre
        self.title("Positionnement de widgets via grid")


# On crée notre fenêtre et on l'affiche
window = MyWindow()
window.mainloop()
On peaufine un peu le look

Voici le résultat produit par cet exemple de code.

La grille occupe maintenant toute la fenêtre.

Comme vous le constatez, le look commence à prendre une part importante du code, des paramètres étant souvent répliqués sur tous les appels de méthodes (constructeur ou la méthode grid, dans notre exemple).

Il est possible de simplifier ce code en déclarant les valeurs des paramètres communs dans un dictionnaire puis de repasser ce dictionnaire à chaque appel. Ainsi, si vous voulez changer les marges de vos boutons ou les couleurs, un seul endroit dans le code devra être impacté. Voici déjà un petit exemple de code et quelques explications suivront.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
#!/usr/bin/python3

from tkinter import Tk, Button


# On définit une classe qui dérive de la classe Tk (la classe de fenêtre).
class MyWindow(Tk):

    def __init__(self):
        # On appelle le constructeur parent
        super().__init__()

        # Pour que la grille occupe 100% de la fenêtre, il faut que tkinter
        # connaisse à l'avance le nombre de lignes et le nombre de colonnes.
        # Le paramètre weight indique que chaque colonne (et chaque ligne)
        # à le même poids. L'une ne prendra pas l'avantage sur l'autre.
        self.grid_rowconfigure(0, weight=1)
        self.grid_rowconfigure(1, weight=1)
        self.grid_columnconfigure(0, weight=1)
        self.grid_columnconfigure(1, weight=1)

        # On définit deux dictionnaires pour les informations de styles
        button_dict = {"bg": "#22cbff", "fg": "white", "font": ("Arial", 25, "bold")}
        grid_dict = {"sticky": "nswe", "padx":10, "pady":10}

        # On place les 4 boutons dans chacune des cases en y injectant nos dictionnaires.
        button1 = Button(self, text="Button 1", **button_dict)
        button1.grid(column=0, row=0, **grid_dict)

        button2 = Button(self, text="Button 2", **button_dict)
        button2.grid(column=1, row=0, **grid_dict)

        button3 = Button(self, text="Button 3", **button_dict)
        button3.grid(column=0, row=1, **grid_dict)

        button4 = Button(self, text="Button 4", **button_dict)
        button4.grid(column=1, row=1, **grid_dict)

        # On dimensionne la fenêtre (400 pixels de large par 400 de haut).
        self.geometry("400x400")

        # On ajoute un titre à la fenêtre
        self.title("Positionnement de widgets via grid")


# On crée notre fenêtre et on l'affiche
window = MyWindow()
window.mainloop()
On rend le code plus facile à maintenir

La syntaxe **button_dict permet d'injecter les valeurs du dictionnaire dans l'appel du constructeur (ou d'une méthode). Cela est possible, car le constructeur de la classe Button accepte un nombre variable de paramètres nommés. Il en va de même pour la méthode grid. J'en ai profité pour rajouter une police de caractères pour les quatre boutons.

L'exemple de la calculatrice

Afin de terminer sur un exemple un peu plus sérieux, je voulais vous proposer le code permettant d'afficher la calculatrice présentée au début de ce chapitre. Si l'idée vous en dit, vous pouvez d'abord remonter en haut de la page pour voir le visuel shouhaité et vous pouvez essayer de réaliser cette calculatrice par vous-même. Ensuite vous pourrez jeter un coup d'oeil à ma proposition ci-dessous ;-)

il est important de comprendre que l'exemple proposé ne prend en charge que l'affichage de la calculatrice et non pas la gestion des événements. Il vous appartient de terminer cette partie si vous souhaiter obtenir une calculatrice fonctionnelle. Cela fera aussi un très bon exercice ;-)
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
 58 
 59 
 60 
 61 
 62 
 63 
 64 
 65 
 66 
 67 
 68 
 69 
 70 
 71 
 72 
 73 
 74 
 75 
 76 
 77 
 78 
 79 
 80 
 81 
 82 
 83 
 84 
 85 
 86 
 87 
 88 
 89 
 90 
 91 
 92 
 93 
 94 
 95 
 96 
 97 
 98 
 99 
 100 
 101 
 102 
 103 
 104 
 105 
 106 
 107 
 108 
 109 
 110 
 111 
 112 
 113 
 114 
#!/usr/bin/python3

from tkinter import Tk, Button, Label


# On définit une classe qui dérive de la classe Tk (la classe de fenêtre).
class MyWindow(Tk):

    def __init__(self):
        # On appelle le constructeur parent
        super().__init__()

        # On prépare une grille de six lignes et 4 colonnes
        # La première ligne cherchera à saturer l'espace restant dans la fenêtre.
        self.grid_rowconfigure(0, weight=1)
        self.grid_rowconfigure(1, weight=0)
        self.grid_rowconfigure(2, weight=0)
        self.grid_rowconfigure(3, weight=0)
        self.grid_rowconfigure(4, weight=0)
        self.grid_rowconfigure(5, weight=0)
        # On force la taille des colonne avec le paramètre uniform. "same_group" est texte libre.
        # Le fait que les 4 colonnes utilisent la même chaîne force la même taille. 
        # Les paramètres weight servent uniquement pour que les 4 colonnes utilisent 100% de la 
        # largeur de la fenêtre.
        self.grid_columnconfigure(0, weight=1, uniform="same_group")
        self.grid_columnconfigure(1, weight=1, uniform="same_group")
        self.grid_columnconfigure(2, weight=1, uniform="same_group")
        self.grid_columnconfigure(3, weight=1, uniform="same_group")

        # On définit quelques éléments de style pour nos boutons.
        default_button_style = {
            "bg": "#595959", "fg": "white", "highlightthickness": 0,
            "font": ("Arial", 25, "bold")
        }
        equal_button_style = default_button_style | {"bg": "#f05a2D"}
        default_button_grid = {"padx": 10, "pady": 10, "sticky": "nsew"}

        # Première ligne
        result_label = Label(self, text="0", anchor='e',     # e => est
                             bg="#a2af77", fg="white", font=("Arial", 25), padx=10)
        result_label.grid(column=0, row=0, columnspan=4, **default_button_grid)

        # Seconde ligne
        mc = Button(self, text="MC", **default_button_style)
        mc.grid(column=0, row=1, **default_button_grid)

        mplus = Button(self, text="M+", **default_button_style)
        mplus.grid(column=1, row=1, **default_button_grid)

        div = Button(self, text="/", **default_button_style)
        div.grid(column=2, row=1, **default_button_grid)

        mul = Button(self, text="*", **default_button_style)
        mul.grid(column=3, row=1, **default_button_grid)

        # Troisième ligne
        d7 = Button(self, text="7", **default_button_style)
        d7.grid(column=0, row=2, **default_button_grid)

        d8 = Button(self, text="8", **default_button_style)
        d8.grid(column=1, row=2, **default_button_grid)

        d9 = Button(self, text="9", **default_button_style)
        d9.grid(column=2, row=2, **default_button_grid)

        sub = Button(self, text="-", **default_button_style)
        sub.grid(column=3, row=2, **default_button_grid)

        # Quatrième ligne
        d4 = Button(self, text="4", **default_button_style)
        d4.grid(column=0, row=3, **default_button_grid)

        d5 = Button(self, text="5", **default_button_style)
        d5.grid(column=1, row=3, **default_button_grid)

        d6 = Button(self, text="6", **default_button_style)
        d6.grid(column=2, row=3, **default_button_grid)

        add = Button(self, text="+", **default_button_style)
        add.grid(column=3, row=3, **default_button_grid)

        # Cinquième ligne
        d1 = Button(self, text="1", **default_button_style)
        d1.grid(column=0, row=4, **default_button_grid)

        d2 = Button(self, text="2", **default_button_style)
        d2.grid(column=1, row=4, **default_button_grid)

        d3 = Button(self, text="3", **default_button_style)
        d3.grid(column=2, row=4, **default_button_grid)

        equal = Button(self, text="=", **equal_button_style)
        equal.grid(column=3, row=4, rowspan=2, **default_button_grid)

        # Cinquième ligne
        d0 = Button(self, text="0", **default_button_style)
        d0.grid(column=0, row=5, columnspan=2, **default_button_grid)

        dot = Button(self, text=".", **default_button_style)
        dot.grid(column=2, row=5, **default_button_grid)

        # On change la couleur de fond et les marges de la fenêtre.
        self.configure(bg="#333333", padx=10, pady=10)

        # On dimensionne la fenêtre (500 pixels de large par 200 de haut).
        self.geometry("400x500")

        # On ajoute un titre à la fenêtre
        self.title("Positionnement de widgets via grid")


# On crée notre fenêtre et on l'affiche
window = MyWindow()
window.mainloop()
Un exemple un peu plus sérieux d'utilisation d'une grille : la calculatrice.
l'opérateur |, utilisé en ligne 34, sert à fusionner deux dictionnaires. Cet opérateur est disponible sur les dictionnaires depuis la version 3.9 de Python. Si vous utilisez une version antérieure, veuillez adapter le code.
Un exemple un peu plus sérieux d'utilisation d'une grille : la calculatrice.
pensez à retailler la fenêtre afin de voir la grille se réorganiser.


Positionnement avec la méthode pack Le widget Canvas