Infoforall

Python 06 : CREER ET GERER DES INTERFACES GRAPHIQUES

La console, c’est simple mais c’est moche. Il nous reste à voir comment concevoir un code qui permet de réaliser des choses mais en présentant les éléments de façon plus jolie.

Pour cela, nous allons aujourd’hui utiliser un nouveau module : le module Tkinter pour « Tk interface ».

Pas besoin de l'importer avec l'instruction pip : il est intégré de base à la distribution Python.

On l’appellera à l’aide de la commande suivante :

from tkinter import *

Je n'utilise pas d'alias comme avec Pillow : nous allons trop régulièrement taper le nom du module sinon.

Cette partie est assez directive de façon à pouvoir y revenir en cours de formation. Les solutions sont souvent données directement avec le code correspondant. Vous aurez tout le temps de vous y mettre lors des projets.

1 - Première fenêtre et un premier widget Label avec du texte

Vous aller créer votre première fenêtre graphique et y insérer un widget (nom dans le domaine informatique d’un « gadget » graphique). Ce premier widget est de classe Label (avec un L majuscule) et il est créé avec la méthode-constructeur qui porte le même nom (Label() ) : il s’agit d’une zone dans laquelle on pourra insérer un texte ou une image.

Le Label

01° Recopier puis lancer le code suivant :

from tkinter import *


fen_princ = Tk()


monAffichage = Label(fen_princ, text = "C’est ici que j’affiche mon premier texte !")

monAffichage.pack()


fen_princ.mainloop()

Quelques explications sur les cinq lignes précédentes :

from tkinter import *

Importation du module tkinter qui contient les classes "Widgets"  …

fen_princ = Tk()

On crée un objet-fenêtre graphique à l’aide de la méthode constructeur Tk() et on le nomme fen_princ (pour fenêtre principale). Si on n’a pas importé le module tkinter avant, Python ne trouvera pas Tk() et va donc déclarer une erreur.

monAffichage = Label(fen_princ, text = "C’est ici que j’affiche mon premier texte !")

On crée un objet-label qui se nomme monAffichage. Les attributs du Label sont :

  • fen_princ : on attache le label à l’objet-fenêtre fen_princ qu’on vient de créer. 
  • text = "C’est … texte !" : on donne le type de contenu du label : du text (sans e) et on affecte son contenu en donnant la chaîne de caractères entre deux guillemets.

monAffichage.pack()

On utilise la méthode pack() sur l’objet-label monAffichage : cela place l’objet monAffichage dans la fenêtre donnée lors de la création : fen_princ ici.

fen_princ.mainloop()

On crée une boucle sur la fenêtre fen_princ pour qu’elle surveille en permanence ce qui s’y passe. C'est comme si vous aviez demandé de surveiller en permanence ce qui se passe sur cette fenêtre, tant que la fenêtre reste ouverte.

02° Créer deux autres objets-labels de type text et les faire apparaître dans votre fenêtre.

Il existe d’autres attributs qu’on peut utiliser :

monAffichage = Label(fen_princ, text = "Mon texte ici !", fg = "red")

Affiche le texte en rouge (fg pour foreground, premier plan).

Le Label

monAffichage = Label(fen_princ, text = "Mon texte ici !", fg = "yellow", bg = "black")

Affiche le texte en jaune (fg pour foreground, premier plan) sur fond noir (bg pour background, arrière plan).

Le background

monAffichage = Label(fen_princ, text = "C’est ici que j’affiche mon premier texte !" , font = ("Helvetica", 32))

Affiche le texte en définissant un 2-uplet (type de police, taille de police) pour le font(police) : ici Helvetica pour le type de police et 32 pour la taille de police. Attention, on doit donc fournir les données à travers un tuple (d'où les parenthèses).

Le Label

03° Tester les attributs présentés pour modifier vos trois textes.

04° Modifier l’un des textes pour qu’il soit très, très long. Relancer. Voyez-vous le problème ?

Le Label

Nous apprendrons à résoudre ce problème dans l'activité suivante. Pour l'instant, tentez de ne pas trop chercher à remplir vos Labels.

Label, Image ou Img avec une majuscule, et int ou float avec une minuscule ? Pourquoi ?

Ce n’est pas le fruit du hasard : le type d’une variable commence par une minuscule et la classe d’un objet par une majuscule. Ca évite de se tromper.

Et la méthode constructeur d'un objet est le nom de la classe : ainsi si la classe se nomme Label, son constructeur sera Label() avec les parenthèses.

Qu'est-ce qu'une variable ? (2)

Ou : c’est quoi à la fin une « classe » ?

Vous avez déjà manipulé plusieurs fois les classes via les objets.

Variables de structure :

Pour rappel, nous avons parfois utilisé des variables dont le type est int (on stocke un entier) ou float (on stocke un réel).

Par exemple :

a = 45

On stocke deux choses en mémoire :

  • l'information integer 45 codé en binaire dans une zone mémoire précise, connue par un identifiant
  • la liaison entre le nom a, l'identifiant précédent dans un tableau qu'on nommme espace des noms et le type de la donnée codée (integer).
variable

a est donc une variable "simple" qui ne contient que la valeur d’un seul type de donnée. On parle de variable de structure car la structure des données est prédéfinie. Elle ne peut contenir qu'un nombre bien précis et prédéterminé de données. Un integer, un float, un string...

Lorsque l'interpréteur tombe sur a, il va donc pouvoir vous afficher la valeur à l'intérieur de cette "case-mémoire".

Si on supprime les références, on obtient la vision des "boites" :

objet simplifié

Variables de référence :

Il existe néanmoins des objets plus complexes, comme les images ou les widgets. Une image est une entité beaucoup plus complexe qu’un entier : elle est définie par :

  • Son nombre de couches : RGB, L, avec éventuellement une couche Alpha en transparence.
  • Son nombre de pixels en hauteur (height)
  • Son nombre de pixels en largeur (width)
  • L’intensité ou les intensités RGB de chaque pixel.

mon_image = Img.open("linux.jpg")

De plus, les objets-image Pillow possèdent des méthodes bien spécifiques (l'activité sur les images devrait vous en rappeler quelques unes : show, putpixel, getpixel, save).

Ici l'utilisation de la variable mon_image ne renvoie pas directement une valeur mais une référence (une adresse) vers les données de l'objet. Cette référence va ensuite être utilisée pour obtenir la bonne valeur parmi toutes celles stockées pour l'objet.

objet

On peut donc voir un objet comme :

  • Un regroupement de plusieurs variables (attributs)
  • pour lequel il existe des fonctions particulières (méthodes)

Si on supprime les références, l'imbrication devient plus facile à visualiser :

objet simplifié

Voilà. Vous savez maintenant pourquoi on utilise des noms différents pour désigner des choses qui ont l'air assez semblables si on n'y regarde pas de près. On a donc :

Des types de variables pour les variables "simples" ou de structures :

x = 45

  • x est une variable
  • renvoyant une valeur.
  • de type int (integer)

Des classes pour les variables-objet ou variables de référence:

monAffichage = Label(fen_princ, text = "C’est ici que j’affiche mon premier texte !")

  • monAffichage est une variable
  • renvoyant une référence vers un objet
  • de classe Label.

2 - Méthode cget

Et si on veut lire le contenu d'un attribut, on peut ?

Et bien oui. Il faut utiliser la méthode cget sur votre widget.

from tkinter import *

fen_princ = Tk()

monAffichage = Label(fen_princ, text = "Mon texte ici !", fg = "red")

monAffichage.pack()


print(monAffichage.cget('fg'))


fen_princ.mainloop()

Si vous lancez le code, vous obtiendrez ceci dans la console :

red

05° Rajouter la ligne de code permettant d'afficher le texte qui va être affiché dans ce widget.

3 - Méthode config ou configure

Mais on peut faire mieux : on peut également configurer par un code la valeur d'un attribut. Il faut dans ce cas utiliser la méthode config sur le widget correspondant.

A titre d'exemple, voici un code qui affecte une variable couleur puis qui l'affecte à l'attribut fg de deux widgets. Ainsi, la modification de cette variable permet de modifier la couleur des deux widgets d'un coup. On fait la même chose avec la largeur du widget (via l'attribut height).

from tkinter import *


fen_princ = Tk()


monAffichage = Label(fen_princ, text = "Premier texte")

monAffichage2 = Label(fen_princ, text = "Second texte")

monAffichage.pack()

monAffichage2.pack()


couleur = 'blue'

monAffichage.config(fg=couleur)

monAffichage2.config(fg=couleur)


fen_princ.mainloop()

La modification des attributs

06° Créer un variable fond, affecter un string correspondant à une couleur puis modifier les deux labels pour que leur background correspondent à fond.

La modification de bg

...CORRECTION...

from tkinter import *

fen_princ = Tk()

monAffichage = Label(fen_princ, text = "Premier texte")

monAffichage2 = Label(fen_princ, text = "Second texte")

monAffichage.pack()

monAffichage2.pack()


couleur = 'yellow'

monAffichage.config(fg=couleur)

monAffichage2.config(fg=couleur)

fond = 'black'

monAffichage.config(bg=fond)

monAffichage2.config(bg=fond)


fen_princ.mainloop()

Configure ou config ?

Pour pourriez obtenir le même résultat en utilisant la méthode configure. Je préfère noter config car c'est plus court mais c'est vous qui voyez...

4 - Modifier le texte

Et si on veut afficher un texte variable en fonction des moments ? On peut ? Oui, on peut. Il existe même deux façon de gérer cela.

La première façon, vous la connaissez déjà. Il suffit d'utiliser la méthode config.

Voici un exemple pour la forme : on crée trois Labels et on leur demande d'afficher le même texte.

from tkinter import *


fen_princ = Tk()


monAffichage = Label(fen_princ, text = "Premier texte")

monAffichage2 = Label(fen_princ, text = "Second texte")

monAffichage3 = Label(fen_princ, text = "Troisième texte")

monAffichage.pack()

monAffichage2.pack()

monAffichage3.pack()


monTexte = 'Voici le texte à mettre à jour'

monAffichage.config(text=monTexte)

monAffichage2.config(text=monTexte)

monAffichage3.config(text=monTexte)


fen_princ.mainloop()

La modification des 3 textes

07° Utiliser le code pour vérifier que cela fonctionne.

Ca fonctionne. Mais le module Tkinter possède un moyen plus efficace de mettre à jour automatiquement les Labels texte si le texte à afficher change justement. C'est pratique pour les applications où un même champ peut être affiché à différents endroits. Ca évite d'oublier l'un des labels.

La deuxième méthode consiste à utiliser l’attribut textvariable qu'on doit remplir avec un objet de classe StringVar, à savoir une sorte de String qui, lorsqu'il change de valeur, provoque automatiquement la modification d'affichage du widget (ou des widgets !) auquel il est rattaché.

08° Lancer le code suivant :

from tkinter import *


fen_princ = Tk()


monTexte = StringVar()

monTexte.set("Hello World !")


monAffichage = Label(fen_princ, textvariable = monTexte)

monAffichage.pack()

monTexte.set("Bonjour le Monde !")


fen_princ.mainloop()

Ligne 3 monTexte = StringVar() : On voit qu’on déclare un objet de classe StringVar : c’est une classe de tkinter contenant un string ayant la particularité d’informer le programme lorsque son contenu change.

Ligne 4 monTexte.set("Hello World !"): On utilise la méthode set(…) pour lui affecter un contenu.

Ligne 5 monAffichage = Label(fen_princ, textvariable = monTexte) : On définit l’objet-Label monAffichage dans lequel on déclare monTexte en tant qu’attribut textvariable.

Ligne 6 monAffichage.pack() : On active monAffichage dans la fenêtre fen_princ.

Ligne 7 monTexte.set("Bonjour le Monde !") : On modifie monTexte APRES qu’il se soit affiché à l’écran pour la première fois.

Ligne 8 fen_princ.mainloop() : On surveille la fenêtre fen_princ et on laisse tourner.

On remarque qu’à l’écran c’est bien le second texte qui apparaît.

avec wraplength

09° Lancer le code suivant qui permet de modifier trois labels simplement en modifiant le contenu du StringVar !

from tkinter import *


fen_princ = Tk()


monTexte = StringVar()

monTexte.set("Hello World !")


monAffichage = Label(fen_princ, textvariable = monTexte)

monAffichage.pack()

monAffichage2 = Label(fen_princ, textvariable = monTexte)

monAffichage2.pack()

monAffichage3 = Label(fen_princ, textvariable = monTexte)

monAffichage3.pack()


monTexte.set("Bonjour le Monde !")


fen_princ.mainloop()

Vous devriez obtenir ceci :

3 d'un coup avec StringVar

Quel est l'intérêt d'utiliser des StringVar par rapport à une modification à la rude via un config ? Et bien, vous êtes certain que tous les widgets qui dépendent de votre texte seront modifiés automatiquement. Ca évite d'en oublier un.

Résumé des deux méthodes en lecture et en écriture

En utilisant l'attribut TEXT

On utilise l'attribut text qui doit contenir un String. On doit alors utiliser la méthode config sur le widget Label. Par exemple :

monAffichage.config(text='Nouvel affichage !')

Si on veut lire le contenu du Label, on doit alors utiliser la méthode cget. Exemple :

print( monAffichage.cget('text') )

Cette ligne afficherait ici :

Nouvel affichage !

En utilisant l'attribut TEXTVARIABLE

On utilise l'attribut textvariable qui doit contenir un StringVar(). On doit alors utiliser la méthode set sur l'objet StringVar pour modifier automatiquement l'affichage du widget.

monTexte = StringVar()

monTexte.set("Hello World !")

monAffichage = Label(fen_princ, textvariable = monTexte)

monTexte.set("Bonjour le Monde !")

D'ailleurs, si on veut lire le contenu de l'affichage, il faut également du coup aller voir le contenu du StringVar à l'aide de la méthode get :

print( monTexte.get() )

Cette ligne afficherait ici :

Bonjour le Monde !

Le problème des langages de haut niveau, c'est qu'il existe souvent plusieurs façons de faire les choses. Normalement la méthode get doit être appliquée au StringVar. Mais le module Tkinter peut également comprendre ce que vous voulez si vous utiliser cette méthode sur un Label qui contient un textvariable...

print( monAffichage.get() )

Cette ligne afficherait ici :

Bonjour le Monde !

Voilà pour un premier tour des fonctionnalités des labels text.

5 - Activer une action : la classe Button

Tout d’abord, remarquons qu’on utilise Button, avec un B majuscule. C’est normal : c’est une classe, pas un type de variable.

Devinez comment se nomme sa méthode-constructeur... Button().

Notre programme manque un peu d’interactivité pour l’instant. Nous allons donc créer un bouton qui va enclencher la modification d’un texte affiché.

Attention néanmoins : nous allons devoir utiliser un nouveau concept : la fonction.

Qu’est-ce qu’une fonction ?

C’est simple : il s’agit de lignes de codes qui ne s’activent que lorsqu’on les appelle. Et on peut les appeler autant de fois qu’on veut, il suffit de taper le code une seule fois. Exemple :

def mise_a_jour():

    monTexte.set("Et voilà !")

On crée une fonction en Python à l’aide du mot clé def (comme définition) def mise_a_jour():.

On doit nécessairement faire suivre le nom de la fonction de () (nous verrons pourquoi plus tard ) : def mise_a_jour(): .C'est le fait qu'on trouve () ou (blabla) après une chaîne de caractères qui nous indique qu'on a une fonction ou une méthode (une fonction de classe).

Attention, les instructions qui doivent s’exécuter doivent être tabulées (touche TAB) pour que Python comprenne qu’elles font partie de la fonction. Il est donc vital de bien tabuler votre code. Dans Notepad++, on peut faire s'afficher les tabulations à l'aide du menu AFFICHAGE - SYMBOLES SPECIAUX - AFFICHER ESPACES ET TABULATIONS

Dernier point vital : on finit la définition de la fonction par :: def mise_a_jour():. Cela indique à Python que c'est à partir de là qu'il doit surveiller les tabulations.

10° Lancer le code suivant :

# -*-coding:Utf-8 -*

from tkinter import *


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

# Déclarations des fonctions utilisées

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


def mise_a_jour():

    monTexte.set("Et voilà !")


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

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

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


fen_princ = Tk()


# Création d'un Label nommé monAffichage ayant monTexte comme textvariable

monTexte = StringVar()

monTexte.set("Texte de base !")

monAffichage = Label(fen_princ, textvariable = monTexte)

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()


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

# Bouclage de la fenêtre fen_princ

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


fen_princ.mainloop()

Cela donne l'interface ci-dessous qui possède un Button qui permet de modifier le texte :

avant après

Que fait le programme ?

  • Il définit la fonction mise_a_jour sans la faire fonctionner. Cette fonction pourra modifier le contenu de l'objet monTexte.
  • def mise_a_jour():

        monTexte.set("Et voilà !")

  • Il crée un objet-fenêtre fen_princ (avec fen_princ = Tk()) contenant :
    • Un objet-Label nommé monAffichage qui affiche le contenu d'un objet-StringVar monTexte.
    • monTexte = StringVar()

      monTexte.set("Texte de base !")

      monAffichage = Label(fen_princ, textvariable = monTexte)

      monAffichage.pack()

    • Un objet-Button nommé monBouton qui va faire fonctionner la fonction mise_a_jour().
    • monBouton = Button(fen_princ, text = "Mise à jour du texte ci-dessus", command = mise_a_jour)

      monBouton.pack()

Attention : Le nom de la fonction ne comporte pas de « à » mais bien un « a ». C’est plus sain en terme de code.

11° Rajouter une fonction et un bouton de façon à pouvoir faire varier encore une fois le texte du Label.

Si vous bloquez, une possibilité de correction est téléchargeable en cliquant sur l'image:

correction.

Remarque : On aurait pu faire ça aussi avec un bouton sans nom :

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

On obtient donc une ligne de code plutôt que deux :

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

monBouton.pack()

Attention à ne pas utiliser cette façon de faire si vous avez besoin d'accéder ensuite au widget : avec la déclaration-affichage en une ligne, on ne garde pas en mémoire l'adresse du widget.

Remarque 2 : La console s'affiche encore, c'est pénible. Si vous voulez ne plus l'afficher, il suffit de changer l'extension de votre fichier Python : transformez le .py en .pyw. Le w veut dire without, sans.

Bon, ça commence à ressembler à de l’interactivité. Il reste à enregistrer les données que fournit l’utilisateur.

La partie 1 de ce cours fournissait l’équivalent du print sur la console.

Voyons maintenant l’équivalent du input.

12° Réaliser une interface comportant un Label dans lequel un texte est inscrit en noir. Créer quatre boutons :

  • Le premier permet d'écrire le texte du Label en jaune.
  • Le deuxième permet d'écrire le texte du Label en noir.
  • Le troisième permet de changer le fond du Label en jaune.
  • Le quatrième permet de changer le fond du Label en noir.

La méthode config est bien entendu indispensable.

6 - Récupérer des données "texte" : la classe Entry

Tout d’abord, remarquons qu’on utilise Entry avec un E majuscule. C’est normal : c’est une classe, pas un type de variable.

Imaginons que nous voulions remplacer le texte du Label monAffichage par un texte rentré par l’utilisateur. Comment faire ?

Il faut créer un objet de classe Entry qui comme son nom l’indique permettra à l’utilisateur de rentrer des données.

Commençons par le voir en dehors d’un code plus complexe : imaginons qu’on désire connaître le nom que l’utilisateur veut donner à la sauvegarde de son image :

nom_fichier = StringVar()

nom_fichier.set("mp_rgb_1.jpg")

entree_fichier = Entry(fen_princ, textvariable = nom_fichier, width=30)

entree_fichier.pack()

Explications :

Le code ci-dessus crée un objet de classe StringVar nommé nom_fichier et lui affecte la valeur initiale "mp_rgb1.jpg" :

nom_fichier = StringVar()

nom_fichier.set("mp_rgb_1.jpg")

Ensuite, on crée un objet de classe Entry de nom entree_fichier et on voit qu'on tape textvariable = nom_fichier, pour le lier à l'objet StringVar précédent :

entree_fichier = Entry(fen_princ, textvariable = nom_fichier, width=30)

entree_fichier.pack()

On peut récupérer le contenu textuel du Label comme avec un Label : en allant voir le contenu du StringVar associé à l’aide de la méthode get().

Comme nous l'avons vu plus haut, on peut récupérer le texte de deux façons avec un StringVar :

contenu = nom_fichier.get()

contenu = entree_fichier.get()

13° Créer une fenêtre avec 3 widgets : un Label, un Entry et un Button. En appuyant sur le bouton, on doit changer le contenu textuel du Label en y recopiant le contenu du Entry.

Si vous bloquez, une possibilité de correction est téléchargeable en cliquant sur les images :

correction correction.

Ca commence à être vraiment interactif. En terme de présentation, on notera que la plupart des attributs graphiques de la classe Label existent sur la classe Entry. Taper « Python Tk Entry » pour plus de précision.

Première solution : Sans bouton. On utilise simplement les propriétés des objets de class StringVar et on attache le StringVar à la fois à une Entry et à un Label. Avantage : les modifications sont réalisées en toute simplicité : lorsqu'on modifie le StringVar, le contenu du Label bouge automatiquement aussi.

# -*-coding:Utf-8 -*

from tkinter import *


nom_fichier = StringVar()

nom_fichier.set("mp_rgb_1.jpg")


monAffichage = Label(fen_princ, textvariable = nom_fichier)

monAffichage.pack()


entree_fichier = Entry(fen_princ, textvariable = nom_fichier, width=30)

entree_fichier.pack()

Deuxième solution : Avec bouton. Lorsqu'on appuie sur le bouton, on va lire le contenu de l'Entry et on met alors le Label à jour :

# -*-coding:Utf-8 -*

from tkinter import *


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

# Déclarations des fonctions utilisées

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


def mise_a_jour():

    monTexte.set(entree_fichier.get())


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

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

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


fen_princ = Tk()


# Création d'un Label nommé monAffichage ayant monTexte comme textvariable

monTexte = StringVar()

monTexte.set("Texte de base !")

monAffichage = Label(fen_princ, textvariable = monTexte)

monAffichage.pack()


# Création d'un Entry nommé nom_fichier ayant nom_fichier comme textvariable

nom_fichier = StringVar()

nom_fichier.set("mp_rgb_1.jpg")

entree_fichier = Entry(fen_princ, textvariable = nom_fichier, width = 30)

entree_fichier.pack()


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

monBouton = Button(fen_princ, text = "BOUTON 1", command = mise_a_jour)

monBouton.pack()


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

# Bouclage de la fenêtre fen_princ

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


fen_princ.mainloop()

On remarquera qu'ici on lit le widget contenant le texte plutôt que le StringVar. Mais les deux façons donnent le même résultat :

    monTexte.set(entree_fichier.get())

    monTexte.set(nom_fichier.get())

7 - Supprimer la console

La console apparaît encore ! Pour la faire disparaître, il suffit de changer manuellement l’extension .py en .pyw (le w est pour without, sans en français).

14° Modifier l'extension pour faire disparaitre la console lors de l'exécution.

8 - Afficher un objet Image (de PIL)

Si ce n'est pas déjà fait, il faut installer Pillow. Si vous êtes en salle informatique, vous pouvez passer cette installation, c'est déjà installé.

Pour installer Pillow, il faut :

  1. Avoir installé Python en incluant bien l'option rajout à Path
  2. Ouvrir la console système (pas la console Python)
  3. Peut-être mettre à jour pip en tapant :
  4. > pip install --upgrade pip

  5. Installer le module PILLOW de gestion d’image :
  6. > pip install Pillow

Si ca ne marche pas, je vous laisse aller lire le tutoriel disponible sur notre site :

En réalité, on peut afficher soit du texte soit une image dans les widgets de classe Label.

Il faut d’abord penser à importer les classes Image (ou Img avec un alias) et ImageTk. En effet, la classe Image de base de la bibliothèque PIL n’est pas directement interprétable par les widgets du module Tk. C'est dommage mais c'est comme ça.

from PIL import Image as Img

from PIL import ImageTk

Convertir presentation (un objet Image) en presentationTk (objet ImageTk) est simple :

presentationTk = ImageTk.PhotoImage(presentation)

C'est bête, Tk ne pourra pas travailler directement avec votre objet-image presentation.

Voilà le bout code qui permet d’afficher une image de classe Image(Img) dans labelphoto, un widget de classe Label :

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

presentationTk = ImageTk.PhotoImage(presentation)

labelphoto = Label(fen_princ, image = presentationTk)

labelphoto.pack()

Le code complet :

# -*-coding:Utf-8 -*

from tkinter import *

from PIL import Image as Img

from PIL import ImageTk


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

# 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 = "Une belle image", width = 70)

monAffichage.pack()


# Création d'une image de base

presentation = Img.new("RGB", (40,40), (0,0,255))

presentationTk = ImageTk.PhotoImage(presentation)


# Création d'un label de type image associé à presentationTk

labelphoto = Label(fen_princ, image = presentationTk)

labelphoto.pack()


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

# Bouclage de la fenêtre fen_princ

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


fen_princ.mainloop()

On obtient : (correction en cliquant sur l'image)

correction

15° Tester le code et l'expliquer, ligne par ligne.

Si on veut créer une image variable, c'est un peu plus complexe à cause d'un mécanisme interne de gestion de la mémoire qu'on nomme "ramasse-miettes". En gros, l'ordinateur libère la place mémoire attribuée à des objets dont on ne posséde plus les références. Nous traiterons de cette difficulté dans le chapitre sur la portée des variables dans les fonctions.

Dans cette activité, nous nous contenterons de changer l'image du Label à partir d'une source qui existe déjà dans le programme lui-même. A cause du "ramasse-miettes", afficher une image qu'on crée dans la fonction d'un bouton est beaucoup plus compliqué.

Il suffit donc d'utiliser la méthode config sur l'attribut image du Label.

16° Chercher dans le code suivant l'endroit où on ordonne le changement d'image.

17° Une erreur s'est glissée dans le code. C'est un petit détail dans le nom d'une des variables. Trouvez l'erreur puis réalisez une interface comportant 3 boutons : un bouton Red, un bouton Green et un bouton Blue.

18° Modifier le programme pour que les boutons modifient en plus la couleur du texte qui s'affiche dans le premier Label texte.

# -*-coding:Utf-8 -*

from tkinter import *

from PIL import Image as Img

from PIL import ImageTk


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

# Déclarations des fonctions utilisées

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


def colorierBleu() :

    labelphoto.configure(image = presentationBTk)


def colorierRouge() :

    labelphoto.configure(image = presentationRTk)


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

# 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 = "Belle image variable avec le Button", width=70)

monAffichage.pack()


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

monBoutonB = Button(fen_princ, text = "En bleu !", command = colorierBleu)

monBoutonB.pack()


monBoutonR = Button(fen_princ, text = "En rouge !", command = colorierRouge)

monBoutonR.pack()


# Création d'une image de base

presentation = Img.new("RGB", (40,40), (50,50,70))

presentationTk = ImageTk.PhotoImage(presentation)


# Création d'une image bleue

presentationB = Img.new("RGB", (40,40), (0,0,255))

presentationBTk = ImageTk.PhotoImage(presentationB)


# Création d'une image rouge

presentationR = Img.new("RGB", (40,40), (0,0,255))

presentationRTk = ImageTk.PhotoImage(presentationB)


# Création d'un label de type image associé à presentationTk

labelphoto = Label(fen_princ, image = presentationTk)

labelphoto.pack()


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

# Bouclage de la fenêtre fen_princ

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


fen_princ.mainloop()

09 - Présentation des méthodes geometry et place

Il nous reste deux petites choses à voir :

  1. Comment imposer la taille de la fenêtre de l'application ?
  2. Comment placer préciser le widget à l'aide des coordonnées x et y en pixels ?

Pour le premier point, c'est très facile : il faut utiliser la méthode geometry sur le widget fen_princ :

Voici de quoi créer une fenêtre de dimensions 600 pixels sur 600 pixels :

# -*-coding:Utf-8 -*

from tkinter import *


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

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

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


fen_princ = Tk()

fen_princ.geometry("600x600")


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

# Bouclage de la fenêtre fen_princ

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


fen_princ.mainloop()

Ce code ne crée RIEN sinon une fenêtre Tkinter de largeur 600 px sur une hauteur de 600 pixels :

resultat de geometry

Pour créer un carré bleu et le déplacer, on peut utiliser la méthode place. Par exemple place(x = 50, y = 100) sur le widget que vous voulez voir placer aux coordonnées x = 50 ( 50 pixels vers la gauche) et y = 100 (100 pixels vers le bas).

# -*-coding:Utf-8 -*

from tkinter import *

from PIL import Image as Img

from PIL import ImageTk


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

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

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


fen_princ = Tk()

fen_princ.geometry("600x600")


# Création d'une image de base

carreBleu = Img.new("RGB", (40,40), (0,0,255))

carreBleuTk = ImageTk.PhotoImage(carreBleu)


# Création d'un label de type image associé à presentationTk

image1 = Label(fen_princ, image = carreBleuTk)

image1.place(x = 50, y = 100)


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

# Bouclage de la fenêtre fen_princ

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


fen_princ.mainloop()

On rajoute les bibliothèques Image et ImageTk.

On crée l'image d'un carré bleu avec PIL et on l'utilise pour créer une image ImageTk.

On place cette image dans un Label nommé image1.

On modifie la position de ce widget avec la nouvelle méthode place

resultat de place

19° Utiliser ces nouvelles connaissances pour créer une sorte de Simon, le jeu des carrés colorés :

resultat de 4 place

C'est clairement perfectible : les carrés ne sont pas centrés par exemple. Nous allons voir d'autres façons de placer les widgets dans une autre activité. Mais vous avez au moins une première manière fonctionnelle de placer les widgets où vous voulez.

20° Dernière action : créer 4 boutons GAUCHE-DROITE-HAUT-BAS : ils devront modifier la position des 4 carrés colorés.

Indications :

  • Il faudra utilser la même méthode de placement pour tous les widgets.
  • Il faudra modifier la position des carrés avec la méthode place.

Voilà. C'est tout pour une première prise en main. Mais vous avez déjà de quoi faire. Si vous en voulez plus, il vous suffit de vous renseigner à l'aide de la documentation.

10 - FAQ

Question : Pourquoi mettre les variables faisant référence aux widgets en orange ?

Pour rendre le code plus clair : sur le site, en orange, c'est une variable-objet faisant référence à un widget.

Comme c'est une variable, j'aurai pu simplement les mettre en bleu comme toutes les autres variables. Mais en les placant en orange, on voit plus facilement ce qui est lié à la bibliothèque Tkinter.

De toutes manières, lorsque vous coderez votre propre interface, toutes les variables auront la même couleur.

J'ai donc utilisé ceci :

from tkinter import *


fen_princ = Tk()


monAffichage = Label(fen_princ, text = "C’est ici que j’affiche mon premier texte !")

monAffichage.pack()


fen_princ.mainloop()

Mais j'aurai pu également faire commencer les noms par wid_ par exemple :

from tkinter import *


wid_fen_princ = Tk()


wid_monAffichage = Label(wid_fen_princ, text = "C’est ici que j’affiche mon premier texte !")

wid_monAffichage.pack()


wid_fen_princ.mainloop()

J'aurai même pu ne rien mettre, ce sont juste des variables comme les autres. Des variables de référence, mais des variables.

from tkinter import *


fen_princ = Tk()


monAffichage = Label(fen_princ, text = "C’est ici que j’affiche mon premier texte !")

monAffichage.pack()


fen_princ.mainloop()

Question : Les widgets s'affichent les uns en dessous des autres. On peut faire mieux ?

Oui, bien entendu. Par contre, c'est l'objet d'une autre activité. Vous pouvez aller chercher l'activité sur le placement des Widgets sur ce site par exemple. Ou vous renseignez sur l'utilisation des méthodes pack, grid et place sur Internet.