Cette activité propose de finaliser un peu vos applications Tkinter : nous allons voir comment créer un menu. Cela fera tout de suite plus sérieux !
En réalité, cela n'est pas bien difficile et ne changera pas intégralement la structure de vos programmes Tkinter déjà réalisés. Il faudra simplement intégrer le code des menus.
Nous allons partir sur l'une des applications réalisées lors du chapitre sur les images et Tkinter : l'application Pixellisation.
PS : Merci à Bryan M. qui a réalisé l'application de base, que nous allons modifier ici.
Commençons par créer une fenêtre pour y placer un menu comportant plusieurs zones de sélection possibles.
01° Utiliser le code suivant pour créer votre interface Tkinter basique :
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from tkinter import *
# Création de la fenêtre
Tk()
="Mon application à moi que j'ai")
.title("900x600")
.geometry(# Création du cadre-conteneur pour les menus
Frame( , borderwidth=3, bg='#557788')
=.grid(row=0,column=0)
# Création de l'onglet Fichier
Menubutton( , text='Fichier', width='20', borderwidth=2, bg='gray', activebackground='darkorange',relief = RAISED)
=.grid(row=0,column=0)
# Création de l'onglet Edition
Menubutton( , text='Editer', width='20', borderwidth=2, bg='gray', activebackground='darkorange',relief = RAISED)
=.grid(row=0,column=1)
# Création de l'onglet Format
Menubutton( , text='Format', width='20', borderwidth=2, bg='gray', activebackground='darkorange',relief = RAISED)
=.grid(row=0,column=2)
# Lancement de la surveillance sur la fenêtre
.mainloop()
Vous devriez obtenir ceci :
Il est temps de voir comment cela fonctionne :
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from tkinter import *
# Création de la fenêtre
Tk()
=title("Mon application à moi que j'ai")
."900x600")
.geometry(On crée la fenêtre de l'application en utilisant le constructeur de la classe Tk. Puis :
Passons à la création du cadre (frame en anglais) qui va contenir notre menu.
# Création du cadre-conteneur pour les menus
Frame( , borderwidth=3, bg='#557788')
=.grid(row=0,column=0)
On utilise la méthode-constructeur de la classe Frame en l'attachant à
.On l'affiche ensuite à l'aide de la méthode grid en utilisant la case (0,0).
Du coup, ce n'est pas très beau. Nous allons remplir la ligne en entier en obligeant le widget à prendre toute la place sur la ligne.
02° Remplacer la méthode d'affichage par méthode pack(fill=X)
pour étendre la ligne du menu.
Il est temps de voir comment on crée et affiche les différents onglets du cadre contenant le menu :
# Création de l'onglet Fichier
Menubutton( , text='Fichier', width='20', borderwidth=2, bg='gray', activebackground='darkorange',relief = RAISED)
=.grid(row=0,column=0)
# Création de l'onglet Edition
Menubutton( , text='Editer', width='20', borderwidth=2, bg='gray', activebackground='darkorange',relief = RAISED)
=.grid(row=0,column=1)
# Création de l'onglet Format
Menubutton( , text='Format', width='20', borderwidth=2, bg='gray', activebackground='darkorange',relief = RAISED)
=.grid(row=0,column=2)
On crée donc les widgets de classe Menubutton (avec b, pas B attention). Ces widgets ne sont pas que de simples labels ou boutons. En appuyant dessous, nous pourrons afficher un sous-menu que nous aurons configuré.
03° Comment se nomme le widget-conteneur ? Avec quelle méthode affiche-t-on les objets Menubutton dans ce conteneur ? Y-a-t'il un conflit sachant qu'on avait utilisé pack précédement ?
...CORRECTION...
Le widget conteneur est
, le cadre créé pour cela juste avant.On y fixe les Menubuttons à l'aide de la méthode grid pour placer les menus dans une grille.
On avait placé
dans avec pack.Mais rien n'empêche de travailler avec une autre méthode pour placer les éléments dans
.04° Trouver les noms des attributs des widgets de classe Menubutton permettant de définir le texte affiché sur le sous-menu, la couleur du background et la couleur du background si la souris passe sur le widget.
05° Rajouter un onglet nommé Affichage par exemple.
Maintenant que nous savons comment créer les onglets, il nous reste à voir comment on crée les sous-menus.
Il va falloir créer les widgets correspondants au choix qu'on veut voir s'afficher lorsqu'on clique sur un onglet (qui est un widget de classe Menubutton.
Les différents widgets qu'on va attacher à notre Menubutton sont ici des widgets de classe Menu.
06° Rajouter le code suivant juste avant les deux dernières lignes, celles où lance la surveillance de la fenêtre.
# Création d'un menu défilant
Menu( )
=petitFormat)
.add_command(label='Petit format', command =formatNormal)
.add_command(label="Normal", command =grandFormat)
.add_command(label="Grand format", command =fondClair)
.add_command(label="Fond clair", command =fondSombre)
.add_command(label="Fond sombre", command =# Attribution du menu déroulant au menu Affichage
.configure(menu= )
Vous devriez obtenir ceci (mais en fait vous allez avoir une belle erreur ) :
Pourquoi ? Simplement parce que les attributs command désignent des fonctions qui n'existent pas. Forcément, ça coince.
Il va donc falloir les créer.
07° Rajouter le code suivant juste APRES l'importation de tkinter. Tester à nouveau le programme. Vous devriez maintenant déclencher des messages dans la console.
# Définitions des fonctions
def petitFormat():
print("Petit format")
def formatNormal():
print("Format Normal")
def grandFormat():
print("Grand format")
def fondClair():
print("Fond Clair")
def fondSombre():
print("Fond Sombre")
Vous allez maintenant pouvoir créer les fonctions qui vont agir sur votre application.
08° Modifier les codes des fonctions qu'elles fassent ce qu'on attend d'elles :
geometry
.bg
) de la fenêtre à l'aide de la méthode configure
(ou on également utiliser config
).Vous devez obtenir résultat visuel de ce type (correction en cliquant sur l'image) :
Si vous cliquez sur l'onglet, vous devriez constater qu'il y a une sorte de pointillés entre l'onglet et les choix. En cliquant dessus, vous pouvez extraire le menu de la fenêtre, et rendre ainsi le menu 'indépendant' de la fenêtre. Testez pour voir ceci :
09° Remplacer la méthode constructeur des menus par ceci : Menu( , tearoff = 0)
.
Cela va vous permettre de mettre de côté cette fonctionnalité et d'avoir une interface qui ressemble aux interfaces habituelles.
Il existe de nombreuses options sur ces menus. Nous allons encore en voir une ici, mais si le sujet vous intéresse, pensez à la documentation officielle de Tkinter ou aux nombreux sites qui en parlent.
La fameuse option que nous allons voir est celle des checkbuttons : un menu dans lequel on peut sélectionner une option.
10° Rajouter un nouvel élément à :
bTest = IntVar()
"Controles actifs", variable=bTest, onvalue=1, offvalue=0)
.add_checkbutton(label=Nous avons déjà rencontré ce type de code dès la première activité Tkinter.
Nous créons bTest, un objet de la classe IntVar(). Il s'agit d'une classe propre à Tkinter qui a comme propriétés :
On rajoute ensuite un checkbutton à
. On lui donne les attributs suivants :Vous devriez avoir le visuel donné juste au dessus. Mais pour l'instant, nous n'utilisons pas du tout l'option.
Pour savoir si l'option est sélectionnée, il suffit de faire bTest.get()
. Vous obtiendrez 0
si l'option n'est pas sélectionnée et 1
si l'option est sélectionnée.
11° Modifier le code des fonctions de façon à ce que les modifications de fond ou de format ne puissent se faire que si l'option est sélectionnée.
Correction en cliquant sur l'image :
Nous allons maintenant tenter d'utiliser notre menu sur un programme réel : le programme de pixellisation d'images que vous pouvez trouver ici :
Le code est le suivant mais vous n'avez pas réellement besoin de le comprendre : il vous suffit de connaitre le nom des fonctions à appeller.
Attention : si vous êtes un étudiant à la recherche d'un code fonctionnel et correctement documenté, je vous conseille d'être très prudent si vous décidez de copier ce code : il a été réalisé par un élève n'ayant pas encore assez de recul ou de connaissances sur certains points. Cela se voit. Un simple copier/coller dans vos projets risquent de vous poser quelques soucis lors de votre oral !
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from tkinter import *
from tkinter.filedialog import *
from PIL import Image as Img
from PIL import ImageTk
def ouverture_de_l_image():
"""
Permet de modifier l'image à traiter en modifiant la variable presentation.
Aucun argument à transmettre.
Presentation est déclarée global car on veut lui affecter une nouvelle valeur.
*
La variable presentation contient la référence de l'objet Img qui contient l'image à traiter.
La variable filepath est locale à la fonction.
La variable nomImage fait référence à un objet de classe StringVar du programme principal
La variable labelphoto fait référence à un objet de classe Label du programme principal.
"""
global presentation
#permet d'ouvrir l'image
filepath = askopenfilename(title="Ouvrir une image",filetypes=[('jpg files','.jpg'),('png files','.png'),('all files','.*')])
nomImage.set(filepath)
#change l'image de base de presentation avec l'image choisie
presentation = Img.open(filepath)
presentationTk = ImageTk.PhotoImage(presentation)
# la ligne ci-dessous permet de placer presentationTk (créée dans la fonction) comme image de labelphoto
presentationTk)
.configure (image =# la ligne ci-dessous permet d'éviter le ramasse-miettes : la réf est dans un objet externe à la fonction
presentationTk
.image =def enregistrement_de_la_valeur_de_montexte():
"""
Permet de modifier la valeur affichée dans le label sous le curseur.
Elle correspond alors à la valeur de pixellisation du curseur.
On modifie variable valeurCurseur qui correspond à un StringVar.
Aucun argument à transmettre.
*
La variable valeurCurseur contient la référence de l'objet StringVar du programme principal.
La variable valeur2 contient la référence de l'objet IntVar qui stocke la valeur du Scale (curseur) nommé curseur2.
Cela permettra de l'enregistrer à l'aide d'une autre fonction.
"""
valeurCurseur.set(valeur2.get())
def pixellisation():
"""
Permet de pixelliser l'image stockée en réduisant sa taille puis en l'agrandissant.
Aucun argument à transmettre.
*
On utilise valeur2, un IntVar du programme principal qui fait stocke la valeur de pixellisation voulue.
La variable presentation est un objet Img qui contient l'image à traiter.
La variable valeurCurseur contient la référence de l'objet StringVar du programme principal.
La variable valeur2 contient la référence de l'objet IntVar qui stocke la valeur du Scale (curseur) nommé curseur2.
La variable imageStockage permet de garder en mémoire la nouvelle image Img pixellisée une fois qu'on sort de la fonction.
La variable labelphoto fait référence à un objet de classe Label du programme principal.
"""
# Permet de modifier la variable imageStockage depuis la fonction
global imageStockage
#enregistrement de valeur2 dans la variable x
x = valeur2.get()
#enregistrement de la largeur et de la hauteur de l'image dans des variables
# http://pillow.readthedocs.io/en/4.2.x/reference/ImageTk.html
larg = presentation.width
haut = presentation.height
#taille de la redimension dans une variables
larg2 = int(larg/x)
haut2 = int(haut/x)
#redimension de l'image
z = presentation.resize((larg2,haut2))
z2 = z.resize((larg,haut))
#affichage de l'image pixeliser
presentationTk = ImageTk.PhotoImage(z2)
presentationTk)
.configure (image =presentationTk
.image =imageStockage = z2
def sauvegarder() :
"""
Permet de sauvarder l'image Img stockée dans imageStockage.
Aucun argument à transmettre.
*
On stocke sous le nom 'image-pixellisee.jpg'
"""
imageStockage.save("image-pixellisee.jpg")
# - - - - - - - - - - -
# PROGRAMME PRINCIPAL
# - - - - - - - - - - -
#creation d'une fenetre
Tk()
=#code pour faire la variable de l'image et afficher le nom de l'image choisi
nomImage = StringVar()
Label( , textvariable = nomImage)
=.pack()
#creation des boutons
Button( , text="ouvrir image", command=ouverture_de_l_image).pack()
Button( , text="Afficher variable", command=enregistrement_de_la_valeur_de_montexte).pack()
Button( , text="pixelliser", command=pixellisation).pack()
Button( , text="sauvegarder", command=sauvegarder).pack()
#creation d'une image objet
presentation=Img.new("RGB", (50,50) , (0,0,0) )
presentationTk= ImageTk.PhotoImage(presentation)
Label( , image=presentationTk)
=.pack()
#creation d'un curseur
valeur2 = IntVar()
Scale(fen_princ, from_=0, to=50, variable=valeur2)
=.pack()
# création d'un label montrant la valeur du curseur
valeurCurseur=IntVar()
valeurCurseur.set(0)
Label( , textvariable=valeurCurseur, width=35)
=.pack()
#creation d'une variable de stockage
imageStockage=0
#fin du programme pour garder la fenetre ouverte
.mainloop()
12° Placer ce code (qui permet de pixelliser une image) et le votre (qui permet de créer un menu) dans un seul et même fichier.
Vous devrez faire attention à :
Vous devriez obtenir quelque chose ressemblant à ceci (correction en cliquant sur l'image, mais faites le d'abord vous même !):
13° Rajouter des options dans le menu FICHIER qui permet d'ouvrir et enregistrer une image, en faisant référence aux mêmes fonctions que les boutons. Vous pourrez d'ailleurs supprimer ces boutons une fois le programme fonctionnel d'ailleurs.
14° Rajouter une possibilité pour QUITTER le programme.
Fermeture : Vous pouvez utiliser sur votre fenêtre deux méthodes :
Et voilà. Vous savez à présent comment gérer vos menus sur les interfaces Tkinter.
S'il vous reste du temps, voyez comment rajouter des choix dans le menu EDITER et FORMAT. Pourquoi pas une gestion du format des images, du choix de l'image enregistrée ...
Sinon, la dernière partie est optionnelle mais vous permettra de gérer vos interfaces en créant des classes. Cela vous permettra de rendre vos bouts de programme plus facilement modulables et récupérables.
Partie totalement optionnelle. Par contre, si vous désirez réaliser un programme en utilisant des objets pour gérer certains widgets, je ne peux que vous conseiller de voir ce qu'on y fait !
Nous allons ici réaliser la même interface mais nous allons en réalité créer :
fen_princ
.barreDesMenus
rattaché à la fenêtre principale.affichage
rattaché à la fenêtre principale.Comme barreDesMenus
et affichage
sont affichés dans fen_princ
, on dit que fen_princ
est le widget père, maitre ou la fenêtre mère.
Les deux autre sont les widgets fils ou les fenêtres filles de fen_princ
.
On trouve beaucoup de programme qui stockent l'adresse des fenêtres mères dans une variable qu'on nomme boss
.
Normalement, nous aurions du directement créer nos classes. La modification à partir d'un programme qui n'avait pas été concu pour faire de la programmation objet n'est pas toujours évident. Le plus simple si vous voulez programmez ainsi et de toujours partir directement sur ce type de programmation.
Voici un bout de la version avec une classe BarreDeMenu :
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from tkinter import *
from tkinter.filedialog import *
from PIL import Image as Img
from PIL import ImageTk
# - - - - - - - - - - - -
# DECLARATION DES CLASSES
# - - - - - - - - - - - -
class BarreDeMenu(Frame):
"""Classe permettant d'intégrer facilement une barre de menu à un widget maitre."""
# Methode constructeur
def __init__(self, boss=None):
Frame.__init__(self, borderwidth=3, bg='#557788')
self.pack(fill=X)
self. = Menubutton(self, text='Affichage', width='20', borderwidth=2, bg='gray', activebackground='darkorange',relief = RAISED)
self. .grid(row=0,column=2)
# Création d'un menu défilant
self. = Menu(self. )
self. .add_command(label='Petit format', command = self.petitFormat)
# Attribution du menu déroulant au menu Affichage
self. .configure(menu=self. )
# Methodes rattachées au menu
def petitFormat(self):
self.master.geometry('600x400')
# - - - - - - - - - - -
# PROGRAMME DE TEST
# - - - - - - - - - - -
if __name__ == '__main__':
# Création d'une fenêtre principale
Tk()
="Mon application à moi que j'ai")
.title("900x600")
.geometry(# Création du menu via la classe BarreDeMenu
BarreDeMenu( )
=# Lancement de la surveillance sur la fenêtre
.mainloop()
Alors, comment marche tout cela ?
Regardons comment on lance le programme :
if __name__ == '__main__':
# Création d'une fenêtre principale
Tk()
="Mon application à moi que j'ai")
.title("900x600")
.geometry(# Création du menu via la classe BarreDeMenu
BarreDeMenu( )
=# Lancement de la surveillance sur la fenêtre
.mainloop()
Pour rappel, le test initial n'est vrai que si on lance ce programme en cliquant directement dessus. Si c'est un autre programme qui l'utilise, le test est faux et le code sous le if ne sera pas exécuté.
On voit qu'on y crée BarreDeMenu.
puis qu'on créé , une instance de la classePour créer ce objet (issu de Tkinter), on lui envoie l'argument
qui correspond au widget dans lequel il doit s'afficher.Regardons maintenant cette fameuse classe :
class BarreDeMenu(Frame):
"""Classe permettant d'intégrer facilement une barre de menu à un widget maitre."""
# Methode constructeur
def __init__(self, boss=None):
On voit qu'on se base sur la classe Frame : un cadre dans la synthaxe de Tkinter.
La méthode __init__ possède le paramètre obligatoire des méthodes (self par exemple) mais également un second paramètre, boss, car les méthodes-constructeurs des widgets Tkinter ont besoin de connaitre le maître du widget en cours de construction, de façon à savoir à qui le rattacher.
Si vous regardez le code du programme de test, vous verrez qu'on utilise l'argument boss contient donc (dans notre exemple) la référence à ce widget.
et que le paramètreRegardons maintenant le contenu du constructeur :
# Methode constructeur
def __init__(self, boss=None):
Frame.__init__(self, borderwidth=3, bg='#557788')
self.pack(fill=X)
self. = Menubutton(self, text='Affichage', width='20', borderwidth=2, bg='gray', activebackground='darkorange',relief = RAISED)
self. .grid(row=0,column=2)
# Création d'un menu défilant
self. = Menu(self. )
self. .add_command(label='Petit format', command = self.petitFormat)
# Attribution du menu déroulant au menu Affichage
self. .configure(menu=self. )
On commence par lancer la méthode constructeur __init__ de la classe Frame. Nous sommes en train de redéfinir notre propre méthode __init__ et si vous oubliez cette ligne, tous les automatismes cachés liés aux Frames ne seraient correctement initialisés dans votre Classe.
On affiche ensuite le widget avec pack en utilisant self pour faire référence à lui-même.
Les lignes suivantes permettent de créer les attributs permettant de stocker les différentes variables qui permettront d'afficher ce qu'on désire dans notre cadre.
Ainsi, à chaque fois qu'on faisait référence dans le code sans objet à self puisque nous sommes à l'intérieur du code de cette zone.
, on doit maintenant faire référence àDe la même façon, on stocke les références aux widgets contenus dans la zone en définissant des attributs à l'aide du point entre self et le nom de l'attribut : self.
Dernière chose : la commande fait référence à self.petitFormat car nous décidons de stocker l'effet dans une méthode de cette classe.
Regardons maintenant cette méthode :
# Methodes rattachées au menu
def petitFormat(self):
self.master.geometry('600x400')
La méthode possède le paramètre obligatoire (qu'on nomme self ici). L'élément troublant via de self.master.
Si vous regardez la méthode constructeur, vous pourrez constater qu'aucun attribut master n'a été créé !
Vous devinerez que master fait référence au widget maître de notre classe, celui que nous avons reçu dans le paramètre boss.
Alors, l'attribut master s'est-il construit magiquement en étant de plus affecté du bon widget maitre ? Non bien entendu, cela vient de la ligne Frame.__init__(self, borderwidth=3, bg='#557788')
.
Lors de l'appel à la méthode constructeur de la classe Frame, le code de Tkinter a fait son travail : il a recupéré la valeur de boss pour créer un attribut automatique des widget Tkinter, l'attribut master. Et voilà. Il suffit de le savoir.
15° Créer les autres actions du menu AFFICHAGE.
Voilà. Je pense que vous avez compris l'esprit. Si vous voulez créer d'autres actions, agissant sur d'autres parties du programme, il va falloir trouver le moyen de signaler à votre classe sur quoi agir. Le mieux est donc de créer un ensemble de classes et de créer des méthodes qui leur permettent de communiquer.