Tkinter

Tkinter (Tk interface) est un module intégré à la bibliothèque standard de Python, permettant de créer des interfaces graphiques :

  • des fenêtres,
  • des widgets (boutons, zones de texte, cases à cocher, …),
  • des évènements (clavier, souris, …).

Tkinter est disponible sur Windows et la plupart des systèmes Unix : les interfaces crées avec Tkinter sont donc portables.

Les noms de Tkinter sont disponibles dans le module tkinter.

Documentation complète de Tkinter : http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/index.html

On se propose de réaliser une petite calculatrice …

Les concepts d’une interface graphique

Première interface

# Import des noms du module
from tkinter import *

# Création d'un objet "fenêtre"
fenetre = Tk()

# Titre (Label)
titre = Label(fenetre, text = "L'informatique, c'est fantastique !")

# Affichage du titre
titre.pack()


# Ajout des autres widgets
# .........................


# Démarrage de la boucle Tkinter (à placer à la fin !!!)
fenetre.mainloop()

Remarque : avant Python 3, le module s’appelait Tkinter (avec un T majuscule)

Les objets d’une interface graphique

Les fenêtres

La fenêtre principale d’une application Tkinter se construit avec le constructeur Tk().

Par défaut, elle porte le nom 'tk' et prend la dimension de ce qu’elle contient.

On peut modifier son apparence en passant certaines options lors de sa construction, ou bien après :

fenetre.title("Calculatrice ISN")
fenetre.minsize(300,200)

Les widgets

Les éléments graphiques qui apparaissent dans une fenêtre sont nommés widgets. Certains sont statiques (affichage simple) et d’autres permettent une interaction avec l’utilisateur.

Syntaxe commune :

Nom_du_widget(parent, [autres arguments])

Tout widget doit être « attaché » à une fenêtre : lors de sa construction, cela correspond à l’argument parent.

 

Cadre

Un cadre (frame) est un conteneur pour d’autres widgets. Il permet de réaliser des groupes cohérents de widgets.

L’usage veut que l’on place au moins un un cadre dans la fenêtre de base, pour contenir les widgets de l’application :

cadre = Frame(fenetre)
cadre.pack()

 

Label

Les labels permettent d’afficher du texte dans une fenêtre. Le constructeur Label() accepte de nombreux arguments pour la mise en forme du texte. Mais on peut modifier les options de l’objet après sa construction grâce à la méthode .configure().

Par exemple :

titre.configure(fg = 'red')

Liste complète des options

 

Entrée

Une entrée permet à l’utilisateur de saisir un texte sur une seule ligne.

Le constructeur Entry() accepte de nombreux arguments (options d’apparence, …) dont le plus important est la variable Tkinter, objet de type StringVar, permettant de lier de façon dynamique le contenu de l’entrée au reste du programme.

# Saisie de l'expression mathématique : Entrée (Entry)
expression = StringVar()
expression.set("6*7")     # texte par défaut affiché dans l'entrée
entree = Entry(cadre, textvariable = expression, width = 30)
entree.pack()

 

Pour bien comprendre le comportement d’une variable Tkinter, il suffit de l’utiliser dans un autre widget (ci-dessous avec un Label):

# Résultat du calcul
sortie = Label(cadre, textvariable = expression)
sortie.pack()

Activité
Modifier le contenu de l’entrée et observer le comportement du label « sortie » …

 

Mais pour faire une calculatrice, il faut évaluer l’expression saisie. Le label de sortie ne doit donc pas afficher l’expression mathématique, mais une autre variable contenant le résultat.

resultat = StringVar()
sortie = Label(cadre, textvariable = resultat)
sortie.pack()

Mais par défaut, une StringVar contient "". Il faut donc effectuer une opération de calcul, par exemple à travers une fonction :

def calculer():
   resultat.set(eval(expression.get()))

Remarque : les méthodes .get() et .set() permettent respectivement d’obtenir la valeur de la variable et d’affecter une nouvelle valeur à la variable.

 

Il faut à présent trouver un moyen de lancer cette fonction …

 

Bouton

Les boutons permettent à l’utilisateur d’exécuter une action. Le constructeur Button() attend donc un argument command de type fonction.

# Bouton pour exécuter les calculs
bouton = Button(cadre, text = "Calculer", command = calculer)
bouton.pack()

Après un appui sur le bouton :

 

L’inconvénient de la fonction calculer(), c’est qu’elle provoque une erreur dans les cas où l’expression n’est pas évaluable. Il faut donc prévoir cette éventualité :

def calculer():
    try:
        resultat.set(eval(expression.get()))
    except:
        pass
Activité
Prévoir une modification de la couleur de fond de l'entrée lorsque l'expression n'est pas valide :

 

Correction

 

On peut également créer un bouton permettant de quitter l’application :

# Bouton pour quitter l'application
bouton_quitter = Button(cadre, text = "Quitter", command = fenetre.quit)
bouton_quitter.pack()

 

Bouton radio

Un bouton radio n’est en principe jamais seul : quand l’utilisateur doit faire un choix unique parmi plusieurs possibilités, on utilise typiquement un groupe de boutons radio.

Par exemple, si nous souhaitons contrôler le mode d’affichage du résultat :

# Boutons radio pour sélection du mode d'affichage
mode = StringVar()
bouton1 = Radiobutton(cadre, text="normal", variable=mode, value="n",
                      command = calculer)
bouton2 = Radiobutton(cadre, text="scientifique", variable=mode, value="s",
                      command = calculer)
bouton1.pack()
bouton2.pack()
bouton1.select()
Activité
Sachant que pour obtenir une écriture scientifique d'un nombre n il faut employer l'expression '%e' %n, modifier la fonction calculer();

 

Correction

 

Case à cocher

 

 

Liste

 

 

Arrangement des widgets

Jusqu’à présent, tous les widgets se sont placés les uns au dessous des autres, dans l’ordre de leur création, et centrés horizontalement dans la fenêtre.

Il existe différentes méthodes pour gérer les emplacements des widgets dans une fenêtre :

  • méthode .place() : positionnement absolu (à partir de coordonnées)
  • méthode .pack() : un gestionnaire simple d’empilement de widgets
  • méthode .grid() : arrangement des widgets dans une grille (ou un « tableau »)

 

Pack

Un bon tutoriel pour pack : https://www.pythontutorial.net/tkinter/tkinter-pack/

 

Grid

La méthode .grid() permet d’arranger les widgets dans un « tableau », composé de lignes (rows), de colonnes (columns).

Un widget peut occuper plusieurs lignes et/ou plusieurs colonnes adjacentes (rowspan, columnspan).

Il peut être placé dans différentes positions (sticky ; N : nord, E : est, S : sud, W : ouest) dans sa cellule  : sticky=NE (haut-droite), SE (bas-droite),  SW (bas-gauche), …

Exemples :

titre.grid(row=0, column=0, columnspan=4)
entree.grid(row = 1, column = 0, columnspan = 3, sticky = W+E)
...

Activité :

  • Réaliser l’arrangement de tous les widgets de la calculatrice de la manière suivante :

  • En consultant plus attentivement la documentation complète de la méthode .grid(), améliorer encore le l’affichage et le comportement des widgets (la zone de saisie doit s’ajuster lorsque la taille de la fenêtre est modifiée !).

Documentation complète de grid()

Les événements

 


Fonctionnalités avancées

Surveillance d’une variable Tkinter

Il peut être particulièrement intéressant d’exécuter une action dès qu’une variable Tkinter a été modifiée. Pour cela, il faut lui attacher un observateur à l’aide de la méthode

expression.trace("w", calculer)

Attention : la fonction calculer()  sera appelée avec 3 arguments (pas exploités ici). Il faut donc les prévoir dans sa définition :

def calculer(*args):
   ...

 

Passer des arguments à une commande

Lorsqu’une commande (widgets Button, RadioButton, …), doit comporter un ou plusieurs arguments, on utilise une fonction lambda :

bouton1 = Radiobutton(cadre, text="normal", variable = mode, value = "n", 
                      command = lambda v = item: calculer(v))

 

Fermer correctement une application Tkinter

def quitter():
    fenetre.destroy()

fenetre.protocol('WM_DELETE_WINDOW', quitter)

 

 


Structure de base d’un programme Tkinter

from tkinter import *

###################################################################################
# Classe définissant l'objet représentant la fenêtre principale de l'application
###################################################################################
class Application(Tk):
    def __init__(self):
        Tk.__init__(self)
        self.title("Application du tonnerre !")   # Le titre de la fenêtre
        
        self.minsize(1550,850)      #taille de fenêtre
        
        # Une méthode séparée pour construire le contenu de la fenêtre
        self.createWidgets()

    # Méthode de création des widgets
    def createWidgets(self):
        self.grid()      # Choix du mode d'arrangement
       
        # Création des widgets
        # ...........
        # ...........
        # ...........

        # Un bouton pour quitter l'application
        self.quitButton = Button(self, text = "Quitter", 
                                 command = self.destroy)
        self.quitButton.grid()

    # D'autres méthodes ....
    # ......................

app = Application()
app.mainloop()

 

Autres exemples

Zone de dessin interactive

from tkinter import *

###################################################################################
# Classe de base pour les objets à dessiner
###################################################################################
class Objet():
    def __init__(self, canvas):
        self.canvas  = canvas

class Rectangle(Objet):
    def __init__(self, canvas, x0, y0, w, h, bg0, bg1):
        super().__init__(canvas)

        # On met l'objet dans la liste des objets du canvas
        self.canvas.objets.append(self)
        
        # Paramètres géométriques
        self.x0 = x0
        self.y0 = y0
        self.w = w
        self.h = h

        # Couleurs
        self.bg0 = bg0
        self.bg1 = bg1
        self.bg = self.bg0 # Couleur courante
        
        # Point cliqué
        self.dx = self.dy = None
        
        # On dessine l'objet
        self.draw()


    def est_dedans(self, x, y):
        """ Renvoie True si le point (x,y) est à l'intérieur de l'objet
        """
        return self.x0 < x < self.x0 + self.w and self.y0 < y < self.y0 + self.h
    

    def mousemove(self, event):
        """ Actions réalisées quand la souris est déplacée
        """
        x, y = event.x, event.y

        # Changement de couleur
        if self.est_dedans(x, y):
            self.bg = self.bg1
        else:
            self.bg = self.bg0

        # Drag
        if self.dx is not None: # drag en cours
            self.x0 = x - self.dx
            self.y0 = y - self.dy

        self.draw()


    def mousedown(self, event):
        """ Actions réalisées quand le bouton de la souris est appuyé
        """
        x, y = event.x, event.y

        # Début du drag : on mémorise la position relative du curseur de la souris
        if self.est_dedans(x, y):
            self.dx = event.x - self.x0
            self.dy = event.y - self.y0


    def mouseup(self, event):
        """ Actions réalisées quand le bouton de la souris est relâché
        """
        x, y = event.x, event.y
        # Fin du drag
        self.dx = self.dy = None


    def draw(self):
        if hasattr(self, 'id'): # l'objet a déja été dessiné
            self.canvas.delete(self.id)
        self.id = self.canvas.create_rectangle((self.x0, self.y0), 
                                               (self.x0 + self.w, self.y0 + self.h), 
                                               fill = self.bg)



###################################################################################
# Classe définissant le canvas où sont dessinés les objets
###################################################################################
class Dessin(Canvas):
    def __init__(self, fen, width, height, bg):
        super().__init__(fen, width = width, height=height, bg=bg)
        self.objets = []
        
        # Liaisons des événements à des méthodes
        self.bind('<Motion>', self.mousemove)
        self.bind('<Button-1>', self.mousedown)
        self.bind('<ButtonRelease-1>', self.mouseup)
        
    def draw(self):
        for o in self.objets:
            o.draw()

    def mousemove(self, event):
        for o in self.objets:
            o.mousemove(event)

    def mousedown(self, event):
        for o in self.objets:
            o.mousedown(event)
    
    def mouseup(self, event):
        for o in self.objets:
            o.mouseup(event)


###################################################################################
# Classe définissant l'objet représentant la fenêtre principale de l'application
###################################################################################
class Application(Tk):
    def __init__(self, w, h):
        super().__init__()
        self.title("Faites glisser les figures")   # Le titre de la fenêtre
        self.w = w
        self.h = h
        self.minsize(self.w, self.h)      # taille de fenêtre
        self.geometry(f"{self.w}x{self.h}")
        self.update()

        # Une méthode séparée pour construire le contenu de la fenêtre
        self.createWidgets()

    def get_size(self):
        return self.winfo_width(), self.winfo_height()

    # Méthode de création des widgets
    def createWidgets(self):
        self.grid()      # Choix du mode d'arrangement
       
        # Création des widgets
        self.dessin = Dessin(self, width = self.winfo_width(), 
                                   height = self.winfo_height()-30, 
                                   bg = "ivory")
        a = Rectangle(self.dessin, 10, 20, 100, 200, "white", "blue")
        b = Rectangle(self.dessin, 120, 50, 160, 180, "green", "red")
        self.dessin.grid()
        
        # Un bouton pour quitter l'application
        self.quitButton = Button(self, text = "Quitter", 
                                 command = self.destroy)
        self.quitButton.grid()


app = Application(600, 400)
app.mainloop()

 

 

Vous aimerez aussi...

6 réponses

  1. attari dit :

    Excellente présentation !

  2. mathilde dit :

    Bonjour,
    Merci pour cet excellent cours. Il me permet de comprendre Tkinder et de l’utiliser pour mon propre projet. Un grand merci. 
    Par contre, j’ai passé pas mal de temps à réaliser qu’après
    « resultat = StringVar()
    sortie = Label(cadre, textvariable = resultat)  »
    il fallait ajouter :
    sortie.pack()
    Je le note au cas où d’autres étudiants seraient passés à coté également.

    Merci encore pour ce cours !
    Mathilde

  3. GUICHARD dit :

    Bonjour, 
    Actuellement étudiant en deuxième année de Master Ingénierie et Ergonomie de l’Activité Physique, je travaille sur la conception d’un miroir connecté permettant d’afficher différentes informations.
    J’utilise un Raspberry Pi 3 et le langage python pour réaliser l’interface graphique.
    Je souhaiterais savoir comment afficher un widget météo (html) sur une interface réalisé en python. Est ce que Tkinter permet de réaliser ceci? Dois-je utiliser une autre bibliothèque?
    Je vous remercie par avance pour votre réponse.
    Romain

    • cfaury dit :

      Bonjour
      Je ne suis pas sûr de bien comprendre …
      Si vous réalisez un serveur web (client+serveur), pour afficher un widget sur un page html, il ne faut pas utiliser tkinter, car ce dernier est une interface graphique pour faire des applications « locales ».
      Je vous conseille plutôt de générer du code html5 avec python/flask (coté serveur)
      Ou bien d’utiliser du javascript.

      Si vous réalisez une application « locale », tkinter peut convenir, mais tout dépend de ce que vous voulez afficher dans votre widget.

      CF

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *