+++
title = "Tutoriel Tkinter Python"
date = 2024-10-30
description = "Initiation aux interfaces graphiques"
insert_anchor_links = "left"
[taxonomies]
tags = ["programmation", "Python"]
+++

**Prérequis** :
Il faut être un peu à l'aise avec le Python, au moins les variables, fonctions, manipulation des chaînes de caractères.
Si ce n'est pas le cas, [France-IOI](https://www.france-ioi.org/algo/chapters.php) propose une introduction ludique au Python.

Tkinter est une bibliothèque d'interface graphique, qui permet de créer des fenêtres sous Linux, Windows et Mac.
C'est une des plus simples à utiliser en Python.
Seulement elle est un peu vieille et trop peu intuitive, d'où ce tutoriel qui peut servir de mini-référence.

Pour aller plus loin : [TkDocs Tutorial](https://tkdocs.com/tutorial/index.html) (en anglais)

[Ce tutoriel est disponible en PDF.](/o/tuto/tkinter.pdf)
Le code source et les commandes utilisées pour générer le PDF sont disponibles [dans ce dépôt Git](https://git.txmn.tk/tuxmain/jsb-editor).

## Code de base

Créer une fenêtre vide :

```python
import tkinter as tk
from tkinter import ttk

root = tk.Tk()

root.mainloop()
```

Ajouter des boutons dans la fenêtre :

```python
import tkinter as tk
from tkinter import ttk

root = tk.Tk()

bt1 = ttk.Button(root, text="Bonjour")
bt1.grid(column=0, row=0)

bt2 = ttk.Button(root, text="Au revoir")
bt2.grid(column=1, row=0)

root.mainloop()
```

### Positionnement

On peut changer le nombre de colonnes ou de lignes occupées par un widget en ajoutant les paramètres `columnspan` ou `rowspan` en plus de `column` et `row`.

Pour coller un widget au bord de la grille, on peut ajouter `sticky` :

```python
bt2.grid(column=1, row=0, sticky=(tk.W, tk.N))
```

`sticky` prend une liste de 0 à 4 éléments parmi :

* `tk.E` : _East_ = droite
* `tk.N` : _North_ = haut
* `tk.S` : _South_ = bas
* `tk.W` : _West_ = gauche

### Redimensionner la fenêtre

Il faut indiquer quelles lignes et colonnes doivent changer de taille quand la fenêtre change de taille.

Dans cet exemple, ce sont la ligne 1 et la colonne 0.

```python
root.rowconfigure(1, weight=1)
root.columnconfigure(0, weight=1)
```

On peut appeler plusieurs fois ces fonctions pour choisir plusieurs lignes et colonnes, et changer les `weight` pour modifier les proportions.

## Widgets

Tous les widgets doivent être positionnés avec `grid`, de la même manière que ci-dessus.
Sans ça, ils ne seront pas affichés !

### Label

Juste du texte.

```python
label = ttk.Label(root, text="Je suis un texte. Wow.")
```

On peut aussi modifier le texte plus tard :

```python
label_text = tk.StringVar()
label["textvariable"] = label_text
label_text.set("Le nouveau texte. Tout ça pour ça !")
```

### Bouton

Un bouton qu'on peut cliquer.

```python
def onclick():
	print("On a cliqué")

bt = ttk.Button(root, text="Cliquez-moi", command=onclick)
```

### Image

Un bouton ou un label peut afficher une image :

```python
img = tk.PhotoImage(file='image.png')
label["image"] = img
```

### Entrée de texte

Boîte dans laquelle on peut écrire une ligne de texte.

```python
text = tk.StringVar()
entry = ttk.Entry(root, textvariable=text)
```

Plus tard, on peut récupérer le texte :

```python
print("L'utilisateur a écrit :", text.get())
```

### Entrée de texte multiligne

Zone dans laquelle on peut écrire du texte en plusieurs lignes.

```python
text = tk.Text(root)
```

Il y a plusieurs méthodes pour interagir avec le texte :

```python
print(text.get("1.0", "end")) # obtenir tout le texte
print(text.get("2.0", "8.0")) # le texte de la ligne 2 à la ligne 8
text.replace("1.0", "end", "le nouveau texte") # remplacer du texte
text.insert("1.0", "le nouveau texte") # insérer du texte
text.insert("1.0 +42 chars", "le nouveau texte")
text.insert("end -3 lines", "le nouveau texte")
```

Pour mettre en forme certaines parties du texte, on peut utiliser des tags :

```python
text.tag_configure("fond_rouge", background="#ffaaaa")
text.tag_configure("insistance", foreground="#008800", underline=True)

text.insert("1.0", "ce texte sera sur fond rouge", "fond_rouge")
text.tag_add("insistance", "1.3", "1.8")
```

[Liste de tous les styles possibles.](https://tcl.tk/man/tcl8.6/TkCmd/text.htm#M43)

### Barre de défilement

Certains widgets (`Text`) peuvent défiler avec la molette, mais pour afficher la barre de défilement il faut un widget supplémentaire.

```python
scroll = ttk.Scrollbar(root, orient=tk.VERTICAL, command=mon_widget_qui_défile.yview)
scroll.grid(column=1, row=0, sticky=(tk.N, tk.S, tk.E, tk.W))
mon_widget_qui_défile.configure(yscrollcommand=scroll.set)
```

### Cadre

Un cadre qui peut avoir une bordure et contenir d'autres widgets.

```python
frame = ttk.Frame(root, borderwidth=5, relief="ridge", width=200, height=100)
```

Les options ne sont pas obligatoires.

Le cadre contient ses propres lignes et colonnes.
Pour placer des widgets dedans, il suffit de remplacer `root` par `frame` en les créant.

### Barre de menus

La barre de menus en haut de la fenêtre.

```python
root.option_add('*tearOff', False)
menubar = tk.Menu(root)
root['menu'] = menubar

menu_file = tk.Menu(menubar)
menu_edit = tk.Menu(menubar)

menubar.add_cascade(menu=menu_file, label='Fichier')
menubar.add_cascade(menu=menu_edit, label='Édition')

menu_file.add_command(label='Ouvrir', command=on_open)
```

### Canevas

Le canevas est une zone de dessin.

```python
canvas = tk.Canvas(root, background='white')
```

On peut y dessiner des formes :

```python
canvas.create_line(x1, y1, x2, y2)
canvas.create_line(x1, y1, x2, y2, fill="red", width=3, dash=6)
canvas.create_rectangle(x1, y1, x2, y2, fill="red", outline="blue")
canvas.create_oval(x1, y1, x2, y2, fill="red", outline="blue")
```

## Événements

Quand il se passe quelque chose sur un widget, un événement est généré.
On peut écouter les événements de certains types, c'est-à-dire lancer une fonction quand l'action se produit.

Par exemple, on affiche des informations quand une touche du clavier est relâchée dans la zone de texte :

```python
text = tk.Text(root)

def onrelease(event):
	print("On a frappé le clavier !")
	print(event)

text.bind("<KeyRelease>", onrelease)
```

On peut aussi utiliser les infos contenues dans l'événement, par exemple `event.keycode`.

### Liste des événements

* Souris
  * `ButtonPress`: clic enfoncé d'un bouton de la souris
  * `Button-1`: comme `ButtonPress` mais seulement pour le clic gauche
  * `Button-2`: comme `ButtonPress` mais seulement pour le clic molette
  * `Button-3`: comme `ButtonPress` mais seulement pour le clic droit
  * `ButtonRelease`: clic relâché d'un bouton de la souris
  * `Double-1`: double-clic gauche
  * `Double-3`: double-clic droit
  * `Enter`: le pointeur de la souris entre
  * `Leave`: le pointeur de la souris sort
  * `Motion`: le pointeur de la souris bouge dedans
  * `B1-Motion`: comme `Motion` mais avec le clic gauche enfoncé
  * `B2-Motion`: comme `Motion` mais avec le clic molette enfoncé
  * `B3-Motion`: comme `Motion` mais avec le clic droit enfoncé
* Clavier
  * `KeyPress`: appui d'une touche du clavier
  * `KeyRelease`: relâche d'une touche du clavier
* Widget
  * `Configure`: le widget est créé
  * `Expose`: le widget est affiché ou réaffiché
  * `FocusIn`: le widget reçoit le focus
  * `FocusOut`: le widget pert le focus

## Dialogues

On peut ouvrir des fenêtres pour dire ou demander des choses.

### Fichiers

Demander où enregistrer un fichier, quel fichier ouvrir, choisir un dossier.

```python
from tkinter import filedialog

filetypes = [
	("Texte", "*.txt"),
	("Image PNG", "*.png"),
	("Autre", "*.*")
]
chemin = tk.filedialog.asksaveasfilename(filetypes=filetypes)
print(chemin)
```

On peut remplacer `asksaveasfilename` par `askopenfilename` ou `askdirectory`.

### Couleur

Demander de choisir une couleur.

```python
from tkinter import colorchooser

color = colorchooser.askcolor(initialcolor='black')
print(color)
```

On peut aussi indiquer la couleur initiale avec sa notation hexadécimale `"#000000"`.
