78. Présentation du module Tkinter

Le module Tkinter contient tout ce qui est nécessaire pour utiliser le toolkit Tk. Il va ainsi nous permettre de créer la fenêtre principale de l’application et mettre à disposition des widgets de base.

78.1. Le constructeur Tk

Le constructeur va créer la fenêtre principale (root widget) qui servira de référence à toutes les autres.

Elle doit par conséquent être créée avant les autres widgets.

Voici comment l’invoquer, vous devriez voir apparaître une fenêtre vide :

[root@CentOS_AdminBDD ~]# python
Python 2.6.6 (r266:84292, Jun 18 2012, 14:18:47) 
[GCC 4.4.6 20110731 (Red Hat 4.4.6-3)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import Tkinter
>>> Tkinter.Tk()
<Tkinter.Tk instance at 0x7f10c2f79a28>

Ce qui nous donne :

Figure 72. Fenêtre Tk avec python

Maintenant créez un fichier dans lequel nous affectons l’instance créée par le constructeur à une variable pour obtenir le code suivant :

from Tkinter import *
root = Tk()

Contrairement à une invocation directement dans l’interpréteur la fenêtre ne s’affiche pas, pour cela nous allons utiliser la méthode mainloop().

Cette méthode permet de déclencher l’affichage de la fenêtre au moment choisi et le programme attend alors des évènements. Les évènements correspondent par exemple à un clic de souris ou l’appui sur une touche.

Ce type de fonctionnement est appelé "programmation événementielle" et consiste à associer une fonctionnalité à un événement. Des fonctionnalités sont déjà présentes comme par exemple la fermeture de la fenêtre principale.

Complétons notre exemple :

from Tkinter import *

root = Tk()
root.mainloop()

78.2. Les widgets de base

Les classes widget disposent de méthodes et de paramètres de configuration appelés options. Ces options sont accessibles en utilisant l’instance du widget comme un dictionnaire ou en les passant au constructeur sous forme de mots clés.

Les widgets partagent un certain nombre de méthodes liées au positionnement ou aux évènements mais ils disposent également de spécificités.

Voyons leur fonctionnement.

78.2.1. Widget Button

Ce widget va créer un simple bouton auquel nous pourrons associer une commande (fonctionnalité) au travers d’une option spécifique. Pour illustrer son fonctionnement nous allons réaliser un bouton qui émet un "bip" sonore.

from Tkinter import *
root = Tk()
bouton = Button(root, text="bip")
def action():
	bouton.bell()
	bouton['text']="refaire bip"
	
bouton['command'] = action
bouton.pack()

root.mainloop()

Dans cet exemple nous passons l’option text sous forme de mot clé au constructeur puis nous la modifions dans la fonction action en utilisant l’instance comme un dictionnaire.

L'option command permet de fournir la fonction de callback, c’est à dire la fonction qui sera exécutée quand le bouton sera pressé.

Pour émettre le bip sonore nous utilisons une des méthodes communes à tous les widgets, bell. La liste complète des options et des méthodes pour ce widget se trouve à cette adresse.

Figure 73. Le widget Button

78.2.2. Widget Label

Grâce à lui nous allons pouvoir afficher du texte (non modifiable) ou une image. Il ne dispose pas de méthode propre mais toutes celles communes aux widgets restent disponibles.

Pour gérer son contenu nous allons utiliser ses options :

# -*- coding: iso-8859-15 -*-
from Tkinter import *

root = Tk()
zoneTexte = Label(root, text="""texte de présentation
sur plusieurs lignes""")
zoneTexte.pack()

root.mainloop()

La première ligne du script est importante car elle indique à Python l'encodage qu'il doit utiliser. Par défaut il utilise le mode ASCII et si vous enlevez cette déclaration vous obtiendrez un warning dû au "é".

Figure 74. Le widget Label

L’exemple suivant utilise le widget Label avec une image :

from Tkinter import *
from PIL import Image, ImageTk

root = Tk()
image = Image.open("image.jpg")
photo = ImageTk.PhotoImage(image)

label = Label(root, image=photo)
label.pack()

root.mainloop()

L'option image reçoit un objet de type Image. Tkinter dispose de deux classes dérivées PhotoImage pour les gifs et BitmapImage pour les Bitmaps.

Pour les autres types nous recourrons à la librairie PIL ou plus exactement à son module ImageTk qui dispose de deux classes PhotoImage et BitmapImage qui assureront la conversion.

78.2.3. Widget Message

Il permet d'afficher du texte comme label mais il gère tout seul les retours à la ligne. L'utilisation de l'option width permet d'en définir la largeur et ainsi de conditionner les retours à la ligne.

# -*- coding: utf-8 -*-
from Tkinter import *

root = Tk()

msg = Message(root, width="100",
text="Un texte suffisament long pour être affiché sur plusieurs lignes.")
msg.pack()

root.mainloop()

Ce qui nous donne :

Figure 75. Le widget Message

78.2.4. Widget Checkbutton

Ce widget correspond à une simple case à cocher à laquelle on associe une classe Variable de Tkinter. Les classes dérivées qui peuvent être utilisées sont IntVar, BoleanVar, DoubleVar et StringVar. Pour récupérer les valeurs associées à l’une de ces classes il suffit d’utiliser la méthode get().

Nous allons afficher dans un Label l’état du Checkbutton :

# -*- coding: utf-8 -*-
from Tkinter import *

root = Tk()

variable = IntVar()

def verifieEtat():
	etat['text'] = variable.get()
	
checkbox = Checkbutton(root, variable=variable, text='checkbox', command=verifieEtat)
etat = Label(root)
checkbox.pack()
etat.pack()

root.mainloop()

Nous pouvons préciser les valeurs pour les deux états que peut prendre ce widget :

# -*- coding: utf-8 -*-
from Tkinter import *

root = Tk()

variable = StringVar()

def verifieEtat():
	etat['text'] = variable.get()
	
checkbox = Checkbutton(root, variable=variable, onvalue='vrai', offvalue='faux', text='checkbox', command=verifieEtat)
etat = Label(root)
checkbox.pack()
etat.pack()

root.mainloop()

Figure 76. Le widget Checkbutton

78.2.5. Widget Radiobutton

Il fonctionne de façon similaire au Checkbutton mais au lieu d’avoir une valeur vrai/faux il permet de choisir une valeur parmi plusieurs. Ce widget n’a pas de sens s’il est utilisé seul.

Voici un exemple d’utilisation :

# -*- coding: utf-8 -*-
from Tkinter import *

root = Tk()

texte = StringVar()

def selection():
	affichage['text'] = texte.get()
	
choix1 = Radiobutton(root, text='choix 1', variable=texte, value='premier choix', command=selection)
choix2 = Radiobutton(root, text='choix 2', variable=texte, value='second choix', command=selection)
choix3 = Radiobutton(root, text='choix 3', variable=texte, value='troisième choix', command=selection)

affichage = Label(root)

choix1.pack()
choix2.pack()
choix3.pack()
affichage.pack()

root.mainloop()

Figure 77. Le widget Radiobutton

L'option variable est là aussi de type Variable et l'option value précise la valeur associée (la valeur doit correspondre au type de variable).

78.2.6. Widget Entry

Il s'agit d’un champ de saisie auquel nous allons associer comme pour les checkbutton une variable (généralement StringVar). Comme dans l’exemple précédent nous allons saisir un texte puis l’afficher dans un Label.

# -*- coding: utf-8 -*-
from Tkinter import *

root = Tk()

texte = StringVar()
texte.set('Saisissez un texte')

def valider():
	affichage['text'] = texte.get()
	
inputText = Entry(root, textvariable=texte)
affichage = Label(root)
validation = Button(root, text='valider', command=valider)

inputText.pack()
affichage.pack()
validation.pack()

root.mainloop()

Vous noterez la méthode d’assignation de valeur à un type Variable. Pour ce widget l’option qui permet l’association d’une variable est textvariable.

Figure 78. Le widget Entry

78.2.7. Widget Text

Ce widget est une zone de texte qui permet de saisir du texte sur plusieurs lignes. Elle permet des fonctionnalités beaucoup plus complexes que celles de Entry mais leur utilisation sort du cadre de ce document.

Prenons l’exemple suivant :

# -*- coding: utf-8 -*-
from Tkinter import *

def valider():
	affichage['text'] = texte.get(1.0, END)
	
root = Tk()

texte = Text(root)
texte.insert(END, "Saisissez votre texte.")

affichage = Label(root)
validation = Button(root, text='valider', command=valider)

texte.pack()
affichage.pack()
validation.pack()

root.mainloop()

Pour insérer du texte le principe de fonctionnement est différent nous utilisons la méthode insert qui permet de préciser où inserer le texte. Dans cet exemple nous l’insérons à la fin du texte. Pour l’insérer où se trouve le curseur il faut utiliser la marque INSERT. Ajoutez le code suivant avant root.mainloop() pour voir la différence.

def ajouter():
	texte.insert(INSERT, "Texte supplémentaire")
	
ajout = Button(root, text='ajouter', command=ajouter)
ajout.pack()

Figure 79. Le widget Text

78.2.8. Widget Listbox

Il correspond à une liste déroulante et comme pour Text il est possible de choisir où l’on ajoute l’élément. END correspond à la fin de liste et ACTIVE à l’élément sélectionné.

Nous allons réaliser une sélection multiple dont nous afficherons le résultat dans un Label :

# -*- coding: utf-8 -*-
from Tkinter import *

root = Tk()

def selectionner():
	affichage['text'] = liste.curselection()
	
liste = Listbox(root, selectmode=EXTENDED)

for element in ('premier choix', 'second choix', 'troisieme choix'):
	liste.insert(END, element)
	
affichage = Label(root)

liste.pack()
selection = Button(root, text='Voir la selection', command=selectionner)
affichage.pack()
selection.pack()

root.mainloop()

Les informations sur la sélection sont récupérées avec la méthode curselection qui nous renvoie les indexes des éléments concernés.

Figure 80. Le widget Listbox

78.2.9. Widget Menu

Permet de gérer les menus système d’une application. Créons un menu avec trois niveaux d’entrées :

# -*- coding: utf-8 -*-
from Tkinter import *

root = Tk()
menu = Menu(root)

def commande1():
	affichage['text'] = "commande 1"
def commande2():
	affichage['text'] = "commande 2"

sousmenu = Menu(menu)
soussousmenu = Menu(sousmenu)
affichage = Label(root, text="choisissez votre commande")
menu.add_cascade(label='sous menu', menu=sousmenu)
sousmenu.add_command(label="commande 1", command=commande1)
sousmenu.add_cascade(label='sous menu', menu=soussousmenu)
soussousmenu.add_command(label="commande 2", command=commande2)

root['menu']= menu
affichage.pack()
root.mainloop()

Les différents sous menus sont rattachés à leur parent lors de la création mais pour qu’ils soient visibles nous les déclarons dans le menu parent à l’aide de la commande add_cascade, la méthode add_command va nous permettre d’associer directement une commande à notre menu.

Vous noterez également l’option menu de l’objet Tk utilisée pour associer un menu.

Figure 81. Le widget Menu

78.2.10. Widget Canvas

Ce widget permet de dessiner des formes géométriques simples et de leur associer du texte et des images.

En voici un exemple simple :

# -*- coding: utf-8 -*-
from Tkinter import *

root = Tk()

ZoneDessin = Canvas(root)
ZoneDessin.create_rectangle(10, 10, 200, 100)
ZoneDessin.pack()

root.mainloop()

Figure 82. Le widget Canvas

Pour plus d’information vous pouvez vous reporter à la documentation.

78.2.11. Widget Scale

Permet de fournir une échelle de valeurs dont il sera possible de récupérer la valeur.

# -*- coding: utf-8 -*-
from Tkinter import *

root = Tk()

Regle = Scale(from_=0, to=250)
Regle.pack()

root.mainloop()

Pour récupérer la valeur nous utilisons la méthode get().

78.2.12. Widget Frame

Il joue le rôle d’un conteneur et il sert à regrouper de façon logique d’autres widgets. Nous verrons son utilisation dans le positionnement des éléments.

78.2.13. Widget Toplevel

Ce widget joue également le rôle d’un conteneur mais il se traduit par l’affichage d’une nouvelle fenêtre.

Prenons l’exemple suivant :

# -*- coding: utf-8 -*-
from Tkinter import *

root = Tk()

fenetre = None
age = StringVar()

def creeFenetre():
	global fenetre
	fenetre = Toplevel(root)
	question = Label(fenetre, text="Quel est votre nom ?")
	reponse = Entry(fenetre, textvariable=age)
	validation = Button(fenetre, text="valider", command=repondre)
	question.pack()
	reponse.pack()
	validation.pack()

def repondre():
	affichage['text'] = age.get()
	fenetre.destroy()
	
ouverture = Button(root, text="question", command=creeFenetre)
affichage = Label(root, width=30)

ouverture.pack()
affichage.pack()

root.mainloop()

Il est passé comme premier paramètre à tous ses widgets comme nous le ferions pour la fenêtre principale.

Vous noterez également la méthode destroy() qui permet de fermer la fenêtre.

Ce widget partage d’autres méthodes avec la fenêtre principale détaillée à cette adresse.

Figure 83. Le widget Toplevel

78.3. Positionnement des éléments

Tkinter permet de positionner les widgets de trois façons différentes :

Considérons une interface dans laquelle nous avons une zone de texte (Text non modifiable) pour afficher les messages que nous avons saisis dans un champ texte (Entry) et validés à l’aide d’un bouton "ajouter".

78.3.1. Grid manager

Voici un exemple de réalisation de notre exemple avec ce gestionnaire :

#-*- Coding: utf-8 -*-
from Tkinter import *

root = Tk()

Text(root, state=DISABLED, height=5, width=20).grid(rowspan=2)
Entry(root).grid(row=0, column=1)
Button(root, text="Ajouter").grid(row=1, column=1, sticky=NW)

root.mainloop()

Voici les différentes étapes de la réalisation :

Nous commençons par ajouter la zone de texte à l’aide de la méthode grid(). Aucune information sur la ligne et colonne du tableau n’est fournie. Par conséquent, il sera placé dans la première colonne de la première ligne libre. Nous lui passons l’option rowspan=2 pour indiquer que le widget s’étendra sur 2 lignes.

Le champ texte de saisie est le second élément que nous plaçons sur la grille. Les options passées à la méthode grid() indique qu’il sera placé dans la deuxième colonne de la première ligne.

Enfin nous ajoutons le bouton toujours avec la méthode grid() sur la ligne du dessous. Le widget est par défaut placé au centre de la cellule. L’option sticky=NW va nous permettre de l’aligner en haut à gauche.

78.3.2. Pack manager

Le fonctionnement de ce gestionnaire permet de placer des widgets en fonction de leur conteneur. On indique simplement de quel côté ils seront ajoutés.

Attention il ne faut pas pour un même conteneur mélanger le Pack manager et le Grid manager.

Voici le même exemple avec ce gestionnaire :

# -*- Coding: utf-8 -*-
from Tkinter import *

root = Tk()

Text(root, state=DISABLED, height=5, width=20).pack(side=LEFT)
frame = Frame(root)
frame.pack(side=LEFT)

Entry(frame).pack()
Button(frame, text="Ajouter").pack()

root.mainloop()

Pour placer le champ de texte et le bouton "Valider" l’un en dessous de l’autre, nous avons recourt à une Frame. En effet la méthode pack() va nous permettre de placer le champ texte et le bouton à droite mais l’un à coté de l’autre.

Avec la Frame nous plaçons celle-ci à droite et nous pouvons ensuite placer les éléments qu’elle contient l’un en dessous de l’autre.

L’option fill peut aussi être passée à la méthode pack() et permet de préciser que le widget doit occuper l’espace disponible. Les valeurs possibles sont NONE (par défaut), X (de façon horizontale), Y (de façon verticale), BOTH (les deux).

78.3.3. Place manager

Ce dernier gestionnaire permet de placer les éléments dans le conteneur en utilisant un positionnement absolu ou relatif.

# -*- Coding: utf-8 -*-
from Tkinter import *

root = Tk()

Text(root, state=DISABLED, height=5, width=20).place(anchor=NW, x=0, y=0)
Entry(root).place(anchor=NW, relx=0.5, rely=0.0)
Button(root, text="Ajouter").place(anchor=NW, relx=0.5, rely=0.2)
root.config(height=100, width=300)

root.mainloop()

Le premier widget (Text) est placé de façon absolu dans le conteneur, cela se traduit par l’utilisation des options x et y.

Tout d’abord nous utilisons l’option anchor qui indique le point du conteneur qui va nous servir de référence. Il peut prendre les valeurs N, NE, E, SE, S, SW, W, NW et CENTER.

Ici nous nous basons sur le coin supérieur gauche.

Les options x et y nous indiquent ensuite que l’objet Text sera placé à 0 pixel de ce point en hauteur et en largeur.

Les deux autres widgets sont placés de façon relative par rapport à ce même point. Les valeurs des options relx et rely varient entre 0.0 et 1.0.

0.0 positionne l’objet à gauche pour relx et en haut pour rely. À l’inverse 1.0 le place à droite pour relx et en bas pour rely.

Enfin nous avons utilisé la méthode config() qui permet de modifier les options d’un objet, dans notre cas la taille de la fenêtre de travail.

78.4. Les évènements

Toutes les actions que l’utilisateur peut effectuer sur l’interface vont produire des événements. Tous les widgets disposent d’une méthode bind() qui permet d’associer une commande à un événement.

Le prototype de la fonction est le suivant :

widget.bind(event, callback)

La fonction de callback recevra en paramètre un objet qui décrit l’évènement.

78.4.1. Définition d’un événement

Le modèle d’évènement est de la forme suivante <MODIFIER-MODIFIER-TYPE-DETAIL>. Aucun des champs MODIFIER, TYPE ou DETAIL n’est obligatoire et la définition des évènements se limite généralement au seul TYPE.

78.4.1.1. MODIFIER

Correspond à l’une des touches alt, control, shift ou à un bouton de la souris. Il est utilisé pour donner une information supplémentaire.

Les codes correspondants à ces touches sont respectivement : Alt, parmi lesquels Control et Shift. Ceux pour la souris sont de la forme Bnn est le numéro du bouton :

  1. bouton gauche,

  2. bouton milieu,

  3. bouton droit.

Enfin pour indiquer s’il s’agit d’un double ou d’un triple clic il faut utiliser DOUBLE ou TRIPLE.

78.4.1.2. TYPE

Correspond au type d’événement, sont disponibles :

  • Enter : la souris entre dans le widget;

  • Leave : la souris sort du widget;

  • FocusIn : Le focus est passé au widget;

  • FocusOut : le widget perd le focus;

  • Configure : la taille du widget change;

  • Button ou ButtonPress : un bouton est enfoncé;

  • ButtonRelease : un bouton de souris est relâché;

  • Key : une touche est enfoncée.

Un certain nombre de touches spéciales du clavier disposent également d’un code associé : BackSpace, Tab, Return, Pause, Caps_Lock, Escape, Prior (Page Up), Next (Page Down), End, Home, Print, Insert, Delete, Num_Lock, Scroll_Lock.

Mais aussi les flêches directionnelles : Left, Up, Right, Down et les touches fonction de F1 à F12

78.4.1.3. DETAIL

Apporte également une information supplémentaire, il va par exemple permettre de spécifier le bouton de souris auquel on veut associer un événement.

Par exemple <Button-1> est l’évènement qui traduit que le bouton 1 de la souris est enfoncé.

78.4.2. L’objet event

Lorsqu’un événement est intercepté un objet de ce type est passé à la fonction de callback. Il dispose des attributs suivants qui ne sont renseignés que s’ils ont un sens par rapport à l’évènement :

  • widget : donne l’identifiant de l’instance du widget concerné par l’évenement.

  • x, y : position courante de la souris, elle est donnée en pixels.

  • x_root, y_root : position relative de la souris par rapport au coin supérieur gauche.

  • char : Chaine qui correspond au code caractère (seulement pour les évènements liés au clavier).

  • keysym : symbole de la touche (seulement pour les évènements liés au clavier).

  • keycode : code de la touche (seulement pour les évènements liés au clavier).

  • num : chiffre correspondant au bouton de la souris utilisé (seulement pour les évènements liés à la souris).

  • width, height : nouvelles dimensions de la fenêtre (seulement dans le cas de l’évènement Configure).

  • type : type de l’évènement.

Pour illustrer notre propos voici un script qui va intercepter les évènements clavier, souris (bouton gauche) et redimensionnement de fenêtre.

from Tkinter import *
root = Tk()

def callback(event):
	infosEvt = "char : " + str(event.char) +"\n"
	infosEvt += "keysym : " + str(event.keysym) +"\n"
	infosEvt += "keycode : " + str(event.keycode) +"\n"
	infosEvt += "num : " + str(event.num) +"\n"
	infosEvt += "type : " + str(event.type) +"\n"
	infosEvt += "widget : " + str(event.widget) +"\n"
	infosEvt += "width,height : " + str(event.width) + "," + str(event.height) +"\n"
	infosEvt += "x,y : " + str(event.x) + "," + str(event.y) +"\n"
	infosEvt += "x_root, y_root : " + str(event.x_root) + "," + str(event.y_root)
	affichage['text'] = infosEvt

root.bind("<Key>", callback)
root.bind("<Button-1>", callback)
root.bind("<Configure>", callback)

affichage = Label(root)
affichage.pack()

root.mainloop()