Mini-cours de Python

Tkinter - en détail

Documentation de référence

Pour créer une application avec une interface graphique, on peut utiliser le module tkinter

Outre Python, ce module peut être utilisé par les langages : Tcl, Ruby et Perl.

Depuis Python 3 (depuis +/- 2009), le module s'appelle tkinter. Or, avant, il s'appelait Tkinter ... => Pour que les anciens codes fonctionnent, il faut les corriger en remplaçant la majuscule par une minuscule

Depuis Python 3.7, lors de son installation, le module tkinter y est compris.

PyQt5, abordé plus tard, permet de réaliser des interfaces parfaites. De plus, l'apprentissage de Qt5 servira avec d'autres langages de programmation tel que C++.

Avec Tkinter, l'interface sera "vieille", mais aussi plus légère et plus rapide.

Exécuter python -m tkinter depuis la ligne de commande ouvre une fenêtre de démonstration d'une interface Tk simple, vous indiquant que tkinter est correctement installé sur votre système et indiquant également quelle version de Tcl/Tk est installée; vous pouvez donc lire la documentation Tcl/Tk.

Le module tkinter contient les modules suivants : (par ordre alphabétique)

Depuis tkinter 8.5, lors de son installation, l'extension ttk est comprise. Cette extension permet d'améliorer l'apparence des widgets (objets graphiques : bouton, ...). Documentation

Il existe des tutoriels :

5 lignes de code suffisent pour qu'une fois interprétées s'affiche, par exemple :

Il s'agit d'un logiciel puisque vous pouvez cliquer sur la croix de fermeture (et vous pouvez l'agrandir pour qu'il prenne toute la surface de l'écran, en cliquant sur le carré à gauche de cette croix)

import tkinter         # import de tkinter
fP = tkinter.Tk ()     # création de la fenêtre principale (fP)
obj = tkinter.Label (text = "Bonjour, Thibaut !")
obj.pack ()            # on ajoute l'objet à la fenêtre principale
fP.mainloop ()         # écouter toute action sur la fenêtre 

Boîtes de dialogue

Documentation sur les boîtes de dialogue

Avant de créer un logiciel, sachons que le module tkinter offre des objets tout faits, très utiles, qui peuvent être utilisés aussi dans un programme en ligne de commande ...

Ainsi, on peut utiliser brièvement une interface graphique par commodité (avant de retourner en ligne de commande). Ces objets sont des boîtes de dialogue qui retournent le choix de l'utilisateur. Ce retour est placé dans une variable.

Coder "manuellement" de tels retours d'information serait trop complexe. Ces boîtes de dialogue diminue le nombre de lignes de code et, par conséquent, le nombre d'erreurs. Le développement du projet est également plus rapide et l'utilisateur est ravi par l'apparence connue (au lieu d'apprendre un interface spartiate).

Sélection d'un fichier

À gauche de la croix de fermeture manquent deux icônes ...
La filedialg box est améliorable.
import os
import platform
import tkinter # --------------------------
from tkinter import filedialog # ----------
from tkinter import messagebox

# utilisation d'une boîte de dialogue -----------------------------------------
fP = tkinter.Tk () # création de la fenêtre principale
fP.withdraw() # Ne pas l'afficher

gPath = os.path.abspath("."); SEP="/"
if platform.system() == "Windows": SEP="\\"#; gPath=os.path.abspath("C:\\Users");
#else: gPath = os.path.abspath("/home")
gFileTypes=(('TEXT files','*.txt'),('HTML files','*.htm'),('All files','*.*'))

filename = filedialog.askopenfilename(initialdir=gPath,
                                  filetypes=gFileTypes,
                                  title="Sélection d'un fichier")
if len(filename)==0:
  messagebox.showwarning("","Pas de fichier sélectionné !")
  # sortir du programme ?
else:
  CC = filename.replace("/",SEP)

fP.destroy() # destruction de la fenêtre (= fermeture du GUI)
# fin de l'utilisation d'une boîte de dialogue --------------------------------

# reste du code du programme
print(CC) # chemin complet du fichier choisi

askopenfilename() retourne une string (vide si l'utilisateur a cliqué sur "Annuler").

askopenfilenames() retourne l'ensemble des fichiers sélectionnés sous la forme d'un tuple (() ou non).

Les retours sont des strings représentant le chemin complet du dossier, du fichier et des fichiers.

Les options facultatives : (extrait de la documentation sur filedialog)

Exemples de open-file-dialog

Sélection d'un dossier

À gauche de la croix de fermeture manquent deux icônes ...
La filedialg box est améliorable.
import os
import platform
import tkinter # --------------------------
from tkinter import filedialog # ----------

# utilisation d'une boîte de dialogue -----------------------------------------
fP = tkinter.Tk () # création de la fenêtre principale
fP.withdraw() # Ne pas l'afficher

gPath = os.path.abspath("."); SEP="/"
if platform.system() == "Windows": SEP="\\"#; gPath=os.path.abspath("C:\\Users");
#else: gPath = os.path.abspath("/home")

dossier  = filedialog.askdirectory(initialdir=gPath, title="Sélection d'un dossier")
if len(dossier)==0 : exit("Pas de dossier sélectionné !")
dossier = dossier.replace("/",SEP) # NB : Sans le slash (ou anti-slash) final

fP.destroy() # destruction de la fenêtre (= fermeture du GUI)
# fin de l'utilisation d'une boîte de dialogue --------------------------------

# reste du code du programme
print(dossier) # chemin complet du dossier choisi

askdirectory() retourne une string (vide si l'utilisateur a cliqué sur "Annuler").

Le programme devient logiciel lorsque tout est demandé via une interface graphique.

Le fenêtre modale peut se trouver sous sa fenêtre parent ...
=> déplacer la fenêtre parent (ou regarder dans la barre des tâches)
Le fenêtre qui a perdu le focus a son titre mis en gris.

Sélection d'un nombre entier

Retourne None ou un nombre entier (positif ou négatif).

None = clic sur la croix de fermeture ou clic sur Cancel

 
"toto" n'est pas un nombre entier
 
from tkinter import *
from tkinter import simpledialog

fP = Tk()
fP.withdraw() # Ne pas l'afficher

def labelWidth(label, taille = 100): #------------------------------------------
  """
  La fonction retourne une string ayant la taille minimale voulue,
  (utile à une largeur correcte de la boîte de dialogue).

  :param str label: un entier (tel qu'un nombre ou une taille de fichiers)
  :param int taille: nombre de caractères de la string formatée

  :return: Retourne une string ayant la taille minimale voulue
  :rtype: str

  2022-10-24 04:42:04
  """
  nb = len(label); diff = taille-nb
  if diff>0 : return label+" "*diff
  return label



label=labelWidth("Entrez un nombre entier [0-100] :")
int_a = simpledialog.askinteger("Titre",label,
                                              minvalue=0,
                                              maxvalue=100,
                                              initialvalue=7)

print(int_a) # affiche None ou un nombre entier (positif ou négatif)
fP.mainloop()

Les paramètres-clés - initialvalue, minvalue et maxvalue - sont facultatifs. Si la valeur donnée est égale à la valeur minimale ou maximale, elle sera acceptée.

Sélection d'un nombre décimal

Retourne None ou un nombre décimal (positif ou négatif).

None = clic sur la croix de fermeture ou clic sur Cancel

 
3 devient 3.0 --- Un entier est un nombre décimal.
("toto" n'est pas un nombre décimal)
from tkinter import *
from tkinter import simpledialog

fP = Tk()
fP.withdraw() # Ne pas l'afficher

float_b = simpledialog.askfloat("b", "Entrez un nombre décimal")
print(float_b) # affiche None ou un nombre décimal (positif ou négatif)
               # ou un entier sous forme décimale (3 devient 3.0)

fP.mainloop()

Les paramètres-clés - initialvalue, minvalue et maxvalue - sont facultatifs. Si la valeur donnée est égale à la valeur minimale ou maximale, elle sera acceptée.

Sélection d'une string

Retourne None ou une string (vide ou non).

None = clic sur la croix de fermeture ou clic sur Cancel

Tout est valide !
from tkinter import *
from tkinter import simpledialog

fP = Tk()
fP.withdraw()

titre = "Tapez 'fin' pour quitter"
label = "Entrez votre nom :" +" "*80
while True:

  str_nom = simpledialog.askstring(titre, label, initialvalue="Toto")

  print(str_nom) # affiche None ou une string (qui peut être vide,
                 # ou contenir un nombre => 7 est un nom valide !)

  if str_nom is None: print("= None")
  else:
    if str_nom == "": print("= Vide")
    else:
      print("= Non vide")
      if str_nom == "fin": print("=> Bye"); break

fP.destroy()
fP.mainloop()

Le paramètre-clé initialvalue="X" ou X sera affiché dans le champ.

Messages

L'appel d'une messagebox suspend le programme, le temps que l'utilisateur clique sur l'un des boutons de cette fenêtre surgissante (popup).

messagebox.showinfo("Titre","Message")

Il existe 8 types de message :

# À Â Ç É È Ê Ë Î Ï Ô Ù Û Ü Ÿ Æ Œ æ œ
# Code source : https://pythonguides.com/python-tkinter-messagebox/

from tkinter import *
from tkinter import messagebox

fP = Tk()
fP.title("Python Guides")
fP.geometry('300x200')
fP.config(bg='#5FB691')

def msg1():
    messagebox.showinfo("Titre","Message")
    messagebox.showerror("Titre","Message")
    messagebox.showwarning("Titre","Message")
    re = messagebox.askquestion("Titre","Message"); print(re)    # "yes" | "no"
    re = messagebox.askokcancel("Titre","Message"); print(re)    # True | False
    re = messagebox.askyesno("Titre","Message"); print(re)       # True | False
    re = messagebox.askretrycancel("Titre","Message"); print(re) # True | False
    re = messagebox.askyesnocancel("Titre","Message"); print(re) # True | False | "None"

Button(fP, text="Cliquez ici", command=msg1).pack(pady=50)

fP.mainloop()

Le premier bouton est le bouton par defaut. Toutefois, il est possible de modifier ce comportement avec l'option default="x" où x vaut, généralement, "no", "cancel"

Pour afficher un message sur plusieurs lignes, il faut utiliser la séquence /n et évitez d'utiliser plus de 55 caractères par ligne.

# messagebox n'affiche des lignes que de 69 caractères

#msg = "01234567890123456789012345678901234567890123456789012" # = 53 chiffres
#msg = "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm" # = 36 lettres m

messagebox.showwarning("Fin de ligne",
  "Une ligne par chemin.\nLa dernière ligne est vide.")

Documentation sur messagebox - (Tuto, trop de pub !)

Choisir une couleur

import tkinter # --------------------------
from tkinter import colorchooser # ----------

# utilisation d'une boîte de dialogue -----------------------------------------
fP = tkinter.Tk () # création de la fenêtre principale
fP.withdraw() # Ne pas l'afficher

couleur  = colorchooser.askcolor()

fP.destroy() # destruction de la fenêtre (= fermeture du GUI)
# fin de l'utilisation d'une boîte de dialogue --------------------------------

# reste du code du programme
print(couleur) # sous forme d'un tuple
               # ((214, 104, 217), '#d668d9') ou (None, None)

print(couleur[1]) # affiche : #d668d9

Sans fenêtre principale

La méthode .withdraw() permet de ne pas afficher la fenêtre (principale). L'utilisation de cette méthode est souvent utilisée lorsqu'on veut utiliser des fonctionnalités de Tkinter (telles que les boîtes de dialogue), sans avoir besoin d'une croix de fermeture.

from tkinter import *
from tkinter import simpledialog
from tkinter import messagebox

fP = Tk()
fP.withdraw()

def labelWidth(label, taille = 100): #------------------------------------------
  """
  La fonction retourne une string ayant la taille minimale voulue,
  (utile à une largeur correcte de la boîte de dialogue).

  :param str label: un entier (tel qu'un nombre ou une taille de fichiers)
  :param int taille: nombre de caractères de la string formatée

  :return: Retourne une string ayant la taille minimale voulue
  :rtype: str

  2022-10-24 04:42:04
  """
  nb = len(label); diff = taille-nb
  if diff>0 : return label+" "*diff
  return label


label=labelWidth("Entrez un nombre entier [0-100] :")

while True:

  int_a = simpledialog.askinteger("Valeur de a",label,
                                              minvalue=0,
                                              maxvalue=100,
                                              initialvalue=7)

  print(int_a)

  if not messagebox.askyesno("Continuer ?",
         "Souhaitez-vous faire un autre test ?") : break

fP.destroy()
fP.mainloop()

Si on veut aller plus loin qu'utiliser des boîtes de dialogue et afficher des messages dans le Terminal ...

Fenêtre principale

Couleur classique

Un logiciel dispose toujours d'une fenêtre principale. Souvent, elle dispose d'un menu et permet l'affichage d'autres fenêtres.

import tkinter      # import de tkinter
fP = tkinter.Tk()   # création de la fenêtre principale (fP)
fP.mainloop()       # écouter toute action sur la fenêtre

L'application de la méthode .Tk() sur tkinter crée un objet qui est attribué à une variable (et qui ainsi dispose d'un nom (ici, fP). Cet objet est une fenêtre qui dispose des trois buttons classiques (réduction, ajustement, fermeture).

L'application de la méthode .mainloop() crée une boucle (infinie) d'écoute de tous les événements qui pourraient se produire sur cette fenêtre, tel qu'un clic sur la croix de fermeture.

On constate que, par défaut, la fenêtre dispose d'une surface, d'une couleur de fond, d'un titre (tk) et qu'on peut utiliser les trois buttons classiques.

On peut modifier l'apparence de cette fenêtre, via différentes méthodes : .title(), .geometry(), .config()

Fenêtre personnalisée
from tkinter import *

fP = Tk()
fP.title("Fenêtre principale") # titre pour pouvoir glisser la fenêtre
fP.geometry("400x100")         # largeur suffisante pour afficher le titre ...
fP.config(bg='#5FB691')

fP.mainloop()

Vous pouvez déplacer une fenêtre en cliquant sur le titre de la fenêtre, puis en maintenant le bouton gauche de la souris enfoncé la déplacer pour déplacer la fenêtre. Il est donc recommandé de donner un titre à la fenêtre.

Cependant pour que le titre s'affiche, la fenêtre doit être suffisamment large. Il est donc recommandé d'indiquer une largeur suffisante. 160 est la largeur minimale pour disposer des trois boutons : _ □ x. Si le titre de la fenêtre n'est pas fourni => titre = tk => largeur minimale = 180.

Outre la dimension de la fenêtre, il est possible de la positionner via un code tel que
fP.geometry('400x100 + 10 + 100') où 10 est la position sur l'axe des X et 100 celle sur l'axe des Y.

Il ne reste plus qu'à remplir cette fenêtre de widgets ...

Widgets

Pour créer une interface, nous disposons de widgets.

Chaque widget est une class.
Une interface est quelque chose entre deux choses. Ici, entre un utilisateur et de la logique.

Les widgets Listbox et Text ne sont pas visés par Ttk.

Par ordre de fréquence d'utilisation :

Button

Le bouton est l'élément (le widget) le plus utile. Un clic sur un bouton déclenche un événement qui est traité par une fonction.

Le clic n'est pas le seul événement pouvant se produire. Pour plus d'info.

Syntaxe (tk) : btn = Button(parent, options)

from tkinter import *

fP = Tk()                            # crée une fenêtre
bouton = Button(fP,text = "Bouton")  # crée un bouton et le lie à la fenêtre
bouton.pack()                        # place le bouton sur la fenêtre (selon la méthode .pack())

fP.mainloop()

Rappel. Le nom d'une variable ne doit pas contenir de caractères spéciaux (tel que "ê").

Un bouton qui appelle rien lorsqu'on le clique.
(Fenêtre sans titre !)

Il faut définir une fonction pour que le bouton serve à quelque chose.

from tkinter import *             # importe toutes les 'class'
from tkinter import messagebox    # importe le module concerné

def afficher():
  print("Bouton cliqué") # pour afficher quelque chose en console
  messagebox.showinfo("Titre", "Bouton cliqué !")

fP = Tk()
bouton = Button(fP, text="Bouton", command=afficher)
bouton.pack()

fP.mainloop()

Options communes

La plupart des options sont communes aux widgets. Toutes ces options ont une valeur par défaut. Ce qui évite de devoir les donner pour construire un objet utilisable.

Le nom de toutes les options sont en minuscules.

Certaines options peuvent être spécifiques à un widget.
Tel que l'option command, dont la valeur est le nom d'une fonction (non encadré de guillemets)

Un bouton dispose de 19 options facultatives.

  1. activebackground : la couleur de fond lors du clic
  2. activeforeground : la couleur du texte lors du clic
  3. bd : la largeur de la bordure en pixels
  4. bg : la couleur de fond
  5. command : le nom (non encadré) de la fonction appelée lors du clic.
    Spécifique aux objets Button.
  6. fg : la couleur du texte
  7. font : le nom de la variable-objet préalablement créé, représentant une font (police, taille, style)
  8. height : le nombre de lignes (pour du texte) ou le nombre de pixels (pour une image)
  9. highlightcolor : la couleur du texte lorsque le bouton a le focus
  10. image : le chemin du fichier-image
  11. justify : alignement du texte multi-lignes : left, center, right ou fill
  12. padx : ajout d'un padding horizontal
  13. pady : ajout d'un padding vertical
  14. relief : type de bordure : sunken, raised, flat, groove ou ridge.
  15. state : état du bouton : disabled ou active (par défaut)
  16. text : texte affiché sur le bouton
  17. underline : soulignement du texte
  18. width : nombre de lettres (pour un texte), de pixels (pour une image)
  19. wraplength : nombre (s'il est positif) de lettres minimum, avant un retour à la ligne

Le nom des couleurs est encadré. Par exemple : "red", "#5FB691"

Bouton personnalisé
from tkinter import *             # importe toutes les 'class'
from tkinter import messagebox    # importe le module concerné
import tkinter.font as font

def afficher():
  messagebox.showinfo("Mon titre", "Mon message : Bouton cliqué !")

fP = Tk(className="Test")

# à déclarer après la création de la fenêtre
myFont = font.Font(family='Lucida Calligraphy', size=20, weight='bold')

bouton = Button(fP, text="Bouton", command=afficher, bg="#5FB691",
                bd=5, font=myFont, relief=RIDGE, padx=30)
bouton.pack()

fP.mainloop()

.config()

Les options sont données lors de la construction de l'objet. Toutefois, la méthode .config() permet de modifier l'objet après sa construction.

rom tkinter import *

fP = Tk()

def modifier():
  btn.config(text = "Texte de ce bouton modifié")

btn = Button(text = "Modifier ce bouton",command = modifier)

btn.pack()

fP.mainloop() 
 

Exemples de bouton (pour ttk)

Label

L'étiquette est généralement utilisée pour indiquer la nature du contenu du champ qui le suit.

Syntaxe (tk) : lbl = Label(parent, options)

from tkinter import *             # importe toutes les 'class'

fP = Tk()
lbl = Label(text = "Étiquette")
lbl.pack()

fP.mainloop()

Une étiquette peut être transformée en lien

# Source : https://www.tutorialspoint.com/how-to-create-a-hyperlink-with-a-label-in-tkinter

#Import the required libraries
from tkinter import *; import webbrowser

#Create an instance of tkinter frame
win = Tk()

#Define a callback function
def callback(url): webbrowser.open_new_tab(url)

#Create a Label to display the link
link = Label(win, text="www.tutorialspoint.com", fg="blue", cursor="hand2")
link.pack()
link.bind("<Button-1>", lambda e: callback("http://www.tutorialspoint.com"))

win.mainloop()

Entry

Le champ est utilisé pour que l'utilisateur puisse introduire des données sur une ligne.

Syntaxe (tk) : txt = Entry(parent, options)

from tkinter import *             # importe toutes les 'class'

fP = Tk()
lbl = Label(text = "Nom")
txt = Entry()

def afficher():
  print(txt.get())

btn = Button(text="Afficher le nom dans la console",command=afficher)

lbl.pack()
txt.pack()
btn.pack()

fP.mainloop()

Pour ajouter du texte au début : txt.insert(0, "texte")

Text

Ce widget est utilisé pour que l'utilisateur puisse introduire des données sur plusieurs lignes.

from tkinter import *             # importe toutes les 'class'

fP = Tk()
lbl = Label(text = "Description")
txt = Text(font=("Times New Roman", 14, "italic"))

def afficher():
  print(txt.get("0.0","end"))

btn = Button(text="Afficher la description dans la console",command=afficher)

lbl.pack()
txt.pack()
btn.pack()

fP.mainloop()

Une position est une string au format deux nombres entiers positifs séparés par un point tel que "numLigne.numCarac" où numLigne est un numéro de ligne et numCarac, la position sur cette ligne. La position "end" indique la fin du texte.

La méthode .get("position1","position2") permet de récupérer les caractères situés entre deux positions.

La dernière ligne est blanche (lors d'une impression).

La méthode .insert("position","texte") permet d'ajouter la valeur du second paramètre à la position indiquée par le premier paramètre.

La méthode .delete("position1","position2") permet de supprimer les caractères situés entre deux positions.

Effacer le contenu : .delete("start","end")

L'option commune height permet d'indiquer la hauteur du widget en nombre de lignes.

Checkbutton

 

Chaque option dispose de sa variable réceptrice (du choix de l'utilisateur)

import tkinter
from tkinter import messagebox

fP = tkinter.Tk ()
fP.title("Tests : Checkbutton")
fP.geometry("350x150")

int_chkFR = tkinter.IntVar()
chk_FR = tkinter.Checkbutton(variable = int_chkFR)
chk_FR.pack()

int_chkUS = tkinter.IntVar(value = 1) # Pour pré-sélectionner
chk_US = tkinter.Checkbutton(fP, variable = int_chkUS)
chk_US.pack()

int_chkNL = tkinter.IntVar()
chk_NL = tkinter.Checkbutton(fP, variable = int_chkNL, text = "Néerlandais")
chk_NL.pack()

int_chkXX = tkinter.IntVar()
chk_XX = tkinter.Checkbutton(fP, variable = int_chkXX, text="XX", state = tkinter.DISABLED)
chk_XX.pack()

def afficher():
  msg=( "Français : "+str(int_chkFR.get())+"\n"
  +"Anglais : "+str(int_chkUS.get())+"\n"
  +"Néerlandais : "+str(int_chkNL.get())+"\n"
  +"XX : "+str(int_chkXX.get()) )
  messagebox.showinfo("Vos choix",msg)

btn = tkinter.Button(text="Afficher vos choix", command=afficher)
btn.pack()

fP.mainloop ()

La variable qui reçoit le choix peut être du type : IntVar, StringVar ou BooleanVar

Plus d'info : pythonguides.com

Radiobutton

 

Une seule variable (de type IntVar, StringVar ou BooleanVar), pour toutes les options.

import tkinter
from tkinter import messagebox

fP = tkinter.Tk ()
fP.title("Tests : Radiobutton")
fP.geometry("350x150")

str_opt = tkinter.StringVar(value = "US") # Pour éviter que toutes les options soient sélectionnés
# => en pré-sélectionner une => en donnant une des valeurs possibles.

opt_FR = tkinter.Radiobutton (variable = str_opt, value = "FR")
opt_FR.pack()

opt_US = tkinter.Radiobutton (fP, variable = str_opt, value = "US")
opt_US.pack()

opt_NL = tkinter.Radiobutton (fP, variable = str_opt, value = "NL", text="Néerlandais")
opt_NL.pack()

opt_XX = tkinter.Radiobutton (fP, variable = str_opt, value = "XX", text="XX", state = tkinter.DISABLED)
opt_XX.pack()

def afficher():
  messagebox.showinfo("Votre choix",str_opt.get())

btn = tkinter.Button(text="Afficher votre choix", command=afficher)
btn.pack()

fP.mainloop ()

Frame

Les Frame sont des conteneurs d'autres widgets. Ils facilitent la présentation des widgets dans une fenêtre.

Un relief peut leur être appliqué :

# Source : https://realpython.com/python-gui-tkinter/

import tkinter as tk

border_effects = {
    "flat": tk.FLAT,
    "sunken": tk.SUNKEN,
    "raised": tk.RAISED,
    "groove": tk.GROOVE,
    "ridge": tk.RIDGE,
}

fP = tk.Tk()

for relief_name, relief in border_effects.items():
    frame = tk.Frame(master=fP, relief=relief, borderwidth=5)
    frame.pack(side=tk.LEFT)
    label = tk.Label(master=frame, text=relief_name)
    label.pack()

fP.mainloop()

Rappel : Ce relief peut être appliqué à la plupart des widgets.

Un cadre peut recevoir une étiquette.

LabelFrame

# https://www.tutorialspoint.com/python/tk_labelframe.htm

from tkinter import *

fP = Tk()
fP.title("Test : LabelFrame")
fP.geometry("350x150")

labelframe = LabelFrame(fP, text="Étiquette du cadre")
labelframe.pack(fill = "both", expand = "yes")

left = Label(labelframe, text = "À l'intérieur du cadre")
left.pack()

fP.mainloop()

Listbox

Par défaut, une liste affiche 10 lignes.
La hauteur de la liste, exprimée en lignes, peut être modifié, via .config(height=11)

Sélectionner la liste puis tourner la roulette de la souris,
ou via la touche TAB, puis les touches ↑ ↓
# https://pythonprogramming.altervista.org/tkinter-15-add-a-scrollbar-to-tkinter/

from tkinter import *
fP = Tk(); fP.title("Test : Listbox"); fP.geometry("300x250")
listbox = Listbox(fP); listbox.pack()
for i in range(100): listbox.insert(END, i)

def selected_item():
  for i in listbox.curselection(): print(listbox.get(i))

btn = Button(fP, text = 'Print Selected', command = selected_item)
btn.pack(side = 'bottom')
fP.mainloop()

xavierdupre.fr

Scrollbar

# https://pythonprogramming.altervista.org/tkinter-15-add-a-scrollbar-to-tkinter/

from tkinter import *
fP = Tk()
fP.title("Test : Listbox + Scrollbar")
fP.geometry("300x250")

# Créer et placer la Scrollbar dans le conteneur
scrollbar = Scrollbar(fP)
scrollbar.pack(side = RIGHT, fill = Y)

# Créer et placer la Listbox dans le conteneur
listbox = Listbox(fP)
listbox.pack()

# Remplir la Listbox
for i in range(100):
    listbox.insert(END, i)

# attach listbox to scrollbar
listbox.config(yscrollcommand = scrollbar.set)
scrollbar.config(command = listbox.yview)


#https://www.geeksforgeeks.org/how-to-get-selected-value-from-listbox-in-tkinter/
# Function for printing the selected listbox value(s)
def selected_item():

    # Traverse the tuple returned by curselection method and print
    # corresponding value(s) in the listbox
    for i in listbox.curselection():
        print(listbox.get(i))

# Create a button widget and map the command parameter to
# selected_item function
btn = Button(fP, text = 'Print Selected', command = selected_item)

# Placing the button
btn.pack(side='bottom')

# Lancer l'écoute des événements
fP.mainloop()

Focus

Plusieurs objets (widgets) peuvent être placés dans un conteneur (tel qu'une fenêtre), mais un seul écoute les actions de l'utilisateur (via le clavier ou la souris). On dit que ce widget a le focus.

Le widget qui a dispose du focus (qui est visé par les actions de l'utilisateur) présente une apparence différente. Le bouton est encadré de pointillé, le champ de saisie dispose du signe indiquant l'attente d'une frappe au clavier, |. Sont aussi concernés les Checkbutton, Radiobutton, ...

L'utilisateur peut taper sur la touche de tabulation (TAB) pour donner le focus au widget suivant (dans l'ordre de création dans le code).

La méthode .focus_set appliquée sur la variable représentant un widget permet au développeur de placer initialement le focus.

Focus sur la zone de saisie
from tkinter import *

fP = Tk()
fP.title("Test : .focus_set()")
fP.geometry("350x100")

lbl = Label(text = "Nom")
txt_nom = Entry()

def afficher():
  nom=txt_nom.get()
  print(nom)

btn = Button(text="Afficher le nom dans la console",command=afficher)

lbl.pack()
txt_nom.pack()
btn.pack()

txt_nom.focus_set()

fP.mainloop()

Emplacement des widgets dans un conteneur

Un conteneur peut être une fenêtre ou une sous-fenêtre (Frame).

.pack()

Cette méthode permet d'ajouter (dans le conteneur) le widget sur lequel elle est appliquée (et donc de le rendre visible)

.pack_forget()

Cette méthode permet de retirer le widget sur lequel elle est appliquée (et donc de le rendre invisible)

.grid()

Toplevel

from tkinter import *

fP = Tk() # fP pour Fenêtre principale
fP.title("Fenêtre principale")
fP.geometry("400x100")

def ouvrir_F2():
  F2 = Toplevel(fP) # F2 pour Fenêtre 2
  F2.title("Fenêtre 2")
  F2.geometry("300x50")
  fP.withdraw() # efface de l'écran la fP (=> F2 devient modale)

  def fermer_F2():
    F2.destroy()
    fP.deiconify() # ré-affiche la fP (à ne pas oublier !)

  btn_F2=Button(F2, text = "Fermer cette fenêtre",
              command = fermer_F2).pack()

  F2.mainloop()

btn_fP=Button(text = "Ouvrir une seconde fenêtre",
              command = ouvrir_F2).pack()

fP.mainloop()

Mes boîtes de dialogue

Nativement, Python vous offre que 3 boîtes de dialogue : askinterger, askfloat et askstring.

Mais, vous pouvez créer vos boîtes de dialogue : asktext, asklistbox, asklistboxID, ...

asktext

Pour enregistrer des retours de ligne
asktext.py
from tkinter import *
win=Tk(); win.title("asktext"); win.geometry("300x100"); gRE = ""


class MyDialog_asktext: #-------------------------------------------------------
  # inspiré par manejandodatos.es/2014/10/modal-forms-tkinter-python/
  # Modif = Utilise la variable globale, gRE, pour recueillir la valeur fournie
  def __init__(self, parent, title, labeltext, initialvalue,withBtnOK):
    self.top = Toplevel(parent); self.top.transient(parent); self.top.grab_set()
    self.top.title(title); Label(self.top, text=labeltext).pack()
    self.e = Text(self.top); self.e.insert("0.0",initialvalue)
    self.e.bind("<Escape>", self.cancel)  # 'Escape' vaut un clic sur "Annuler"
    self.e.pack(padx=15); frame = Frame(self.top)
    btnOK = Button(frame,text="Valider", command=self.ok)
    btnCancel = Button(frame,text="Annuler", command=self.cancel)
    if withBtnOK : btnOK.grid(row=0, column=1); self.e.focus_set();
    else: self.e.config(state=DISABLED,bg="#e6e6e6"); btnCancel.focus_set()
    btnCancel.grid(row=0, column=2); frame.pack()


  def ok(self, event=None): # ajout automatique d'une ligne vide
    global gRE; gRE = self.e.get("0.0","end"); self.top.destroy()


  def cancel(self, event=None):
    global gRE; gRE=None; self.top.destroy()


def asktext(parent, titre="", etiquette="", initialvalue="", withBtnOK=True): #-
  """
  Si withBtnOK=False, la modale ressemble à une messagebox.showinfo()
  améliorée car plus large et permettant le défilement vertical
  """
  global gRE; gRE=None;
  d=MyDialog_asktext(parent,titre,etiquette,initialvalue, withBtnOK)
  parent.wait_window(d.top) # lance la popup (et attend la réponse du user)
  return gRE


def afficher(): #---------------------------------------------------------------
  print("=",asktext(win))


Button(win, text="Afficher le texte dans la console",
       command=afficher).pack()


win.mainloop()

Le code de l'application déclare la variable globale : gRE.

La class MyDialog_asktext est recopiée tel quel dans le code.

La fonction asktext() retourne le texte (avec les retours de ligne) ou 'None'.

asklistbox

from tkinter import *
win=Tk(); win.title("listbox"); win.geometry("300x100")
re="vide"; liste=["33 : aaa","1 : bb", "9 : cccc"]

def choice(option):
   global re
   if option:
      for i in listBox.curselection(): re=listBox.get(i)
   else: re=None
   pop.destroy()

def createModal(liste):

   global pop, listBox; pop=Toplevel(win); listBox=Listbox(pop)
   pop.title("Liste :"); pop.geometry("250x200")
   for x in liste : listBox.insert(END, x)
   listBox.pack()

   frame = Frame(pop)
   btnOK = Button(frame,text="Valider", command=lambda: choice(True))
   btnOK.grid(row=0, column=1)
   btnCancel = Button(frame,text="Annuler", command=lambda: choice(False))
   btnCancel.grid(row=0, column=2)
   frame.pack()

label=Label(text=""); label.pack()
Button(win, text="Choisir dans une liste", command=lambda: createModal(liste)).pack()

def afficher():
   if re is not None : print(re)

Button(win, text="Afficher le choix dans la console", command=afficher).pack()

win.mainloop()

asklistboxID_multi

Le résultat d'une requête SQL de sélection (SELECT id, ... ORDER BY id) est 'None', une liste (list) de tuples ou un tuple. Ici, la première valeur d'un tuple doit être l'ID.

asklistboxID_multi.py
from tkinter import *
win=Tk(); win.title("asklistboxID_Multiple"); win.geometry("300x100")
gListTup = None
liste=[(3, 'moi', 18), (7, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 19), (8, 'x', 20)] # from .fetchall


class MyDialog_asklistboxID_multi: #-------------------------------------------
  """ inspiré par manejandodatos.es/2014/10/modal-forms-tkinter-python/
  Modifie la variable globale, gListTup, pour les éléments sélectionnés
  (issus d'une requête SQL retournant sous la forme : ID, nom, ...) """

  def __init__(self, parent, liste, title, labeltext, id_byDefault, isMultiple ):
    self.top = Toplevel(parent); self.top.transient(parent); self.top.grab_set()
    self.top.title(title); Label(self.top, text=labeltext).pack(); i=-1; maxi=0
    self.e = Listbox(self.top, activestyle="none")
    if isMultiple : self.e.config(selectmode="multiple")
    self.e.pack(padx=15); self.e.focus_set()

    for x in liste :
      if len(str(x))>maxi : maxi=len(str(x))
      i+=1; self.e.insert(END, x) # x est un tuple
      if str(x[0])==str(id_byDefault) : self.e.select_set(i)
    Button(self.top,text="Valider", command=self.ok).pack()
    self.e.config(width=maxi) #;print(str(maxi))


  def ok(self, event=None):
    global gListTup; gListTup = None
    if len(self.e.curselection())!=0 :
      gListTup=[]
      for i in self.e.curselection():
        gListTup.append(self.e.get(i))
    self.top.destroy()


  def cancel(self, event=None):
    global gListTup; gListTup = None; self.top.destroy()


def asklistboxID_multi(parent, liste, titre = "Liste", etiquette = "IDs :",
                 id_byDefault = "", isMultiple = False ): #--------------------
  """ return : None ou une liste de Tuples """
  global gListTup; gListTup=None
  if len(liste)>0 :
    d = MyDialog_asklistboxID_multi(parent, liste, titre, etiquette,
                                    id_byDefault, isMultiple)
    parent.wait_window(d.top) # lance la popup (et attend la réponse du user)
    return gListTup


def afficher(): #---------------------------------------------------------------
  y = asklistboxID_multi(win, liste, "", "", "7", True)
  if y is not None :
    for x in y:
      print(str(x[0]), str(x[1]))

Button(win, text="Afficher les ID choisis dans la console",
       command=afficher).pack()

win.mainloop()

Le code de l'application déclare la variable globale : gListTup.

La class MyDialog_asklistboxID_multi est recopiée tel quel dans le code.

La fonction asklistboxID_multi() retourne une liste de tuples ou 'None', chaque tuple correspond à un ligne de la listbox présentant les données (issues d'une DB) sous la forme : ID, nom, ...

Menu

Les boîtes de dialogue (simples) sont des fenêtres sans menu.

Une fenêtre peut contenir un menu.

# À Â Ç É È Ê Ë Î Ï Ô Ù Û Ü Ÿ Æ Œ æ œ
from tkinter import *

fP = Tk(); m = Menu()

mFichier = Menu()
mAide    = Menu()

m.add_cascade(label = "Fichier", menu = mFichier)
m.add_cascade (label = "Aide",    menu = mAide)

def menu1(): # à définir avant son utilisation dans le reste du code
    pass

mFichierNouveau = Menu()
mFichierOuvrir  = Menu()

mFichier.add_command (label = "Nouveau",     command = menu1)
mFichier.add_command (label = "Ouvrir ...",  command = menu1)

mFichierMenu = Menu()
mFichier.add_cascade (label = "Menu", menu = mFichierMenu)

mFichier.add_command (label = "Quitter",     command = fP.destroy)

mAide.add_command (label = "À propos de",    command = menu1)
mFichierMenu.add_command (label = "Sous-Menu", command = menu1)

fP.config(menu = m)
fP.mainloop()

Les options de Menu() :

Les méthodes de Menu() :

Les options de Menu() :

Autre tutoriel

.protocol()

WM_DELETE_WINDOW

fP.protocol("WM_DELETE_WINDOW", quitter)

Cette méthode appliquée sur l'objet représentant l'interface Tkinter permet d'appeler une fonction lors du clic sur la croix de fermeture. Ici, quitter est le nom de la fonction appelée lorsque l'utilisateur clique sur le menu Fichier > Quitter.

Presse papier

La méthode .clipboard_clear() appliqué sur l'objet créé par le constructeur Tk() permet de vider le contenu textuel du presse-papier.

La méthode .clipboard_append() appliqué sur l'objet créé par le constructeur Tk() permet d'ajouter du contenu textuel au presse-papier.

from tkinter import *
fP = Tk()

texte="Thibaut"

fP.clipboard_clear()
fP.clipboard_append(texte)

La méthode .clipboard_get() appliqué sur l'objet créé par le constructeur Tk() permet de récupérer le contenu textuel du presse-papier.

from tkinter import *
fP = Tk()

fP.withdraw()  # keep the window from showing

print(fP.clipboard_get())