Vous pouvez me contacter via Facebook pour questions & suggestions :
Page Facebook relative à mon site
Nous avons déjà réalisé de nombreux programmes avec des traitements de plus en plus complexes.
Mais nos entrées et sorties avec les fonctions
input() & print() sont "tristounettes" ...
Le module Tkinter nous permet de réaliser d'agréables interfaces graphiques.
Tkinter est un module intégré à la bibliothèque standard de Python donc il faut simplement l'importer.
En fait avec ce module nous créons une fenêtre comprenant des éléments ou "widgets" ou "contrôles" : cadres, zones de texte, cases à cocher, boutons radio, zone de listes,
boutons de commande, etc.
# nom programme : test_tkinter0.py from tkinter import * # Création d'un objet "fenêtre" fenetre = Tk() # nouvelle instance de Tk fenetre.title("Connexion ") fenetre.geometry("600x400") # pour saisir identifiant label1 = Label(fenetre, text = "Votre identifiant ? ") champ1 = Entry(fenetre, width=30) label1.pack() champ1.pack() # pour saisir identifiant label2 = Label(fenetre, text = "Votre mot de passe ? ") champ2 = Entry(fenetre, width=30) label2.pack() champ2.pack() # Bouton de commande bouton1 = Button(fenetre, text = "connexion") bouton1.pack() fenetre.mainloop()
Il faut importer le module tkinter.
On crée une fenêtre, pour cela, on crée une instance de classe tk que l'on nomme "fenetre".
Puis on attribue à cette fenêtre un titre et une taille par défaut.
Il faut toujours associer une étiquette ("label") à une zone de saisie pour que l'utilisateur sache la nature de ce qu'il doit taper.
label1 = Label(fenetre, text = "Votre identifiant ? ") : définition d'une étiquette à l'intérieur de l'objet " parent. Il faut utiliser la méthode Label() avec deux arguments au moins : l'objet parent, le texte de l'étiquette.
champ1 = Entry(fenetre, width=30) : définition d'une zone de texte à l'intérieur de l'objet parent. Il faut utiliser la méthode Entry() avec deux arguments au moins : l'objet parent (ici "fenetre") puis la largeur du champ (en pixels).
bouton1 = Button(fenetre, text = "connexion") : définition d'un bouton de commande à l'intérieur de l'objet parent. Il faut utiliser la méthode Button() avec au moins deux arguments : l'objet parent, le texte du bouton.
label1.pack() : positionner l'élément "label1" dans la fenêtre.
Il y a 5 widgets donc il y a donc cinq instructions reposant sur la méthode pack().
fenetre.mainloop() : crée une boucle infinie dont on ne sortira que lorsque l'on fermera la fenêtre.
Attention à la "casse".
Les méthodes du module sont sensibles à la casse. Ainsi pour créer une étiquette il faut saisir "Label" (avec un L majuscule)
et pour définir une zone de texte il faut écrire "Entry" (avec un E majuscule).
# nom programme : test_tkinter1.py # objet : présentation de widgets moins connus from tkinter import * # Création d'un objet "fenêtre" fenetre = Tk() # nouvelle instance de Tk fenetre.title("Fiche de renseignements") fenetre.geometry("900x600") fenetre.minsize(300, 200) fenetre.config(bg ="skyblue") for i in range(1,6): fenetre.rowconfigure(i, pad =15) # spinbox e1 = Label(fenetre, text = "Votre âge ? ") spinbox1 = Spinbox(fenetre, from_=20, to=90, increment=1) e1.grid(row =1, column =1) spinbox1.grid(row =1, column =2) #scale e2 = Label(fenetre, text = "Votre poids ? ") scale1 = Scale(fenetre, from_=40, to=100, showvalue=True, label='kilos' ,orient='h') e2.grid(row =2, column =1) scale1.grid(row =2, column =2) #radiobutton ; rd e3 = Label(fenetre, text = "Votre sexe ? ") rb1 = Radiobutton(fenetre, text="femme" , value ="f") rb2 = Radiobutton(fenetre, text="homme", value ="m") e3.grid(row =3, column =1) rb1.grid(row =3, column =2) rb2.grid(row =3, column =3) #checkbutton ; cc e4 = Label(fenetre, text = "Jeux de cartes pratiqués ? ") cc1 = Checkbutton(fenetre, text="belote") cc2 = Checkbutton(fenetre, text ="tarot") cc3 = Checkbutton(fenetre, text ="bridge") e4.grid(row =4, column =1) cc1.grid(row =4, column =2) cc2.grid(row =4, column =3) cc3.grid(row =4, column =4) #listbox e5 = Label(fenetre,text = "sport premier pratiqué ? ") liste1 = Listbox(fenetre) liste1.insert(1,"aucun") liste1.insert(2,"tennis") liste1.insert(3,"vélo") liste1.insert(4,"foot") liste1.insert(5,"natation") liste1.insert(6,"autre") e5.grid(row =5, column =1) liste1.grid(row =5, column =2) fenetre.mainloop()
Il y a beaucoup de nouveautés dans ce programme.
Passons les en revue.
fenetre.geometry("900x600") : dimensions par défaut de la fenêtre.
fenetre.config(bg ="skyblue") : définition d'une couleur de fond pour la fenêtre.
for i in range(1,6):
____fenetre.rowconfigure(i, pad =15) : pour les 5 lignes de la grille on définit une marge externe de 15 pixels.
Ainsi les widgets ne seront pas collés car sinon la hauteur d'une ligne est égale à celle du widget le plus haut dans la ligne.
Par défaut la largeur d'une colonne est égale à celle du widget le plus large dans la colonne. Il existe aussi la méthode columnconfigure
pour produire un espacement vertical.
spinbox1 = Spinbox(fenetre, from_=20, to=90, increment=1) : un "spinbox" permet de sélectionner une valeur de l'intervalle défini en cliquant sur des flèches. Mais si l'intervalle est large, la saisie dans ce type de widget devient fastidieuse.
e1.grid(row =1, column =1) : cette étiquette est placée dans la cellule L1C1 de la grille.
scale1 = Scale(fenetre, from_=40, to=100, showvalue=True, label='kilos' ,orient='h') : un widget de type "scale" permet de sélectionner rapidement une valeur de l'intervalle défini. Ici la valeur sélectionnée est affichée et le widget "scale" est dans l'exemple, orienté horizontalement.
rb1 = Radiobutton(fenetre, text="femme", value="f") : définition d'un bouton radio.
Il y a un deuxième bouton radio. Dans un groupe de boutons radio, un seul peut être coché.
cc1 = Checkbutton(fenetre, text="belote") : définition d'une case à cocher.
liste1 = Listbox(fenetre) : création d'une liste.
Il faut ensuite la remplir avec la méthode insert(rang,valeur). Autant d'instructions que le lignes dans la liste !
C'est un peu lourd. Nous verrons plus loin qu'il y a une méthode plus élégante et plus succincte de remplir le contenu d'une Listbox.
Retenons de cet exemple, que la méthode grid() permet le positionnement sur une grille (des lignes et des colonnes).
En plus des étiquettes et des zones de texte il existe d'autres types de widgets : "spinbox", "scale", case à cocher, bouton radio, zone de liste.
Observez bien l'apparence d'un widget de type "spinbox" (toupie) et "scale" (échelle / curseur). Ceux-qui connaissent bien les formulaires HTML5 ne seront pas surpris. On a le même rendu avec en HTML les instructions : input type ="number" & input type ="range"
On peut diviser la fenêtre en différentes zones appelés "frames" (cadres).
Chaque cadre contient des widgets.
Thème : page d'accueil d'une application pour laquelle l'utilisateur doit être inscrit.
L'utilisateur doit donc se connecter ou s'inscrire, si c'est sa première visite.
# nom programme : test_tkinter4.py # objet : fenêtre avec deux frames from tkinter import * # Création d'un objet "fenêtre" fenetre = Tk() # nouvelle instance de Tk fenetre.title("Connexion / inscription ") fenetre.geometry("900x600") fenetre.config(bg ="white") f1 =LabelFrame(fenetre,bd=2, text ="connexion", bg ="skyblue",relief="groove") f2= LabelFrame(fenetre,bd=2, text ="inscription", bg ="skyblue",relief="groove") f1.pack(side =LEFT, padx =20, pady=20) f2.pack(side =RIGHT,padx =20, pady=20) e1 = Label(f1, text ="identifiant ?").pack(padx =20,pady=20) zt1 = Entry(f1,width =50).pack(padx =20, pady=20) e2 = Label(f1, text ="mot de passe ? ").pack(padx =20,pady=20) zt2 = Entry(f1,width =50).pack(padx =20, pady=20) e3 = Label(f2, text ="identifiant ? ").pack(padx =20,pady=20) zt3 = Entry(f2,width =50).pack(padx =20, pady=20) e4 = Label(f2, text ="confirmer identifiant ! ").pack(padx =20,pady=20) zt4 = Entry(f2,width =50).pack(padx =20, pady=20) e5 = Label(f2, text ="mot de passe ? ").pack(padx =20,pady=20) zt5 = Entry(f2,width =50).pack(padx =20, pady=20) e6 = Label(f2, text =" confirmer mot de passe !").pack(padx =20,pady=20) zt6 = Entry(f2,width =50).pack(padx =20, pady=20) fenetre.mainloop()
Vous trouvez sans doute que le code est un peu lourd. Mais en fait il se produit très rapidement car il y a 6 étiquettes et 6 zones de texte pratiquement identiques pour leurs arguments donc il suffit de réaliser des copier-coller.
f1 =LabelFrame(fenetre,bd=2, text ="connexion", bg ="skyblue", relief="groove") : définition d'un cadre, enfant de "fenetre", borduré
avec l'étiquette "connexion", couleur de fond : "skyblue".
Il existe aussi la méthode frame() mais qui ne prévoit pas d'étiquette.
f1.pack(side =LEFT, padx =20, pady=20) : le frame "f1" est positionné à gauche avec des marges par rapport à son parent : "fenetre".
e1 = Label(f1, text ="identifiant ? ").pack(padx =20, pady=20) : chainage de deux méthodes pour définir une étiquette enfant de "f1" et
la positionner avec des marges par rapport au parent.
Arrivé à ce stade, vous devez vous dire : c'est bien gentil tout ça. Mais à quoi ça sert d'effectuer des saisies
via une belle interface si il n'y a aucun traitement de ces données ?
Et bien j'y arrive.
Thème : via une interface l'utilisateur saisit x et n dans des champs et en retour il y a affichage de x à la puissance n dans un troisième champ.
Une fonction doit donc être capable de récupérer les saisies et injecter le résultat dans une zone de texte.
# nom programme : test_tkinter3.py # objet du programme : élever x à la puisssance n from tkinter import * from math import * def fpuissance(): x = zt1.get() n = zt2.get() resultat = pow(eval(x), eval(n)) zt3.delete(0,END) zt3.insert(0,resultat) # ------ fin fonction # Création d'un objet "fenêtre" fenetre = Tk() # nouvelle instance de Tk fenetre.title("Elever x à la puissance n ") fenetre.geometry("400x400") fenetre.config(bg ="lime") for i in range(1,5): fenetre.rowconfigure(i, pad =20) fenetre.columnconfigure(1,pad = 20) fenetre.columnconfigure(2, pad =20) # pour saisir x e1 = Label(fenetre, text = " valeur de x ? ") e1.grid(row=1, column=1) zt1 = Entry(fenetre, width=30) zt1.grid(row=1, column=2) # pour saisir n e2 = Label(fenetre, text = "valeur de n ? ") e2.grid(row=2, column=1) zt2 = Entry(fenetre, width=30) zt2.grid(row=2, column=2) # Bouton de commande b1 = Button(fenetre, text = "Calcul", command=fpuissance) b1.grid(row=3, column=2) # pour afficher le résultat e3 = Label(fenetre, text = "x à la puissance n = ") e3.grid(row=4, column=1) zt3 = Entry(fenetre, width=30) zt3.grid(row=4, column=2) fenetre.mainloop()
Un objet de type "Button" peut avoir comme argument command =nomFonction
Le code du programme principal ne présente aucune difficulté c'est seulement du design donc une syntaxe déja vue.
Ici le bouton de commande appelle la fonction "fpuissance" ; toutes les nouveautés sont dans cette fonction.
Étudions le code de la fonction.
x = zt1.get() : récupération du contenu du widget "zt1" dans la variable x
n = zt2.get() : la variable n de la fonction récupère le contenu du champ "zt2"
resultat = pow(eval(x), eval(n)) : x et n sont convertis en numérique avant d'être les arguments de la méthode pow().
N'oubliez pas d'importer le module math.
zt3.delete(0,END) : effacer le contenu du champ "zt3"
zt3.insert(0,resultat) : insertion du contenu de la variable resultat dans le champ "zt3"
J'ai demandé d'élever à la puissance 64 l'entier 2. Donc le résultat est tellement énorme qu'il est affiché en notation scientifique.
Le meilleur moyen de vérifier si vous avez bien assimilé tout ce que je viens de dire, c'est de réaliser vous même un programme avec une UI (User Interface).
Vous devez réaliser une calculatrice.
Le programme doit être structuré : fonctions & programme principal.
def saisies() : a= eval(zt1.get()) b = eval(zt2.get()) return a,b def fadd() : a,b = saisies() resultat = a+b zt3.delete(0,END) zt3.insert(0,resultat) def fsous() : a,b = saisies() resultat = a - b zt3.delete(0,END) zt3.insert(0,resultat) ...
Il y a une nouveauté syntaxique : appel d'une fonction dans une fonction.
En effet les fonctions fadd(), fsous(), fmulti(), fdivi(), fexp() appellent chacune la fonction saisies()
celle-ci retourne après conversion, et sous forme d'un tuple, les saisies dans les zones de texte zt1 & zt2
#programme principal from tkinter import * # Création d'une interface graphique fenetre = Tk() # nouvelle instance de Tk fenetre.title("Calculatrice ") fenetre.geometry("900x400") fenetre.resizable(False, False) #l'interface n'est pas redimensionnable fenetre.config(bg ="lime") for i in range(1,6): fenetre.rowconfigure(i, pad =10) for i in range(1,5): fenetre.columnconfigure(i, pad =15) mafonte = "{courier new} 14" # pour saisir A e1 = Label(fenetre, text = " saisir A", font =mafonte) e1.grid(row=1, column=1) zt1 = Entry(fenetre, width=15,font =mafonte) zt1.grid(row=1, column=2) # pour saisir B e2 = Label(fenetre, text = "saisir B", font =mafonte) e2.grid(row=2, column=1) zt2 = Entry(fenetre, width=15,font =mafonte) zt2.grid(row=2, column=2) # Boutons de commande b1 = Button(fenetre, text = "A + B ", command=fadd, font =mafonte) b1.grid(row=3, column=1) b2 = Button(fenetre, text = "A - B ", command=fsous, font =mafonte) b2.grid(row=3, column=2) ... # il manque le code de trois boutons de commande : # b3,b4,b5 pour multiplier, diviser, exponentiel. ... # pour afficher le résultat e3 = Label(fenetre, text = "Résultat ",font =mafonte) e3.grid(row=4, column=1) zt3 = Entry(fenetre, width=20,font =mafonte) zt3.grid(row=4, column=2) fenetre.mainloop()
Dans le programme principal, notez l'instruction fenetre.resizable(False, False).
La fenêtre ne peut être redimensionnée ni en hauteur, ni en largeur.
mafonte = "{courier new} 14" : je définis un objet "font" dénommée "mafonte".
e1 = Label(fenetre, text = " saisir A", font =mafonte) : à chaque widget j'ajoute l'option "font = mafonte" afin
que la taille des textes soit plus grande.
zt1 = Entry(fenetre, width=15,font =mafonte) : évitez des champs de saisie trop larges.
Vous savez pour le moment, récuperer les contenus des zones de texte (widget de type "Entry") mais quid
des saisies effectuées dans des cases à cocher, boutons radio et zones de liste ?
Je vous le dit sans détour ; c'est un peu plus compliqué !
Le programmeur doit utiliser alors des variables spécifiques à tKinter dites variables de contrôle.
Reprenons le thème du questionnaire dans lequel on demande le sexe, le poids, le jeu de cartes préféré et le sport pratiqué.
Le code est assez long et comprend un programme principal pour la description de l'interface et une fonction de traitement.
Nous déclarons 5 variables de contrôle nommées "vc1, ... vc5".
"vc1" est relié au widget "scale" et mémorise un entier.
scale1 = Scale(fenetre, from_=40, to=100, showvalue=True, label='kilos' ,orient='h', variable =vc1) :
dans les arguments de Scale() j'ai rajouté "variable =vc1".
"vc2" est relié au groupe de boutons radio et mémorise une chaine.
rb1 = Radiobutton(fenetre, text="femme" , value ="féminin", variable = vc2) :
pour les boutons radio "rb1" et "rb2" j'ai rajouté dans la méthodeRadiobutton() l'argument "variable =vc2"
Notez bien que pour chaque bouton radio il y a l'argument "value = valeur". Donc si vous sélectionnez "rb1" vous saisissez en fait "féminin" et si vous sélectionnez "rb2" vous saisissez "masculin".
Il y a trois cases à cocher, il faut donc définir trois variables de contrôle qui mémorise un entier (1 ou 0 selon que la case est cochée ou pas) :
"vc3, vc4, vc5"
cc1 = Checkbutton(fenetre, text="belote", variable =vc3) : dans chacun des trois cases à cocher je rajoute dans les arguments de Ckeckbutton() :
"variable = nomVariableControle".
Pour établir le contenu de la Listbox, j'ai utilisée une boucle qui parcoure une liste.
Donc la maintenance logicielle sera simple ; pour modifier le contenu de la Listbox il suffit de modifier la liste "contenu".
bouton1 = Button(fenetre, text = "Récupérez les valeurs saisies", command=mafonction) :
sur clic sur le bouton de commande j'appelle la fonction "mafonction".
Il faut récupérer les valeurs de contrôle dans des variables de la fonction.
monpoids = vc1.get() : récupération du contenu de "vc1" dans variable de fonction "monpoids"
monsexe =vc2.get() : récupération du contenu de "vc2" dans variable de fonction "monsexe"
belote = vc3.get(): récupération dans vc3 de 1 ou zéro.
Mêmes remarques pour les variables de contrôle vc4 et vc5
index = liste1.curselection() : si, dans la liste, j'ai sélectionné par exemple, la cinquième ligne ; la variable "index" récupère 5
grâce à la méthode curselection().
item= liste1.get(index) :la variable "item" récupère l'item d'indice 5 donc "natation".
Notez que la fonction comprend des instructions "print". Donc les sorties se feront dans la fenêtre Shell de l'IDLE.
Tout ça c'est bien mais perfectible !
Pour les jeux de cartes pratiqués, à la place des 1 ou zéro (coché ou décoché) on préferait une chaine du genre : "tarot" ou "bridge"
Les informations saisies doivent s'afficher dans une zone de texte de l'interface.
La taille des caractères doit être plus grande avec de la graisse.
mafonte = "{courier new} 12 bold " : je définis un objet "fonte".
e1 = Label(fenetre, text = "Fiche de renseignements ", font = mafonte) : à chaque widget j'ajoute l'option font = mafonte.
Pour chaque case à cocher, j'ai rajouté un argument à la méthode checkbutton(). Exemple : onvalue = "belote"
Donc si la case est cochée la valeur retournée n'est plus 1 mais la valeur de l'argument onvalue.
Donc les trois variables de contrôle associées aux "Checkbutton" doivent changer de type puisqu'elles doivent désormais retourner une chaine.
zt1 = Entry(fenetre, width=40, font =mafonte) : ajout d'une zone de texte large (40px) qui va afficher la chaine retournée par la fonction.
Les variables "belote", "tarot", "bridge" contiennent désormais une chaine ou zéro (si case non cochée).
Il aurait été judicieux d'utiliser pour les cases à cocher l'option "offvalue" également.
Dans chacune des trois "Checkbutton" on rajoute :Checkbutton( ... , offvalue =" ", ...) . Ainsi si la case en question n'est pas cochée on récupère
une chaine vide (au lieu d'un zéro).
Revenons sur la base de données "animaux.db" qui comprend deux tables : taxons & especes.
Dans le chapitre précédent j'ai présenté un programme basique pour ajouter des lignes à la table 'especes' (avec des input). Je présente maintenant une version améliorée : avec interface graphique.
La saisie sera effectuée via un formulaire (objet Tkinter).
Pour positionner les widgets j'utilise une troisième méthode (après pack() & grid()) : la méthode place().
Avec cette technique on dispose d'un repère cartésien donc l'origine et le coin haut gauche de l'objet Tkinter.
Il faut argumenter la méthode avec des coordonnées X & Y.
Attention le contenu de la Listbox (nommée "liste2") n'est pas dynamique.
L'idéal aurait été de bâtir cette liste à partir de la colonne "taxons.code" (champ "code"
de la table "taxons"). Mais c'est un peu délicat ; un peu de patience, voir la fin de ce chapitre !
Dans la procédure "validation" il faut récupérer la valeur saisie dans "champ1" et la valeur sélectionnée dans "liste2".
Puis il faut argumenter la requête SQL "insert ..." avec ces valeurs.
Dans la procédure "fin" il y a déconnexion de la base, effacement du formulaire et affichage d'un message dans la fenêtre d'exécution.
Comme je dis plus haut, le contenu du widget Listbox n'est pas dynamique.
Je vous propose la solution pour que son contenu soit basé sur la colonne "code" de la table "taxons".
Je crée une liste "contenu" qui est vide.
Cette liste est remplie par l'exécution de la requête "select code from taxons ...".
Il n'y a plus qu'à remplir Listbox avec la liste "contenu" via une boucle basée sur la méthode insert()
Aperçu du formulaire de saisie: