Infoforall

Python 07 : COMPLEMENTS SUR LES WIDGETS DE TKINTER

Nous allons apprendre ici à gérer quelques nouveaux widgets. Pratique mais pas indispensable pour réaliser vos projets.

1 - Scale : Les curseurs, les curseurs !

Rien de mieux qu’un curseur pour rendre un programme interactif.

Il s’agit de la classe Scale (oui oui, avec une majuscule).

Nous allons pouvoir récupérer des données numériques plutôt que texte.

On peut créer des widgets autonomes, non rattachés à une variable externe :

curseur1 = Scale(fen_princ, from_ = -20, to = 300)

curseur1.pack()

On peut alors lire la valeur actuelle du Scale avec la méthode get :

valeur = curseur1.get()

Et on peut même forcer la valeur du curseur à une certaine valeur avec la méthode set :

curseur1.set(10)

Si on intègre cela dans une interface Tkinter basique (un Scale, un Label et un Button qui modifie l'affichage du Label avec la valeur du Scale), le code devient :

# -*-coding:Utf-8 -*

from tkinter import *


# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions utilisées

# - - - - - - - - - - - - - - - - - -


def afficherValeur() :

    valeur = curseur1.get()

    monAffichage.configure(text = valeur)


# - - - - - - - - - - - - - - - - - -

# Création de la fenêtre et des objets associés la fenêtre

# - - - - - - - - - - - - - - - - - -


fen_princ = Tk()


# Création d'un Scale nommé curseur1

curseur1 = Scale(fen_princ, from_ = -20, to = 300)

curseur1.pack()


# Création d'un Label nommé monAffichage

monAffichage = Label(fen_princ, text = "C'est ici qu'on affichera le résultat du Scale", width=70)

monAffichage.pack()


# Création d'un Button lancant la fonction afficherValeur()

monBouton = Button(fen_princ, text = "Récupérer la valeur du curseur", command = afficherValeur)

monBouton.pack()


# - - - - - - - - - - - - - - - - - -

# Bouclage de la fenêtre fen_princ

# - - - - - - - - - - - - - - - - - -


fen_princ.mainloop()

01° Lancer le programme test et vérifier qu'il fonctionne correctement en faisant bouger le curseur :

Lors du lancement, on obtient :

Le Scale

Après l'activation du bouton :

Le Scale après

Voici quelques attributs permettant d'améliorer le visuel de votre interface :

Comment avoir un curseur/scale horizontal ?

Scale(fen_princ, orient='horizontal', from_=0, to=10)

Comment gérer la taille du curseur/scale ?

Scale(fen_princ, from_=0, to=10, length=350)

Comment mettre un nom sur un curseur/scale ?

Scale(fen_princ, from_=0, to=10, label='titre du scale')

02° Modifier le curseur pour que cela ressemble à ceci :

Le Scale horizontal

...CORRECTION...

curseur1 = Scale(fen_princ, from_ = -20, to = 300, orient='horizontal', bg='yellow', fg='black')

Rappel : le background (bg) et le foreground(fg) permettent de gérer les couleurs.

On peut également rattacher le curseur à une variable externe. On utilisera ici une méthode constructeur IntVar() qui est l'équivalent du StringVar() mais en integer. Si vous modifiez cette variable, les widgets qui en dépendent vont automatiquement être modifiés.

valeur2 = IntVar

curseur2 = Scale(fen_princ, from_ = -20, to = 300, variable = valeur2)

curseur2.pack()

Le plus fort, c'est qu'on peut associer cet objet de classe IntVar à un Label via l'attribut textvariable. Et Tkinter fera le boulot à votre place dès que ce IntVar va varier.

monAffichage = Label(fen_princ, textvariable = valeur2, width=70)

03° Utiliser ces nouvelles connaissances pour parvenir à créer un interface sans bouton dans laquelle bouger le curseur modifie automatiquement la valeur affichée dans le Label.

...CORRECTION...

# -*-coding:Utf-8 -*

from tkinter import *


# - - - - - - - - - - - - - - - - - -

# Création de la fenêtre et des objets associés la fenêtre

# - - - - - - - - - - - - - - - - - -


fen_princ = Tk()


# Création d'un Scale nommé curseur1


valeur2 = IntVar()

curseur2 = Scale(fen_princ, from_ = -20, to = 300, variable = valeur2)

curseur2.pack()


# Création d'un Label nommé monAffichage

monAffichage = Label(fen_princ, textvariable = valeur2, width=70)

monAffichage.pack()


# - - - - - - - - - - - - - - - - - -

# Bouclage de la fenêtre fen_princ

# - - - - - - - - - - - - - - - - - -


fen_princ.mainloop()

2 - La SpinBox

Il s’agit de la classe Spinbox : une entrée où on peut rentrer la valeur à la main ou en utilisant deux boutons + ou -.

s = Spinbox(fen_princ, from_ = 0, to = 10)

s.pack()

04° Afficher le contenu de la Spinbox en appuyant sur l’un des boutons. Il faudra utiliser la fonction get() appliquée à la spinbox s.

La correction en cliquant sur l'image :

correction

Les remarques faites sur le Scale ayant un attribut variable pointant vers un Scale sont valables ici aussi.

3 - La liste prédéfinie : la Listbox

Cette classe se note Listbox, avec une majuscule, oui oui, on a compris.

liste = Listbox(fen_princ)

liste.insert(1, "Python")

liste.insert(2, "C++")

liste.insert(3, "HTML")

liste.insert(4, "CSS")

liste.insert(5, "Javascript")

liste.pack()

On voit que ça se code un peu différemment mais sans plus.

Pour récupérer la valeur du choix, on utilise :

liste.get('active')

Pour définir les choix par programmation, on peut utiliser le code ci-dessous. Il crée les choix 'un, 'deux' et 'trois'.

liste.set('un deux trois')

Beaucoup d'autres choses sont possibles. Il suffit de lire la documentation.

05° Compléter le programme ci-dessous pour qu'il permette de modifier l’affichage du label en fonction des valeurs d’une liste et de l’appui sur un bouton.

La correction   en cliquant sur l'image :

correction

# -*-coding:Utf-8 -*

from tkinter import *


# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions utilisées

# - - - - - - - - - - - - - - - - - -


def changerAffichage() :

    Compléter ici


# - - - - - - - - - - - - - - - - - -

# Création de la fenêtre et des objets associés la fenêtre

# - - - - - - - - - - - - - - - - - -


fen_princ = Tk()


# Création d'un Label nommé monAffichage

monTexte = StringVar()

monTexte.set("Hello World !")


monAffichage = Label(fen_princ, textvariable = monTexte, width=70)

monAffichage.pack()


# Création d'une ListBox

liste = Listbox(fen_princ)

liste.insert(1, "Python")

liste.insert(2, "C++")

liste.insert(3, "HTML")

liste.insert(4, "CSS")

liste.insert(5, "Javascript")

liste.pack()


# Création d'un Button lancant la fonction changerAffichage()

monBouton = Button(fen_princ, text = "Change l'affichage !", command = changerAffichage)

monBouton.pack()


# - - - - - - - - - - - - - - - - - -

# Bouclage de la fenêtre fen_princ

# - - - - - - - - - - - - - - - - - -


fen_princ.mainloop()

06° Créer un programme qui utilise une liste contenant des strings ('blue', 'green', 'red' ...). Utilisez ensuite les valeurs de cette liste pour changer la couleur d'un Label texte lorsqu'on appuie sur un bouton.

Modification des couleurs

Remarque : comme le texte n'est pas destiné à changer, j'ai utilisé l'attribut text et pas textvariable dans la correction.

...CORRECTION...

# -*-coding:Utf-8 -*

from tkinter import *


# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions utilisées

# - - - - - - - - - - - - - - - - - -


def changerCouleur() :

    couleur = liste.get('active')

    monAffichage.configure(fg = couleur)


# - - - - - - - - - - - - - - - - - -

# Création de la fenêtre et des objets associés la fenêtre

# - - - - - - - - - - - - - - - - - -


fen_princ = Tk()


# Création d'un Label nommé monAffichage

monAffichage = Label(fen_princ, text = "C'est ce texte qui devra changer de couleurs", width=70)

monAffichage.pack()


# Création d'une ListBox

liste = Listbox(fen_princ)

liste.insert(1, "blue")

liste.insert(2, "red")

liste.insert(3, "green")

liste.insert(4, "orange")

liste.insert(5, "yellow")

liste.pack()


# Création d'un Button lancant la fonction changerCouleur()

monBouton = Button(fen_princ, text = "Change la couleur !", command = changerCouleur)

monBouton.pack()


# - - - - - - - - - - - - - - - - - -

# Bouclage de la fenêtre fen_princ

# - - - - - - - - - - - - - - - - - -


fen_princ.mainloop()

4 - Ouvrir la fenêtre de sélection des fichiers

Nous allons avoir besoin d'importer les éléments de tkinter.filedialog.

Il faudra ensuite utiliser la fonction askopenfilename et lui transmettre en argument le titre de la boîte de dialogue (title), les types de fichiers qui seront affichés (filetypes).

Filetypes doit recevoir une liste, cela se voit aux crochets de définitions [ et ]. Chaque élément de la liste est un 2-uplets (string affiché, extension correspondante).

filepath = askopenfilename(title = "Ouvrir une image", filetypes = [ ('jpg files','.jpg'),('all files','.*') ])

Une fois le fichier sélectionné, votre variable (ici filepath) contiendra un string correspondant au nom du fichier voulu avec son chemin d'accés.

07° On voudrait activer la fenêtre lorsqu’on appuie sur un bouton. Il faudrait alors afficher le contenu de filepath dans un Label. Un simple monTexte.set(filepath) devrait suffire. N'oubliez pas non plus qu'on modifie le contenu d'un StringVar à l'aide de la méthode get.

Le programme ci-dessous crée un Label et un Entry qui sont liés au même StringVar nommé monTexte.

# -*-coding:Utf-8 -*

from tkinter import *

from tkinter.filedialog import * # pour les gestions de fichiers


# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions utilisées

# - - - - - - - - - - - - - - - - - -


def chercherFichier() :

    Compléter ici


# - - - - - - - - - - - - - - - - - -

# Création de la fenêtre et des objets associés la fenêtre

# - - - - - - - - - - - - - - - - - -


fen_princ = Tk()


monTexte = StringVar()

monTexte.set("Pas de fichier pour l'instant")


# Création d'un Label nommé monAffichage

monAffichage = Label(fen_princ, textvariable = monTexte, width=70)

monAffichage.pack()


# Création d'un Entry nommé monEntree

monEntree = Entry(fen_princ, textvariable = monTexte, width=70)

monEntree.pack()


# Création d'un Button lancant la fonction chercherFichier()

monBouton = Button(fen_princ, text = "Boîte de dialogue", command = chercherFichier)

monBouton.pack()


# - - - - - - - - - - - - - - - - - -

# Bouclage de la fenêtre fen_princ

# - - - - - - - - - - - - - - - - - -


fen_princ.mainloop()

La correction  en cliquant sur l'image :

correction

5 - Ouvrir la boite de dialogue fichier et afficher l’image

C'est la suite logique : on va associer ce qu’on a vu sur l'affichage d'une image et ce qu'on vient de voir sur le choix d'un fichier.

N’oubliez pas l’importation des classes indispensables :

from tkinter import *

from tkinter.filedialog import * #pour les gestions de fichiers

from PIL import Image as Img

from PIL import ImageTk

Pour affecter l'image au bon Label, utilisons :

filepath = askopenfilename(title = "Ouvrir une image", filetypes = [ ('jpg files','.jpg'),('all files','.*') ])

presentation = Img.open(filepath)

presentationTk = ImageTk.PhotoImage(presentation)

labelphoto = Label(fen_princ, image = presentationTk)

labelphoto.pack()

Voici ce que cela donne sur un programme qui demande directement l'image qu'on veut voir s'afficher.

# -*-coding:Utf-8 -*

from tkinter import *

from tkinter.filedialog import * # pour les gestions de fichiers

from PIL import Image as Img

from PIL import ImageTk


# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions utilisées

# - - - - - - - - - - - - - - - - - -


# - - - - - - - - - - - - - - - - - -

# Création de la fenêtre et des objets associés la fenêtre

# - - - - - - - - - - - - - - - - - -


fen_princ = Tk()


monFichier = StringVar()

monFichier.set("Pas de fichier pour l'instant")


# Création d'un Label nommé monAffichage

monAffichage = Label(fen_princ, textvariable = monFichier, width=70)

monAffichage.pack()


# Recherche de l'adresse du fichier-image voulu

filename = askopenfilename(title = "Ouvrir une image", filetypes = [ ('jpg files','.jpg'),('all files','.*') ])


# Mise à jour de monFichier

monFichier.set(filename)


# Création d'un Label image associé à l'adresse précédente

presentation = Img.open(filename)

presentationTk = ImageTk.PhotoImage(presentation)

labelphoto = Label(fen_princ, image = presentationTk)

labelphoto.pack()


# - - - - - - - - - - - - - - - - - -

# Bouclage de la fenêtre fen_princ

# - - - - - - - - - - - - - - - - - -


fen_princ.mainloop()

08° Tester le code pour qu'on puisse ouvrir la fenetre de dialogue, sélectionner un fichier image et l'afficher dans un Label. Et expliquer le, ligne par ligne, c'est très important. Aucune ligne de code ne doit vous paraitre inconnue.

correction

Si on sélectionne une image, ça donne

correction

Et si nous tentions de faire la même chose mais avec un bouton. Pour l'instant, la boîte de dialogue s'affiche dès le départ.

09° Modifier le programme pour que le choix de l'image se fasse après avoir cliqué sur un bouton. Tenter de modifier l'image du Label.

Dans le corps du programme, vous pouvez créer au départ l'image de base sans sélection avec le code suivant : (voir l'activité précédente sur Tkinter)

presentation = Img.new("RGB", (20,20), (255,255,150))

presentationTk = ImageTk.PhotoImage(presentation)

labelphoto = Label(fen_princ, image = presentationTk)

labelphoto.pack()

Dans la fonction, il faudra donc recréer un contenu pour presentationTk et il faudra donc modifier l'attribut image avec un code du type :

labelphoto.config(image=presentationTk)

Tenter d'aller jusqu'au bout et de ne plus avoir d'erreur dans le code. Vous ne devriez néanmoins pas réussir à modifier votre image ... Tout cela à cause du ramasse-miettes : vous tentez de modifier l'image de votre label avec un = depuis une fonction : dès qu'on sort de la fonction, le ramasse-miettes détruit les variables qui ne sont pas liées à des objets du programme principal directement.

Vous devriez néanmoins réussir à afficher l'image de base (un carré coloré) et choisir le fichier et modifier le nom affiché dans le label.

Voici une correction du code non fonctionnel que vous devriez avoir écrit :

...CORRECTION...

# -*-coding:Utf-8 -*

from tkinter import *

from tkinter.filedialog import * # pour les gestions de fichiers

from PIL import Image as Img

from PIL import ImageTk


# - - - - - - - - - - - - - - - - - -

# Déclarations des fonctions utilisées

# - - - - - - - - - - - - - - - - - -

def mise_a_jour():

    # Recherche de l'adresse du fichier-image voulu

    filename = askopenfilename(title = "Ouvrir une image", filetypes = [ ('jpg files','.jpg'),('all files','.*') ])

    # Mise à jour de monFichier

    monFichier.set(filename)

    # Modification du Label image associé à l'adresse précédente

    presentation = Img.open(filename)

    presentationTk = ImageTk.PhotoImage(presentation)

    labelphoto.config(image=presentationTk)


# - - - - - - - - - - - - - - - - - -

# Création de la fenêtre et des objets associés la fenêtre

# - - - - - - - - - - - - - - - - - -


fen_princ = Tk()


monFichier = StringVar()

monFichier.set("Pas de fichier pour l'instant")


# Création d'un Label nommé monAffichage

monAffichage = Label(fen_princ, textvariable = monFichier, width=70)

monAffichage.pack()


# Création d'un Button lancant la fonction mise_a_jour

monBouton = Button(fen_princ, text = "Mise à jour du texte ci-dessus", command=mise_a_jour)

monBouton.pack()


# Création d'un Label image associé à l'adresse précédente

presentation = Img.new("RGB", (20,20), (255,255,150))

presentationTk = ImageTk.PhotoImage(presentation)

labelphoto = Label(fen_princ, image = presentationTk)

labelphoto.pack()


# - - - - - - - - - - - - - - - - - -

# Bouclage de la fenêtre fen_princ

# - - - - - - - - - - - - - - - - - -


fen_princ.mainloop()

Alors, d'où vient le problème ?

Regardons le code :

def mise_a_jour():

    # Recherche de l'adresse du fichier-image voulu

    filename = askopenfilename(title = "Ouvrir une image", filetypes = [ ('jpg files','.jpg'),('all files','.*') ])

    # Mise à jour de monFichier

    monFichier.set(filename)

    # Modification du Label image associé à l'adresse précédente

    presentation = Img.open(filename)

    presentationTk = ImageTk.PhotoImage(presentation)

    labelphoto.config(image=presentationTk)

Pourquoi ça ne marche pas ? La documentation explique qu'il faut garder une référence vers votre objet-image. Ici, on ne stocke dans l'attribut image que l'adresse mémoire de l'objet-image lui-même. Or, le ramasse-miette détruit les variables locales créées par la fonction qui ne sont pas stockées dans un objet-créé dans le corps du programme. Le résultat en image :

strucutre de la fonction

La technique pour stocker un objet-image qui n'est pas créé dans le programme principal est de le stocker dans l'objet-Label en lui créant un nouvel attribut. On peut le nommmer comme on veut : toto, monEmplacement, memoireImage ...

Voici ce que cela donnera en image :

strucutre de la fonction modifiee

Et on peut obtenir ceci avec une ligne en plus :

    labelphoto.monEmplacement = presentationTk

def mise_a_jour():

    # Recherche de l'adresse du fichier-image voulu

    filename = askopenfilename(title = "Ouvrir une image", filetypes = [ ('jpg files','.jpg'),('all files','.*') ])

    # Mise à jour de monFichier

    monFichier.set(filename)

    # Modification du Label image associé à l'adresse précédente

    presentation = Img.open(filename)

    presentationTk = ImageTk.PhotoImage(presentation)

    labelphoto.monEmplacement = presentationTk

    labelphoto.config(image=presentationTk)

10° Rajouter la ligne qui permet de stocker notre référence à l'image dans l'objet-Label lui-même et relancer votre programme. Cette fois, cela va fonctionner.

avant sélection

Puis :

après sélection

6 - Complément sur les textes

Nous n'avions pas réglé le probléme des textes trop grands pour le widget-Label dans lequel ils sont affichés :

Le Label

Voici le code utilisé pour obtenir un résultat proche du précédent :

# -*-coding:Utf-8 -*

from tkinter import *

# - - - - - - - - - - - - - - - - - -

# Création de la fenêtre et des objets associés la fenêtre

# - - - - - - - - - - - - - - - - - -


fen_princ = Tk()


# Création d'un Label nommé monAffichage

monAffichage = Label(fen_princ, text = "Mon premier Affichage ! Mon premier Affichage !", width=35, font = ("Helvetica", 32))

monAffichage.pack()


# - - - - - - - - - - - - - - - - - -

# Bouclage de la fenêtre fen_princ

# - - - - - - - - - - - - - - - - - -


fen_princ.mainloop()

Tentons de résoudre ce problème : on peut définir des formats d’affichage.

L’attribut height définit la hauteur de l’affichage :

L’attribut width définit la largeur de l’affichage.

11° Tester le code suivant :

from tkinter import *

fen_princ = Tk()

monAffichage = Label(fen_princ, text = "C’est ici que j’affiche mon très très long premier texte !" , font = ("Helvetica", 32), height = 3, width = 15)

monAffichage.pack()

fen_princ.mainloop()

Vous devriez voir que le Label prend 3 lignes de hauteur (mais n’en utilise qu’une) et que la chaîne de caractères ne s’affiche que sur 15 caractères. Ce n’est pas ce qui était voulu…

Le Label

Si le texte ne s’affiche pas correctement, on peut déjà indiquer quelle zone du texte on désire afficher :

Le paramètre anchor permet d’afficher le texte en l’alignant sur   :

On notera qu’on peut également utiliser les associations : NW ….

Quelques exemples 

anchor=W
Avec anchor=W. On voit le début du texte
anchor=E
Avec anchor=E. On voit la fin du texte
anchor=N
Avec anchor=N
anchor=NW
Avec anchor=NW

12° Tester le code suivant (attention, votre chaîne de caractères doit être réellement longue).

from tkinter import *

fen_princ = Tk()

monAffichage = Label(fen_princ, text = "C’est ici que j’affiche mon très très long premier texte !", font = ("Helvetica", 32), height = 3, width = 15, anchor = W)

monAffichage.pack()

fen_princ.mainloop()

Ca fonctionne mieux un peu mieux.

ATTENTION : Il y a des modifications à faire si vous importer Tkinter avec un alias :

import tkinter as tk

Comme vous avez créé un alias, il faudra taper tk devant W, E ...

monAffichage = Label(fen_princ, text = "C’est ici que j’affiche mon très très long premier texte !", font = ("Helvetica", 32), height = 3, width = 15, anchor = tk.W)

Pensez à modifier les codes ci-dessous en conséquence.

13° Tester le code en modifiant le type de l’ancrage du texte (anchor) pour en comprendre la signification (notamment les associations NW, SE ..)

C’est bien beau tout ça, mais mon texte ne s’affiche toujours pas en entier. Ce ne fait pas très sérieux.

Pour palier à ce petit problème, nous allons lui demander d’accepter de s’afficher sur plusieurs lignes si ça peut vous faire plaisir. Pour cela, on utilise wraplength.

monAffichage = Label(fen_princ, text = "C’est ici que j’affiche mon très très long premier texte !", font = ("Helvetica", 32), height = 3, width = 25, anchor = NW, wraplength = 800)

14° Tester cette ligne pour que votre long texte s’affiche maintenant correctement. Attention wraplength est à donner en mesure-écran. Il faudra sûrement jouer sur width pour que cela fonctionne bien. Pensez également à rajouter des lignes avec height si la place n’est pas suffisante. Le problème vient du fait que la police utilisée fait varier la taille de la lettre en fonction de la taille réellement nécessairement en largeur : un a prend plus de place qu'une virgule.

Vous devriez réussir à obtenir quelque chose comme

avec wraplength

Il existe beaucoup d’autres attributs modifiables. Le mieux est encore de faire une recherche sur « Python Tkinter Label ».

7 - Attributs height et width

Il s'agit juste d'une remarque.

Ces deux attributs que nous avons utilisé avec les widgets Label pour du texte sont utilisables sur tous les widgets. Si vous devez modifier la taille d'un widget, pensez à ces deux attributs.

Par exemple pour un bouton :

Gros boutons

Ce résultat est obtenu avec :

# Création d'un Button lancant la fonction mise_a_jour

monBouton = Button(fen_princ, text = "Activer le bouton", width=50, height=5, bg='red')

monBouton.pack()

Comme il s'agit d'un bouton affichant du texte, les dimensions sont en colonnes et lignes, pas en pixels.

Cela marche aussi avec les labels affichant des images. Et cette fois, le résulat est en pixels.

Il reste encore des cas à traiter. Mais vous avez maintenant assez de connaissances pour vous débrouiller avec la documentation de Tkinter.