Skip to content
Snippets Groups Projects
Select Git revision
  • master default
  • vS5_2020-2021
2 results

seanceTk_4h.md

Blame
  • Forked from Derrode Stéphane / INF-TC2
    39 commits behind the upstream repository.

    Sommaire

    BE #4 : Application de dessin vectoriel

    L'objectif de ce TD est d'apprendre à manipuler quelques composants du module Python Tkinter permettant de créer des interfaces graphiques. Vous allez créer une application simple de dessin vectoriel, qui permettra de tracer à la souris les formes définies dans le BE #2.

    Quelques éléments de Tkinter (45 min.)

    Le module Tkinter ("Tk interface") permet de créer des interfaces graphiques. Il contient de nombreux composants graphiques (ou widgets), tels que les boutons (classe Button), les cases à cocher (classe CheckButton), les étiquettes (classe Label), les zones d'entrée de texte (classe Entry), les menus (classe Menu), ou les zones de dessin (classe Canvas).

    Durant ce BE, nous vous recommandons de conserver la documentation de Tkinter ouverte dans un onglet. Elle contient des exemples de code qui vous seront utiles pour utiliser chacun des widgets.

    Voici un premier exemple de code Tkinter :

    import random
    from tkinter import *
    
    def tirage():
        nb = random.randint(1, 100)
        texteResultat.set('Nombre : ' + str(nb))
    
    if __name__ == '__main__':
        
        # création de l'arbre de scène
        racine = Tk() # Appel à une méthode de classe (et non un constructeur, cf slide cours #2)
        racine.title('Tirage aléatoire')
        racine.geometry('300x100+400+400')
        
        # Les widgets de la scène
        boutonLancer = Button(racine, text='Tirage')
        boutonLancer.pack(side=LEFT, padx=5, pady=5)
        texteResultat = StringVar()
        labelResultat = Label(racine, textvariable=texteResultat)
        labelResultat.pack(side=LEFT, padx=5, pady=5)
        boutonQuitter = Button(racine, text='Quitter')
        boutonQuitter.pack(side=LEFT, padx=5, pady=5)
    
        # association des commandes aux widgets
        boutonLancer.config(command=tirage) # appel dit callback (pas de parenthèses)
        boutonQuitter.config(command=racine.quit) # idem
        racine.mainloop() # affichage de l'interface jusqu'à quit

    Exercice 1 - Copiez le code suivant dans un fichier appelé Exo1.py et exécutez-le pour observer le résultat.

    Attention, utilisateurs de Mac : l'association Spyder+Tkinter ne fonctionne pas bien sous Mac! Lorsque vous quitterez l'interface (par le biais du bouton quitter), la fenêtre va se bloquer (freeze). Deux solutions:

    • soit vous forcez l'application à s’arrêter à chaque fois (utilisez le menu contextuel sur l'icône de l'application concernée dans la barre d'outils);
    • soit vous exécutez votre programme en ligne de commande. Pour cela, ouvrez un terminal dans le répertoire de travail (clic-droit dessus → Nouveau terminal au dossier). Puis lancer la commande : python3 Exo1.py. Vous devriez pouvoir quitter l'application sans difficulté. N'oubliez pas de sauvegarder votre fichier sous Spyder avant toute exécution!

    Prenez le temps d'étudier cet exemple, et répondez aux questions suivantes :

    • Combien d'éléments contient l'arbre de scène ?
    • Que se passe-t-il lorsqu'on clique sur le bouton ?
    • Comment peut-on inverser les positions des deux boutons ?
    • Comment peut-on augmenter l'espace à gauche et à droite du label ?
    • Comment peut-on colorier le texte du label en rouge ?

    Squelette de l'application de dessin (45 min.)

    On souhaite obtenir l'interface ci-dessous, dans laquelle les utilisateurs sélectionneront le type de forme à dessiner avec les boutons, et créeront une forme en cliquant dans la zone située sous la barre d'outils (widget Canvas de Tkinter). On a donné une couleur grise au fond de la fenêtre pour vous aider à déterminer les widgets présents.

    Exercice 2 - Dessinez l'arbre de scène correspondant à cette capture d'écran.

    Une pratique courante dans les interfaces graphiques est de créer des classes qui remplacent des nœuds de l'arbre de scène, et d'y mettre le code de l'application. Ces classes héritent des classes de Tkinter (pour pouvoir les remplacer dans l'arbre), et nous leur ajouterons des attributs et méthodes spécifiques à leurs responsabilités dans l'application de dessin. Nous allons ainsi introduire deux classes :

    • la classe ZoneAffichage, qui hérite de Canvas et gère toutes les opérations de dessin spécifiques à votre application.
    • la classe FenPrincipale, qui hérite de Tk et gère l'initialisation de l'arbre de scène et des callbacks des widgets.

    Voici le diagramme UML correspondant :

    Exercice 3 - Complétez le code ci-dessous avec l'initialisation de votre arbre de scène. Vous utiliserez une instance de ZoneAffichage à la place de Canvas. À ce stade, on ne vous demande pas de programmer les actions, uniquement de mettre en place le design de l'interface. Vous trouverez des exemples d'utilisation de chacun des widgets dans la documentation référencée plus haut.

    from tkinter import *
    
    class ZoneAffichage(Canvas):
        def __init__(self, parent, largeur, hauteur):
            Canvas.__init__(self, parent, width=largeur, height=hauteur)
    
    class FenPrincipale(Tk):
        def __init__(self):
            Tk.__init__(self)
            # L'initialisation de l'arbre de scène se fait ici
    
    if __name__ == "__main__":
        fen = FenPrincipale()
        fen.mainloop()

    Dessin de formes dans le canevas (60 min.)

    Vous trouverez dans le dossier de ce BE le fichier formes.py développé durant le BE #2. Nous avons agrémenté les classes Rectangle et Ellipse pour qu'elles reçoivent un canevas en argument et se dessinent dessus lors de leur initialisation. Téléchargez ce fichier dans votre répertoire de travail.

    Les classes seront intégrées selon le diagramme UML suivant :

    Exercice 4 - Créez une méthode ajout_forme(..., x, y) dans ZoneAffichage qui crée un Rectangle dont le centre sera donné par les 2 arguments x et y, de largeur 10 et de hauteur 20. N'oubliez pas de stocker ce rectangle dans ZoneAffichage !

    Exercice 5 - À l'aide de la méthode bind vue en cours, reliez les clics de souris sur le canevas (évènements <ButtonRelease-1>) à la méthode ajout_forme. Attention, pour utiliser une méthode comme fonction de callback, il faut la précéder de self..

    Exercice 6 - Ajoutez un attribut à ZoneAffichage qui stocke le type de forme actuellement sélectionné, et associez les boutons Rectangle/Ellipse au type de forme qui est dessiné lorsqu'on clique dans le canevas.

    Quelques opérations de dessin supplémentaires (90 min.)

    Nous allons à présent intégrer quelques commandes simples dans l'application de dessin :

    • Lorsqu'on clique sur une forme en maintenant la touche CTRL enfoncée, elle doit être effacée du canevas.
    • Lorsqu'on déplace la souris avec le bouton enfoncé sur une forme, on déplace la forme en même temps que la souris.
    • Lorsqu'on clique sur le bouton Couleur, un sélecteur de couleur apparaît pour choisir la couleur de l'outil de dessin.

    Exercice 7 - Implémentez l'effacement des formes avec CTRL-clic (événement <Control-ButtonRelease-1>). Vous pourrez faire appel aux méthodes contient_point(...) des classes Rectangle et Ellipse pour déterminer si la position de la souris au moment de l’événement est dans le périmètre d'une forme donnée, ainsi qu'à la méthode effacer(...) de la classe Forme.

    Exercice 8 - À l'aide du module colorchooser de Tkinter (from tkinter import colorchooser), liez les clics sur le bouton Couleur à l'affichage d'un sélecteur de couleur, et utilisez la couleur renvoyée pour tous les ajouts de formes suivants.

    Exercice 9 - À l'aide des types d’événements <Button-1>, <B1-Motion> et <ButtonRelease-1>, implémentez la translation des formes lors des actions d'appui-déplacement de la souris. Comment faire pour qu'elles n'interfèrent pas avec la création de nouvelles formes ?

    Exercices bonus

    Il n'y a pas d'ordre prédéfini pour ces trois exercices supplémentaires, choisissez celui dont la fonctionnalité vous semble la plus intéressante.

    Bonus 1 - Durant le BE #2 vous avez conçu un troisième type de forme. Il est temps de l'intégrer à votre application de dessin ! Inspirez-vous du code du fichier formes.py de ce BE pour adapter la classe que vous aviez développée. Vous trouverez également les instructions de dessin dans la documentation de Tkinter sur Canvas.

    Bonus 2 - Maintenant que votre programme de dessin vectoriel est fonctionnel, il devrait être possible d'exporter chaque image produite dans un fichier. On utilise pour cela le format SVG, qui est un fichier texte contenant des instructions de dessin. Il suffit d'écrire <svg width=600 height=400 xmlns=http://www.w3.org/2000/svg> au début du fichier, </svg> à la fin, et d'insérer des balises rect et ellipse entre les deux. C'est à vous !

    Bonus 3 - Dans tout programme de dessin respectable, on doit pouvoir dessiner des formes de tailles arbitraires (pas prédéfinies). À l'aide des types d’événements <Button-1>, <B1-Motion> et <ButtonRelease-1>, faites qu'un mouvement de souris avec le bouton enfoncé dessine une forme en tirant ses coins (lorsqu'il ne déplace pas une forme existante). Vous utiliserez les méthodes redimension_par_points des classes Rectangle et Ellipse.