Projet Python Initiation à Tkinter (Tool Kit Interface) Objectif de ce tutoriel : initiation à Tkinter (gestionnaire d’interface graphique) en vue des projets de fin d’année. Gestion du projet Méthodologie Ce travail de prise main de tkinter doit être réparti en plusieurs séances, en autonomie (chez vous), et doit être mené à bien d’ici le mois de mars. Taper et ne pas hésiter à modifier les scripts ci-dessous. Commentez vos scripts sous peine de perdre un temps considérable : ce travail va s’étaler dans le temps ! Parallèlement à cette prise en main, commencer à définir un jeu ou une application que vous souhaiteriez programmer. En cas de problème ou pour rechercher des fonctionnalités supplémentaires, vous trouverez sur le web de très nombreuses ressources (cf. liste non exhaustive ci-dessous). Ressources (liste non exhaustive !) http://apprendre-python.com/page-tkinter-interface-graphique-python-tutoriel (synthétique, parfois des problèmes avec Pyzo) http://fsincere.free.fr/isn/python/cours_python.php (Beaucoup de ressources sur le son, les images…) http://effbot.org/tkinterbook/ (Exhaustif ! Toute la documentation) Ne pas hésiter à lancer des requêtes sur un moteur de recherche. Par exemple : tkinter move image Le site Stack Overflow (forums) proposé dans les réponses à cette requête est généralement très pertinent. Consignes Travail par équipe de 2 ou 3 élèves. Présentation du jeu à la classe en fin d’année et mise à disposition en ligne (espace spécifique sur mon site). Nous n’utiliserons pas le module pygame. Le jeu ou l’application devra obligatoirement sauvegarder des informations dans des fichiers et des bases de données (à venir). Widgets Widget : contraction de l'anglais windows gadget (gadget fenêtre). Les widgets sont tous les objets graphiques que l'on peut insérer dans une interface (fenêtre). Les principaux widgets sont : Les boutons : Les labels : Les zones de saisie : Les canevas : Button Label Entry Canvas (pour commander une action). (pour insérer un texte). (pour permettre l'entrée d'une donnée). (pour insérer des dessins ou des images). Chaque widget a des propriétés et des méthodes qui permettent de régler son apparence et les interactions avec l'utilisateur. https://fr.wikibooks.org/wiki/Programmation_Python/Tkinter PCSI – IPT G. Monod Initiation_Tkinter.docx 1/9 Modules from tkinter import * from tkinter.messagebox import * from numpy import random as rd # Boîtes de dialogue prédéfinies Création (instanciation) d’une fenêtre : Tk() – Boucle principale : méthode mainloop() fenetre = Tk() # Création de la fenêtre, le nom choisi est quelconque : f1 = Tk() convient """ Instructions pour définir et placer les éléments de la fenêtre, interagir… """ fenetre.mainloop() # Boucle scrutant les événements (clavier, souris et autres actions) Il est alors possible de placer des widgets dans la fenêtre et d’interagir avec elle. La méthode mainloop() scrute les évènements déclenchés au clavier ou par les clics souris (cf. méthode bind() ci-dessous). Astuces pratiques Lors de la mise au point d’un script, s’il se produit une erreur qui interrompt l’exécution après l’instruction fenetre = Tk(), taper fenetre.destroy() dans la console interactive afin de supprimer la fenêtre créée (sinon Python se bloque). Contrairement à ce que vous trouverez parfois sur le web, c’est bien fenetre.destroy() et non fenetre.quit() qu’il faut utiliser avec Pyzo (sinon Python se bloque). Widgets Label, Entry, Button - Méthodes destroy() et pack() - Focus # Personnalisation de la fenêtre fenetre = Tk() fenetre.title('3 Widgets') fenetre.geometry('300x100') fenetre['bg'] = 'lightgrey' # Titre de la fenêtre # Dimensions de la fenêtre largeur x hauteur # Couleur du fond # bg = background (arrière-plan) fg = foreground (1er plan) # Widget Label = zone de texte à lire (étiquette, légende, information…) label1 = Label(fenetre, text='Nom : ',fg='blue', bg='yellow') # Positionnement automatique du widget avec la méthode pack() label1.pack(padx=5, pady=5) # padx et pady = distance par rapport au bord de la fenêtre # Widget Entry = zone de texte à entrer (zone de saisie) entree1 = Entry(fenetre, width=100, fg='red') entree1.pack(padx=5, pady=5) # Place le focus sur la zone d'entrée de texte (curseur de la souris dans la zone de saisie) entree1.focus_force() # Widget Button utilisant la méthode destroy SANS PARENTHESES boutQ = Button(fenetre, text='Quitter', command=fenetre.destroy) boutQ.pack(padx=5, pady=5) fenetre.mainloop() PCSI – IPT G. Monod Initiation_Tkinter.docx 2/9 Widget Canvas - Bouton associé à une fonction : command = fonction # Fonction appelée par le bouton boutM def dessineDisque(): """ Déplace le disque aléatoirement """ global largeur, hauteur, dessin, disque xd = rd.randint(30,largeur-30) yd = rd.randint(30,largeur-30) # Modification de la position du disque dessin.coords(disque,xd,yd,xd+30,yd+30) fenetre = Tk() fenetre.title('Widget Canvas') # widget Canvas = zone de dessin (ou fichier image png) hauteur, largeur = 250, 300 dessin = Canvas(fenetre,bg='dark grey',height=hauteur, width=largeur) # Positionnment du widget sur le côté gauche dessin.pack(side=LEFT, padx=5, pady=5) # Dessin d'un disque dans la zone de dessin xd, yd = rd.randint(largeur), rd.randint(hauteur) disque = dessin.create_oval(xd, yd, xd+30, yd+30, width=1, fill='red') # Les coordonnées sont celles du rectangle circonscrit à l'oval # width = épaisseur du contour ; fill = couleur de remplissage # widget Label = zone de texte à lire label1 = Label(fenetre, text='Déplace le disque') # Positionnment automatique label1.pack(padx=5, pady=5) # widget Button utilisant appelant la fonction dessineDisque SANS PARENTHESES boutM = Button(fenetre, text='Move !', command=dessineDisque) # Positionnment automatique boutM.pack(padx=5, pady=5) boutQ = Button(fenetre, text='Quitter', command=fenetre.destroy) # Positionnment du widget en bas boutQ.pack(side=BOTTOM,padx=5, pady=5) fenetre.mainloop() PCSI – IPT G. Monod Initiation_Tkinter.docx 3/9 Animation D'après le site de Gérard Swinnen auteur de « Apprendre à programmer avec Python 3 » : http://inforef.be/swi/python.htm def deplaceDisque(): """ Déplace le disque """ global x, y, dx, dy, anim x, y = x +dx, y + dy if x > 210: x, dx, dy = 210, 0, 15 if y > 210: y, dx, dy = 210, -15, 0 if x < 10: x, dx, dy = 10, 0, -15 if y < 10: y, dx, dy = 10, 15, 0 dessin.coords(oval,x,y,x+30,y+30) if anim: fenetre.after(50,deplaceDisque) # boucler après 50 millisecondes def stop_anim(): """ Arrêt de l'animation """ global anim anim = False def start_anim(): """ Démarrage de l'animation """ global anim if not anim: # pour éviter que le bouton ne puisse lancer plusieurs boucles anim =True deplaceDisque() x, y = 10, 10 dx, dy = 15, 0 anim = False # coordonnées initiales # Pas du déplacement # commutateur fenetre = Tk() fenetre.title("Animation avec Tkinter") dessin = Canvas(fenetre,bg='dark grey',height=250, width=250) dessin.pack(side=LEFT, padx =5, pady =5) oval = dessin.create_oval(x, y, x+30, y+30, width=2, fill='red') boutQ = Button(fenetre,text='Quitter', width =8, command=fenetre.destroy) boutQ.pack(side=BOTTOM,padx =5, pady =5) boutD = Button(fenetre, text='Démarrer', width =8, command=start_anim) boutD.pack(padx =5, pady =5) boutA = Button(fenetre, text='Arrêter', width =8, command=stop_anim) boutA.pack(padx =5, pady =5) fenetre.mainloop() PCSI – IPT G. Monod Initiation_Tkinter.docx 4/9 Détection des événements : clics souris - event.x, event.y et bind() <Button-1>" # Fonction appelée lors de la détection d'un clic gauche (<Button-1>) def souris(event): # event est un évènement (ici clic bouton gauche) """ Récupération des coordonnées du pointeur de la souris """ xsouris, ysouris = event.x, event.y # Affichage des coordonnées dans une boîte de dialogue showinfo('Clic','x =' + str(xsouris) + '\ny =' + str(ysouris)) # Facultatif mais utile pour le débogage ; affichage dans la console print(xsouris, ysouris) fenetre = Tk() fenetre.title('Détection clics') # Surveillance du bouton gauche (<Button-1>) de la souris et appel de souris() fenetre.bind("<Button-1>", souris) fenetre.mainloop() Détection des événements : clavier - event.char, repr() et bind() <Key>, <Up> , <Down> , <Left>, <Right> # Fonction appelée lors de la détection d'une touche enfoncée (<Key>) def clavier(event): # event est un évènement (ici touche enfoncée) """ Récupération de la touche enfoncée """ touchePressee = event.char # Affichage de la touche enfoncée showinfo('Touche pressée',repr(touchePressee)) # Facultatif mais utile pour le débogage ; affichage dans la console print(repr(touchePressee)) fenetre = Tk() fenetre.title('Détection clavier') # Indispensable : place le focus sur la fenêtre qui vient d'être créée fenetre.focus_force() # Surveillance du clavier (<Key>) et appel de clavier() fenetre.bind("<Key>", clavier) # Autres choix : <Up>, <Down>, <Left>, <Right> (flèches du clavier) fenetre.mainloop() PCSI – IPT G. Monod Initiation_Tkinter.docx 5/9 Déplacer une image au clavier ou à la souris (clics ou drag & drop) <B1-Motion> def deplaceImage(event,dx,dy): """ Déplace un objet grâce aux flèches """ global xc, yc, dessin, chat xc += dx yc += dy dessin.move(chat,dx,dy) def sourisClic(event): """ Déplace un objet jusqu'au point cliqué """ global xc, yc xs, ys = event.x, event.y deplaceImage(None,xs-xc,ys-yc) # Réutilisation d'une fct dans un autre contexte => event remplacé par None def sourisDrag(event): """ Déplace un objet par drag & drop """ global xc, yc, dessin xs, ys = event.x, event.y objet = dessin.find_closest(xs,ys) deplaceImage(None,xs-xc,ys-yc) fenetre = Tk() fenetre.title("Déplacement d'un objet") pas = 15 # Pas de déplacement de l'objet au clavier hauteur, largeur = 300, 300 # Dimensions de la zone de dessin dessin = Canvas(fenetre,bg='dark grey',height=hauteur, width=largeur) dessin.pack(side=LEFT, padx=10, pady=10) # Coordonnées du centre de l'image # Personnaliser le chemin (chemin complet si StartDir est resté vide dans Shell/Configuration des Shells) xc, yc = 100, 100 fichier = 'Projets\\Scratchcat2.png' # Création d'une image Tkinter (utiliser un fichier png) imgChat = PhotoImage(file='Projets\\Scratchcat2.png') # Création d'un objet dans la zone de dessin chat = dessin.create_image(xc,yc, image=imgChat) label1 = Label(fenetre, text='Flèches, clic souris ou drag & drop') label1.pack(padx=5, pady=5) boutQ = Button(fenetre, text='Quitter', command=fenetre.destroy) boutQ.pack(side=BOTTOM,padx=10, pady=10) fenetre.focus_force() # Syntaxe appel d'une fct AVEC PARAMETRES : utilisation de lambda fenetre.bind("<Up>", lambda event : deplaceImage(event, 0, -pas)) fenetre.bind("<Down>", lambda event : deplaceImage(event, 0, pas)) fenetre.bind("<Left>", lambda event : deplaceImage(event, -pas,0)) fenetre.bind("<Right>", lambda event : deplaceImage(event, pas,0)) fenetre.bind("<Button-1>", sourisClic) # Surveillance du drag & drop fenetre.bind("<B1-Motion>", sourisDrag) fenetre.mainloop() PCSI – IPT G. Monod Initiation_Tkinter.docx 6/9 Créer, effacer des objets - Activer / désactiver des boutons def creeCarre(): """ Dessine un carré s'il n'en existe aucun """ global existeCarre, xr, yr, largeur, hauteur, cote, dessin, carre if not existeCarre: xr, yr = rd.randint(cote,largeur-cote), rd.randint(cote,hauteur-cote) carre = dessin.create_rectangle(xr, yr, xr+cote, \ yr+cote, width=1, fill='blue') existeCarre = True boutC.configure(state=DISABLED) # Activation / désactivation de boutons boutE.configure(state=ACTIVE) def effaceCarre(): """ Efface un carré existant """ global dessin,existeCarre, carre if existeCarre: dessin.delete(carre) existeCarre = False boutC.configure(state=ACTIVE) boutE.configure(state=DISABLED) def deplaceCarre(event,dx,dy): """ Déplace un carré grâce aux flèches """ global existeCarre, xr, yr, cote, dessin, carre if existeCarre: xr += dx yr += dy dessin.move(carre,dx,dy) # Ou dessin.coords(carre,xr,yr,xr+cote,yr+cote) def sourisClic(event): """ Déplace un carré jusqu'au point cliqué """ global existeCarre, xr, yr, cote xs, ys = event.x, event.y if existeCarre: deplaceCarre(None,xs-xr-cote//2,ys-yr-cote//2) def sourisDrag(event): """ Déplace un carré par drag & drop """ global existeCarre, xr, yr, cote, dessin, carre if existeCarre: xr, yr = event.x - cote//2, event.y - cote//2 objet = dessin.find_closest(xr,yr) dessin.coords(carre,xr,yr,xr+cote,yr+cote) fenetre = Tk() fenetre.title("Déplacement d'un carré") existeCarre = False # Initialisation du booléen utilisé par les fonctions pas = 15 # Pas de déplacement du carré au clavier cote = 30 # Dimension du côté du carré hauteur, largeur = 250, 300 # Dimensions de la zone de dessin dessin = Canvas(fenetre,bg='dark grey',height=hauteur, width=largeur) dessin.pack(side=LEFT, padx=10, pady=10) label1 = Label(fenetre, text='Flèches ou souris') label1.pack(padx=5, pady=5) boutC = Button(fenetre, text='Crée un carré', width = 15, command= creeCarre) boutC.pack(padx=5, pady=5) boutC.configure(state=ACTIVE) # Bouton actif boutE = Button(fenetre, text='Efface un carré', width = 15, command=effaceCarre) boutE.pack(padx=5, pady=5) boutE.configure(state=DISABLED) # Bouton désactivé boutQ = Button(fenetre, text='Quitter', command=fenetre.destroy) boutQ.pack(side=BOTTOM,padx=10, pady=10) fenetre.focus_force() fenetre.bind("<Up>", lambda event : deplaceCarre(event, 0, -pas)) fenetre.bind("<Down>", lambda event : deplaceCarre(event, 0, pas)) fenetre.bind("<Left>", lambda event : deplaceCarre(event, -pas,0)) fenetre.bind("<Right>", lambda event : deplaceCarre(event, pas,0)) fenetre.bind("<Button-1>", sourisClic) fenetre.bind("<B1-Motion>", sourisDrag) fenetre.mainloop() PCSI – IPT G. Monod Initiation_Tkinter.docx 7/9 Récupération du contenu de Entry : get() - Version 1 def afficheTexte(): """ Récupération du texte tapé """ # Méthode get() valeur = int(entree1.get()) if (valeur) > 0: # Affichage d'une boîte de dialogue "info" showinfo('Mon info','Bravo !\nA bientôt ?') fenetre.destroy() else: # Affichage d'une boîte de dialogue "warning" showwarning('Mon avertissement','Raté ! \nTry again...') monTexte ='' # Effacement du contenu de entree1 fenetre = Tk() fenetre.title('Ma fenêtre') label1 = Label(fenetre, text='Entrer un entier : ',fg='blue', bg='yellow') label1.pack(side=LEFT,padx=10, pady=10) monTexte = '' entree1 = Entry(fenetre, textvariable=monTexte, width=50, fg='red') entree1.focus_set() entree1.pack(side=LEFT,padx=10, pady=10) entree1.focus_force() boutA = Button(fenetre, text='Affiche', command=afficheTexte) boutA.pack(padx=10, pady=10) boutQ = Button(fenetre, text='Quitter', command=fenetre.destroy) boutQ.pack(padx=10, pady=10) fenetre.mainloop() Récupération du contenu de Entry : IntVar( ) - Version 2 def afficheTexte(): """ Récupération du texte tapé """ # Utilise une variable typée (cf. ci-dessous) valeur = monTexte.get() if (valeur) > 0: # Affichage d'une boîte de dialogue "info" showinfo('Mon info','Bravo !\nA bientôt ?') fenetre.destroy() else: # Affichage d'une boîte de dialogue "warning" showwarning('Mon avertissement','Raté ! \nTry again...') monTexte.set('') # Effacement du contenu de entree1 fenetre = Tk() fenetre.title('Ma fenêtre') label1 = Label(fenetre, text='Entrer un entier > 0 : ',fg='blue', bg='yellow') label1.pack(side=LEFT,padx=10, pady=10) # Création d'une variable Tkinter traçable (modification détectable par get) monTexte = IntVar() # Autres types : StringVar, BooleanVar, DoubleVar # Utilisation de cette variable dans entree1 entree1 = Entry(fenetre, textvariable=monTexte, width=100, fg='red') Suite identique PCSI – IPT G. Monod Initiation_Tkinter.docx 8/9 Disposition avancée des widgets : grid( ) La méthode grid() permet de disposer les widgets sur une « grille » (une matrice) constituée de lignes et de colonnes. fenetre = Tk() fenetre.title('Ma fenêtre') label1 = Label(fenetre, text='Nom : ',fg='red') label2 = Label(fenetre, text='Prénom : ') label3 = Label(fenetre, text='2015', fg='red') dessin = Canvas(fenetre, width=200, height=200, bg='lightgrey') entree1 = Entry(fenetre, width=50, fg='blue') entree2 = Entry(fenetre, width=50) bout1 = Button(fenetre, text='Appliquer', padx =20) bout2 = Button(fenetre, text='Quitter', command=fenetre.destroy, padx =20) # sticky = collé, E = Est, O = Ouest, rowspan = fusion de lignes label1 .grid(row=1, entree1.grid(row=1, label2 .grid(row=2, entree2.grid(row=2, dessin .grid(row=1, bout1 .grid(row=3, bout2 .grid(row=3, label3 .grid(row=4, column=1, column=2) column=1, column=2) column=3, column=2) column=3) column=2, sticky=E, padx=10) sticky=E, padx=10) rowspan=2, padx=10, pady=10) pady=10) fenetre.mainloop() Autres boîtes de dialogue du module tkinter.messagebox showinfo(), showwarning(), showerror() askquestion(), askokcancel(), askyesno(), askretrycancel() Gestion rigoureuse des exceptions Il existe des instructions permettant une gestion rigoureuse des erreurs susceptibles de se produire au cours d’un programme : try / except. Voir par exemple : https://openclassrooms.com/courses/les-exceptions-9 http://fsincere.free.fr/isn/python/cours_python_ch5.php À vous de jouer ! PCSI – IPT G. Monod Initiation_Tkinter.docx 9/9