Apprendre programmer avec Python


prcdentsommairesuivant

Chapitre 8 : Utilisation de fentres et de graphismes

Jusqu' prsent, nous avons utilis Python exclusivement en mode texte . Nous avons procd ainsi parce qu'il nous fallait absolument d'abord dgager un certain nombre de concepts lmentaires ainsi que la structure de base du langage, avant d'envisager des expriences impliquant des objets informatiques plus labors (fentres, images, sons, etc.). Nous pouvons prsent nous permettre une petite incursion dans le vaste domaine des interfaces graphiques, mais ce ne sera qu'un premier amuse-gueule : il nous reste en effet encore bien des choses fondamentales apprendre, et pour nombre d'entre elles l'approche textuelle reste la plus abordable.

8.1. Interfaces graphiques (GUI)

Si vous ne le saviez pas encore, apprenez ds prsent que le domaine des interfaces graphiques (ou GUI : Graphical User Interface) est extrmement complexe. Chaque systme d'exploitation peut en effet proposer plusieurs bibliothques de fonctions graphiques de base, auxquelles viennent frquemment s'ajouter de nombreux complments, plus ou moins spcifiques de langages de programmation particuliers. Tous ces composants sont gnralement prsents comme des classes d'objets, dont il vous faudra tudier les attributs et les mthodes.

Avec Python, la bibliothque graphique la plus utilise jusqu' prsent est la bibliothque Tkinter, qui est une adaptation de la bibliothque Tk dveloppe l'origine pour le langage Tcl. Plusieurs autres bibliothques graphiques fort intressantes ont t proposes pour Python : wxPython, pyQT, pyGTK, etc. Il existe galement des possibilits d'utiliser les bibliothques de widgets Java et les MFC de Windows.
Dans le cadre de ces notes, nous nous limiterons cependant Tkinter, dont il existe fort heureusement des versions similaires (et gratuites) pour les plates-formes Linux, Windows et Mac.

8.2. Premiers pas avec Tkinter

Pour la suite des explications, nous supposerons bien videmment que le module Tkinter a dj t install sur votre systme. Pour pouvoir en utiliser les fonctionnalits dans un script Python, il faut que l'une des premires lignes de ce script contienne l'instruction d'importation :

 
Sélectionnez

from Tkinter import *

Comme toujours sous Python, il n'est mme pas ncessaire d'crire un script. Vous pouvez faire un grand nombre d'expriences directement la ligne de commande, en ayant simplement lanc Python en mode interactif. Dans l'exemple qui suit, nous allons crer une fentre trs simple, et y ajouter deux widgets27 typiques : un bout de texte (ou label) et un bouton (ou button).

Image non disponible
 
Sélectionnez

>>> from Tkinter import *
>>> fen1 = Tk()
>>> tex1 = Label(fen1, text='Bonjour tout le monde !', fg='red')
>>> tex1.pack()
>>> bou1 = Button(fen1, text='Quitter', command = fen1.destroy)
>>> bou1.pack()
>>> fen1.mainloop()

Note : Suivant la version de Python utilise, vous verrez dj apparatre la fentre d'application immdiatement aprs avoir entr la deuxime commande de cet exemple, ou bien seulement aprs la septime28.

Examinons prsent plus en dtail chacune des lignes de commandes excutes :

  1. Comme cela a dj t expliqu prcdemment, il est ais de construire diffrents modules Python, qui contiendront des scripts, des dfinitions de fonctions, des classes d'objets, etc. On peut alors importer tout ou partie de ces modules dans n'importe quel programme, ou mme dans l'interprteur fonctionnant en mode interactif (c'est--dire directement la ligne de commande). C'est ce que nous faisons la premire ligne de notre exemple : from Tkinter import * consiste importer toutes les classes contenues dans le module Tkinter.
    Nous devrons de plus en plus souvent parler de ces classes. En programmation, on appelle ainsi des gnrateurs d'objets, lesquels sont eux-mmes des morceaux de programmes rutilisables.
    Nous n'allons pas essayer de vous fournir ds prsent une dfinition dfinitive et prcise de ce que sont les objets et les classes, mais plutt vous proposer d'en utiliser directement quelques-un (e)s. Nous affinerons notre comprhension petit petit par la suite.

  2. A la deuxime ligne de notre exemple : fen1 = Tk() , nous utilisons l'une des classes du module Tkinter, la classe Tk(), et nous en crons une instance (autre terme dsignant un objet spcifique), savoir la fentre fen1.

    Ce processus d'instanciation d'un objet partir d'une classe est une opration fondamentale dans les techniques actuelles de programmation. Celles-ci font en effet de plus en plus souvent appel une mthodologie que l'on appelle programmation oriente objet (ou OOP : Object Oriented Programming).

    La classe est en quelque sorte un modle gnral (ou un moule) partir duquel on demande la machine de construire un objet informatique particulier. La classe contient toute une srie de dfinitions et d'options diverses, dont nous n'utilisons qu'une partie dans l'objet que nous crons partir d'elle. Ainsi la classe Tk() , qui est l'une des classes les plus fondamentales de la bibliothque Tkinter, contient tout ce qu'il faut pour engendrer diffrents types de fentres d'application, de tailles ou de couleurs diverses, avec ou sans barre de menus, etc.

    Nous nous en servons ici pour crer notre objet graphique de base, savoir la fentre qui contiendra tout le reste. Dans les parenthses de Tk(), nous pourrions prciser diffrentes options, mais nous laisserons cela pour un peu plus tard.

    L'instruction d'instanciation ressemble une simple affectation de variable. Comprenons bien cependant qu'il se passe ici deux choses la fois :

    • la cration d'un nouvel objet, (lequel peut tre complexe et donc occuper un espace mmoire considrable)
    • l'affectation d'une variable qui va dsormais servir de rfrence pour manipuler l'objet29.


  3. A la troisime ligne : tex1 = Label(fen1, text='Bonjour tout le monde !', fg='red') , nous crons un autre objet (un widget), cette fois partir de la classe Label().
    Comme son nom l'indique, cette classe dfinit toutes sortes d'tiquettes (ou de libells ). En fait, il s'agit tout simplement de fragments de texte quelconques, utilisables pour afficher des informations et des messages divers l'intrieur d'une fentre.
    Nous efforant d'apprendre au passage la manire correcte d'exprimer les choses, nous dirons donc que nous crons ici l'objet tex1 par instanciation de la classe Label().

    Remarquons ici que nous faisons appel une classe, de la mme manire que nous faisons appel une fonction : c'est--dire en fournissant un certain nombre d'arguments dans des parenthses. Nous verrons plus loin qu'une classe est en fait une sorte de 'conteneur' dans lequel sont regroupes des fonctions et des donnes.

    Quels arguments avons-nous donc fournis pour cette instanciation ?

    • Le premier argument transmis (fen1), indique que le nouveau widget que nous sommes en train de crer sera contenu dans un autre widget prexistant, que nous dsignons donc ici comme son matre : l'objet fen1 est le widget matre de l'objet tex1. (On pourra dire aussi que l'objet tex1 est un widget esclave de l'objet fen1).
    • Les deux arguments suivants servent prciser la forme exacte que doit prendre notre widget. Ce sont en effet deux options de cration, chacune fournie sous la forme d'une chane de caractres : d'abord le texte de l'tiquette, ensuite sa couleur d'avant-plan (ou foreground, en abrg fg). Ainsi le texte que nous voulons afficher est bien dfini, et il doit apparatre color en rouge.
      Nous pourrions encore prciser bien d'autres caractristiques : la police utiliser, ou la couleur d'arrire-plan, par exemple. Toutes ces caractristiques ont cependant une valeur par dfaut dans les dfinitions internes de la classe Label(). Nous ne devons indiquer des options que pour les caractristiques que nous souhaitons diffrentes du modle standard.


  4. A la quatrime ligne de notre exemple : tex1.pack() , nous activons une mthode associe l'objet tex1 : la mthode pack(). Nous avons dj rencontr ce terme de mthode ( propos des listes, notamment). Une mthode est une fonction intgre un objet (on dira aussi qu'elle est encapsule dans l'objet). Nous apprendrons bientt qu'un objet informatique est en fait un morceau de programme contenant toujours :

    • un certain nombre de donnes (numriques ou autres), contenues dans des variables de types divers : on les appelle les attributs (ou les proprits) de l'objet.
    • un certain nombre de procdures ou de fonctions (qui sont donc des algorithmes) : on les appelle les mthodes de l'objet.

    La mthode pack() fait partie d'un ensemble de mthodes qui sont applicables non seulement aux widgets de la classe Label(), mais aussi la plupart des autres widgets Tkinter, et qui agissent sur leur disposition gomtrique dans la fentre. Comme vous pouvez le constater par vous-mme si vous entrez les commandes de notre exemple une par une, la mthode pack() rduit automatiquement la taille de la fentre matre afin qu'elle soit juste assez grande pour contenir les widgets esclaves dfinis au pralable.

  5. A la cinquime ligne : bou1 = Button(fen1, text='Quitter', command = fen1.destroy) , nous crons notre second widget esclave : un bouton.
    Comme nous l'avons fait pour le widget prcdent, nous appelons la classe Button() en fournissant entre parenthses un certain nombre d'arguments. tant donn qu'il s'agit cette fois d'un objet interactif, nous devons prciser avec l'option command ce qui devra se passer lorsque l'utilisateur effectuera un clic sur le bouton. Dans ce cas prcis, nous actionnerons la mthode destroy associe l'objet fen1, ce qui devrait provoquer l'effacement de la fentre.

  6. La sixime ligne utilise la mthode pack() pour adapter la gomtrie de la fentre au nouvel objet que nous venons d'y intgrer.

  7. La septime ligne : fen1.mainloop() est trs importante, parce que c'est elle qui provoque le dmarrage du rceptionnaire d'vnements associ la fentre. Cette instruction est ncessaire pour que votre application soit l'afft des clics de souris, des pressions exerces sur les touches du clavier, etc. C'est donc cette instruction qui la met en marche , en quelque sorte.

    Comme son nom l'indique (mainloop), il s'agit d'une mthode de l'objet fen1, qui active une boucle de programme, laquelle tournera en permanence en tche de fond, dans l'attente de messages mis par le systme d'exploitation de l'ordinateur. Celui-ci interroge en effet sans cesse son environnement, notamment au niveau des priphriques d'entre (souris, clavier, etc.). Lorsqu'un vnement quelconque est dtect, divers messages dcrivant cet vnement sont expdis aux programmes qui souhaitent en tre avertis. Voyons cela un peu plus en dtail.

27 "widget" est le rsultat de la contraction de l'expression "window gadget". Dans certains environnements de programmation, on appellera cela plutt un "contrle" ou un "composant graphique". Ce terme dsigne en fait toute entit susceptible d'tre place dans une fentre d'application, comme par exemple un bouton, une case cocher, une image, etc., et parfois aussi la fentre elle-mme.

28 Si vous effectuez cet exercice sous Windows, nous vous conseillons d'utiliser de prfrence une version standard de Python dans une fentre DOS ou dans IDLE plutt que PythonWin. Vous pourrez mieux observer ce qui se passe aprs l'entre de chaque commande.

29 Cette concision du langage est une consquence du typage dynamique des variables en vigueur sous Python. D'autres langages utilisent une instruction particulire (telle que new) pour instancier un nouvel objet. Exemple : maVoiture = new Cadillac (instanciation d'un objet de classe Cadillac, rfrenc dans la variable maVoiture)

8.3. Programmes pilots par des vnements

Vous venez d'exprimenter votre premier programme utilisant une interface graphique. Ce type de programme est structur d'une manire diffrente des scripts textuels tudis auparavant.

Tous les programmes d'ordinateur comportent grosso-modo trois phases principales : une phase d'initialisation, laquelle contient les instructions qui prparent le travail effectuer (appel des modules externes ncessaires, ouverture de fichiers, connexion un serveur de bases de donnes ou l'internet, etc.), une phase centrale o l'on trouve la vritable fonctionnalit du programme (c'est-dire tout ce qu'il est cens faire : afficher des donnes l'cran, effectuer des calculs, modifier le contenu d'un fichier, imprimer, etc.), et enfin une phase de terminaison qui sert clturer proprement les oprations (c'est--dire fermer les fichiers rests ouverts, couper les connexions externes, etc.)

Dans un programme en mode texte , ces trois phases sont simplement organises suivant un schma linaire comme dans l'illustration ci-contre. En consquence, ces programmes se caractrisent par une interactivit trs limite avec l'utilisateur. Celui-ci ne dispose pratiquement d'aucune libert : il lui est demand de temps autre d'entrer des donnes au clavier, mais toujours dans un ordre prdtermin correspondant la squence d'instructions du programme.
Image non disponible

Dans le cas d'un programme qui utilise une interface graphique, par contre, l'organisation interne est diffrente. On dit d'un tel programme qu'il est pilot par les vnements. Aprs sa phase d'initialisation, un programme de ce type se met en quelque sorte en attente , et passe la main un autre logiciel, lequel est plus ou moins intimement intgr au systme d'exploitation de l'ordinateur et tourne en permanence.

Ce rceptionnaire d'vnements scrute sans cesse tous les priphriques (clavier, souris, horloge, modem, etc.) et ragit immdiatement lorsqu'un vnement y est dtect.
Un tel vnement peut tre une action quelconque de l'utilisateur : dplacement de la souris, appui sur une touche, etc., mais aussi un vnement externe ou un automatisme (top d'horloge, par ex.)

Image non disponible

Lorsqu'il dtecte un vnement, le rceptionnaire envoie un message spcifique au programme30, lequel doit tre conu pour ragir en consquence.

La phase d'initialisation d'un programme utilisant une interface graphique comporte un ensemble d'instructions qui mettent en place les divers composants interactifs de cette interface (fentres, boutons, cases cocher, etc.). D'autres instructions dfinissent les messages d'vnements qui devront tre pris en charge : on peut en effet dcider que le programme ne ragira qu' certains vnements en ignorant tous les autres.

Alors que dans un programme textuel , la phase centrale est constitue d'une suite d'instructions qui dcrivent l'avance l'ordre dans lequel la machine devra excuter ses diffrentes tches (mme s'il est prvu des cheminements diffrents en rponse certaines conditions rencontres en cours de route), on ne trouve dans la phase centrale d'un programme avec interface graphique qu'un ensemble de fonctions indpendantes. Chacune de ces fonctions est appele spcifiquement lorsqu'un vnement particulier est dtect par le systme d'exploitation : elle effectue alors le travail que l'on attend du programme en rponse cet vnement, et rien d'autre31.

Il est important de bien comprendre ici que pendant tout ce temps, le rceptionnaire continue tourner et guetter l'apparition d'autres vnements ventuels.

S'il arrive d'autres vnements, il peut donc se faire qu'une seconde fonction (ou une 3e, une 4e, ...) soit active et commence effectuer son travail en parallle avec la premire qui n'a pas encore termin le sien32. Les systmes d'exploitation et les langages modernes permettent en effet ce paralllisme que l'on appelle aussi multitche.

Au chapitre prcdent de ces notes, nous vous avons dj fait remarquer que la structure du texte d'un programme n'indique pas directement l'ordre dans lequel les instructions seront finalement excutes. Cette remarque s'applique encore bien davantage dans le cas d'un programme avec interface graphique, puisque l'ordre dans lequel les fonctions sont appeles n'est plus inscrit nulle part dans le programme. Ce sont les vnements qui pilotent !

Tout ceci doit vous paratre un peu compliqu. Nous allons l'illustrer dans quelques exemples.

30 Ces messages sont souvent nots WM (Window messages) dans un environnement graphique constitu de fentres (avec de nombreuses zones ractives : boutons, cases cocher, menus droulants, etc.). Dans la description des algorithmes, il arrive frquemment aussi qu'on confonde ces messages avec les vnements eux-mmes.

31 Au sens strict, une telle fonction qui ne devra renvoyer aucune valeur est donc plutt une procdure (cfr. page 70).

32 En particulier, la mme fonction peut tre appele plusieurs fois en rponse l'occurrence de quelques vnements identiques, la mme tche tant alors effectue en plusieurs exemplaires concurrents. Nous verrons plus loin qu'il peut en rsulter des "effets de bords" gnants.

8.3.1. Exemple graphique : trac de lignes dans un canevas

Le script dcrit ci-dessous cre une fentre comportant trois boutons et un canevas. Suivant la terminologie de Tkinter, un canevas est une surface rectangulaire dlimite, dans laquelle on peut installer ensuite divers dessins et images l'aide de mthodes spcifiques33.

Lorsque l'on actionne le bouton Tracer une ligne , une nouvelle ligne colore apparat sur le canevas, avec chaque fois une inclinaison diffrente de la prcdente.

Si l'on actionne le bouton Autre couleur , une nouvelle couleur est tire au hasard dans une srie limite. Cette couleur est celle qui s'appliquera aux tracs suivants.

Image non disponible

Le bouton Quitter sert bien videmment terminer l'application en refermant la fentre.

 
Sélectionnez

# Petit exercice utilisant la bibliothque graphique Tkinter
 
from Tkinter import *
from random import randrange
 
# --- dfinition des fonctions gestionnaires d'vnements : ---
def drawline():
    "Trac d'une ligne dans le canevas can1"
    global x1, y1, x2, y2, coul
    can1.create_line(x1,y1,x2,y2,width=2,fill=coul)
 
    # modification des coordonnes pour la ligne suivante :
    y2, y1 = y2+10, y1-10
 
def changecolor():
    "Changement alatoire de la couleur du trac"
    global coul
    pal=['purple','cyan','maroon','green','red','blue','orange','yellow']
    c = randrange(8)             # => gnre un nombre alatoire de 0  7
    coul = pal[c]
 
#------ Programme principal -------
 
# les variables suivantes seront utilises de manire globale :
x1, y1, x2, y2 = 10, 190, 190, 10             # coordonnes de la ligne
coul = 'dark green'                           # couleur de la ligne
 
# Cration du widget principal ("matre") :
fen1 = Tk()
# cration des widgets "esclaves" :
can1 = Canvas(fen1,bg='dark grey',height=200,width=200)
can1.pack(side=LEFT)
bou1 = Button(fen1,text='Quitter',command=fen1.quit)
bou1.pack(side=BOTTOM)
bou2 = Button(fen1,text='Tracer une ligne',command=drawline)
bou2.pack()
bou3 = Button(fen1,text='Autre couleur',command=changecolor)
bou3.pack()
 
fen1.mainloop()              # dmarrage du rceptionnaire d'vnements
 
fen1.destroy()               # destruction (fermeture) de la fentre

Conformment ce que nous avons expliqu dans le texte des pages prcdentes, la fonctionnalit de ce programme est essentiellement assure par les deux fonctions drawline() et changecolor(), qui seront actives par des vnements, ceux-ci tant eux-mmes dfinis dans la phase d'initialisation.

Dans cette phase d'initialisation, on commence par importer l'intgralit du module Tkinter ainsi qu'une fonction du module random qui permet de tirer des nombres au hasard. On cre ensuite les diffrents widgets par instanciation partir des classes Tk(), Canvas() et Button(). (Remarquons au passage que le mot canevas s'crit diffremment en franais et en anglais !)

L'initialisation se termine avec l'instruction fen1.mainloop() qui dmarre le rceptionnaire d'vnements. Les instructions qui suivent ne seront excutes qu' la sortie de cette boucle, sortie elle-mme dclenche par la mthode fen1.quit() (voir ci-aprs).

L'option command utilise dans l'instruction d'instanciation des boutons permet de dsigner la fonction qui devra tre appele lorsqu'un vnement <i><clic gauche de la souris sur le widget></i> se produira. Il s'agit en fait d'un raccourci pour cet vnement particulier, qui vous est propos par Tkinter pour votre facilit parce que cet vnement est celui que l'on associe naturellement un widget de type bouton. Nous verrons plus loin qu'il existe d'autres techniques plus gnrales pour associer n'importe quel type d'vnement n'importe quel widget.

Les fonctions de ce script peuvent modifier les valeurs de certaines variables qui ont t dfinies au niveau principal du programme. Cela est rendu possible grce l'instruction global utilise dans la dfinition de ces fonctions. Nous nous permettrons de procder ainsi pendant quelque temps encore (ne serait-ce que pour vous habituer distinguer les comportements des variables locales et globales), mais comme vous le comprendrez plus loin, cette pratique n'est pas tout fait recommandable, surtout lorsqu'il s'agit d'crire de grands programmes. Nous apprendrons une meilleure technique lorsque nous aborderons l'tude des classes ( partir de la page 152).

Dans notre fonction changecolor(), une couleur est choisie au hasard dans une liste. Nous utilisons pour ce faire la fonction randrange() importe du module random. Appele avec un argument N, cette fonction renvoie un nombre entier, tir au hasard entre zro et N-1.

La commande lie au bouton Quitter appelle la mthode quit() de la fentre fen1. Cette mthode sert fermer (quitter) le rceptionnaire d'vnements (mainloop) associ cette fentre. Lorsque cette mthode est active, l'excution du programme se poursuit avec les instructions qui suivent l'appel de mainloop. Dans notre exemple, cela consiste donc effacer (destroy) la fentre.

33 Ces dessins pourront ventuellement tre anims dans une phase ultrieure (voir plus loin)

(8) Exercices : modifications au programme Trac de lignes ci-dessus.

8.1. Comment faut-il modifier le programme pour ne plus avoir que des lignes de couleur cyan, maroon et green ?

8.2. Comment modifier le programme pour que toutes les lignes traces soient horizontales et parallles ?

8.3. Agrandissez le canevas de manire lui donner une largeur de 500 units et une hauteur de 650. Modifiez galement la taille des lignes, afin que leurs extrmits se confondent avec les bords du canevas.

8.4. Ajoutez une fonction drawline2 qui tracera deux lignes rouges en croix au centre du canevas : l'une horizontale et l'autre verticale. Ajoutez galement un bouton portant l'indication viseur . Un clic sur ce bouton devra provoquer l'affichage de la croix.

8.5. Reprenez le programme initial. Remplacez la mthode create_line par create_rectangle . Que se passe-t-il ?
De la mme faon, essayez aussi create_arc , create_oval , et create_polygon . Pour chacune de ces mthodes, notez ce qu'indiquent les coordonnes fournies en paramtres.
(Remarque : pour le polygone, il est ncessaire de modifier lgrement le programme !)

8.6. - Supprimez la ligne global x1, y1, x2, y2 dans la fonction drawline du programme original. Que se passe-t-il ? Pourquoi ?
- Si vous placez plutt x1, y1, x2, y2 entre les parenthses, dans la ligne de dfinition de la fonction drawline , de manire transmettre ces variables la fonction en tant que paramtres, le programme fonctionne-t-il encore ? (N'oubliez pas de modifier aussi la ligne du programme qui fait appel cette fonction !)
- Si vous dfinissez x1, y1, x2, y2 = 10, 390, 390, 10 la place de global x1, y1, ... , que se passe-t-il ? Pourquoi ? Quelle conclusion pouvez-vous tirer de tout cela ?

8.7. a) Crez un court programme qui dessinera les 5 anneaux olympiques dans un rectangle de fond blanc (white). Un boutton Quitter doit permettre de fermer la fentre.
b) Modifiez le programme ci-dessus en y ajoutant 5 boutons. Chacun de ces boutons provoquera le trac de chacun des 5 anneaux

8.8. Dans votre cahier, tablissez un tableau deux colonnes. Vous y noterez gauche les dfinitions des classes d'objets dj rencontres (avec leur liste de paramtres), et droite les mthodes associes ces classes (galement avec leurs paramtres). Laisser de la place pour complter ultrieurement.

8.3.2. Exemple graphique : deux dessins alterns

Cet autre exemple vous montrera comment vous pouvez exploiter les connaissances que vous avez acquises prcdemment concernant les boucles, les listes et les fonctions, afin de raliser de nombreux dessins avec seulement quelques lignes de code. Il s'agit d'une petite application qui affiche l'un ou l'autre des deux dessins reproduits ci-contre, en fonction du bouton choisi.

 
Sélectionnez

from Tkinter import *
 
def cercle(x, y, r, coul ='black'):
    "trac d'un cercle de centre (x,y) et de rayon r"
    can.create_oval(x-r, y-r, x+r, y+r, outline=coul)
 
def figure_1():
    "dessiner une cible"
    # Effacer d'abord tout dessin prexistant :
    can.delete(ALL)
    # tracer les deux lignes (vert. et horiz.) :
    can.create_line(100, 0, 100, 200, fill ='blue')
    can.create_line(0, 100, 200, 100, fill ='blue')
    # tracer plusieurs cercles concentriques :
    rayon = 15
    while rayon < 100:
        cercle(100, 100, rayon)
        rayon += 15
 
def figure_2():
    "dessiner un visage simplifi"
    # Effacer d'abord tout dessin prexistant :
    can.delete(ALL)
    # Les caractristiques de chaque cercle sont
    # places dans une liste de listes :
    cc =[[100, 100, 80, 'red'],       # visage
    [70, 70, 15, 'blue'],             # yeux
    [130, 70, 15, 'blue'],
    [70, 70, 5, 'black'],
    [130, 70, 5, 'black'],
    [44, 115, 20, 'red'],             # joues
    [156, 115, 20, 'red'],
    [100, 95, 15, 'purple'],          # nez
    [100, 145, 30, 'purple']]         # bouche
    # on trace tous les cercles  l'aide d'une boucle :
    i =0
    while i < len(cc):            # parcours de la liste
        el = cc[i]                # chaque lment est lui-mme une liste
        cercle(el[0], el[1], el[2], el[3])
        i += 1
 
##### Programme principal : ############
fen = Tk()
can = Canvas(fen, width =200, height =200, bg ='ivory')
can.pack(side =TOP, padx =5, pady =5)
b1 = Button(fen, text ='dessin 1', command =figure_1)
b1.pack(side =LEFT, padx =3, pady =3)
b2 = Button(fen, text ='dessin 2', command =figure_2)
b2.pack(side =RIGHT, padx =3, pady =3)
fen.mainloop()
Image non disponible
Image non disponible

Commenons par analyser le programme principal, la fin du script :
Nous y crons une fentre, par instanciation d'un objet de la classe Tk() dans la variable fen. Ensuite, nous installons 3 widgets dans cette fentre : un canevas et deux boutons. Le canevas est instanci dans la variable can, et les deux boutons dans les variables b1 et b2. Comme dans le script prcdent, les widgets sont mis en place dans la fentre l'aide de leur mthode pack(), mais cette fois nous utilisons celle-ci avec des options :

  • l'option side peut accepter les valeurs TOP, BOTTOM, LEFT ou RIGHT, pour pousser le widget du ct correspondant dans la fentre.
  • les options padx et pady permettent de rserver un petit espace autour du widget. Cet espace est exprim en nombre de pixels : padx rserve un espace gauche et droite du widget, pady rserve un espace au-dessus et au-dessous du widget.

Les boutons commandent l'affichage des deux dessins, en invoquant les fonctions figure_1() et figure_2(). Considrant que nous aurions tracer un certain nombre de cercles dans ces dessins, nous avons estim qu'il serait bien utile de dfinir d'abord une fonction cercle() spcialise. En effet : Vous savez probablement dj que le canevas Tkinter est dot d'une mthode create_oval() qui permet de dessiner des ellipses quelconques (et donc aussi des cercles), mais cette mthode doit tre invoque avec quatre arguments qui seront les coordonnes des coins suprieur gauche et infrieur droit d'un rectangle fictif, dans lequel l'ellipse viendra alors s'inscrire. Cela n'est pas trs pratique dans le cas particulier du cercle : il nous semblera plus naturel de commander ce trac en fournissant les coordonnes de son centre ainsi que son rayon. C'est ce que nous obtiendrons avec notre fonction cercle(), laquelle invoque la mthode create_oval() en effectuant la conversion des coordonnes. Remarquez que cette fonction attend un argument facultatif en ce qui concerne la couleur du cercle tracer (noir par dfaut).

L'efficacit de cette approche apparat clairement dans la fonction figure_1(), ou nous trouvons une simple boucle de rptition pour dessiner toute la srie de cercles (de mme centre et de rayon croissant). Notez au passage l'utilisation de l'oprateur += qui permet d'incrmenter une variable (dans notre exemple, la variable r voit sa valeur augmenter de 15 units chaque itration).

Le second dessin est un peu plus complexe, parce qu'il est compos de cercles de tailles varies centrs sur des points diffrents. Nous pouvons tout de mme tracer tous ces cercles l'aide d'une seule boucle de rptition, si nous mettons profit nos connaissances concernant les listes.

En effet. Ce qui diffrencie les cercles que nous voulons tracer tient en quatre caractristiques : coordonnes x et y du centre, rayon et couleur. Pour chaque cercle, nous pouvons placer ces quatre caractristiques dans une petite liste, et rassembler toutes les petites listes ainsi obtenues dans une autre liste plus grande. Nous disposerons ainsi d'une liste de listes, qu'il suffira ensuite de parcourir l'aide d'une boucle pour effectuer les tracs correspondants.

Exercices :

8.9. Inspirez-vous du script prcdent pour crire une petite application qui fait apparatre un damier (dessin de cases noires sur fond blanc) lorsque l'on clique sur un bouton :

8.10. l'application de l'exercice prcdent, ajoutez un bouton qui fera apparatre des pions au hasard sur le damier (chaque pression sur le bouton fera apparatre un nouveau pion).

Image non disponible

8.3.3. Exemple graphique : calculatrice minimaliste

Bien que trs court, le petit script ci-dessous implmente une calculatrice complte, avec laquelle vous pourrez mme effectuer des calculs comportant des parenthses et des fonctions scientifiques. N'y voyez rien d'extraordinaire. Toute cette fonctionnalit n'est qu'une consquence du fait que vous utilisez un interprteur plutt qu'un compilateur pour excuter vos programmes.

Image non disponible

Comme vous le savez, le compilateur n'intervient qu'une seule fois, pour traduire l'ensemble de votre code source en un programme excutable. Son rle est donc termin avant mme l'excution du programme. L'interprteur, quant lui, est toujours actif pendant l'excution du programme, et donc tout fait disponible pour traduire un nouveau code source quelconque, comme par exemple une expression mathmatique entre au clavier par l'utilisateur.

Les langages interprts disposent donc toujours de fonctions permettant d'valuer une chane de caractres comme une suite d'instructions du langage lui-mme. Il devient alors possible de construire en peu de lignes des structures de programmes trs dynamiques. Dans l'exemple cidessous, nous utilisons la fonction intgre eval() pour analyser l'expression mathmatique entre par l'utilisateur dans le champ prvu cet effet, et nous n'avons plus ensuite qu' afficher le rsultat.

 
Sélectionnez

# Exercice utilisant la bibliothque graphique Tkinter et le module math
 
from Tkinter import *
from math import *
 
# dfinition de l'action  effectuer si l'utilisateur actionne
# la touche "enter" alors qu'il dite le champ d'entre :
 
def evaluer(event):
    chaine.configure(text = "Rsultat = " + str(eval(entree.get())))
 
# ----- Programme principal : -----
fenetre = Tk()
entree = Entry(fenetre)
entree.bind("<Return>", evaluer)
chaine = Label(fenetre)
entree.pack()
chaine.pack()
 
fenetre.mainloop()

Au dbut du script, nous commenons par importer les modules Tkinter et math, ce dernier tant ncessaire afin que la dite calculatrice puisse disposer de toutes les fonctions mathmatiques et scientifiques usuelles : sinus, cosinus, racine carre, etc.

Ensuite nous dfinissons une fonction evaluer(), qui sera en fait la commande excute par le programme lorsque l'utilisateur actionnera la touche Return (ou Enter) aprs avoir entr une expression mathmatique quelconque dans le champ d'entre dcrit plus loin.

Cette fonction utilise la mthode configure() du widget chaine34, pour modifier son attribut text. L'attribut en question reoit donc ici une nouvelle valeur, dtermine par ce que nous avons crit la droite du signe gale : il s'agit en l'occurrence d'une chane de caractres construite dynamiquement, l'aide de deux fonctions intgres dans Python : eval() et str(), et d'une mthode associe un widget Tkinter : la mthode get().

eval() fait appel l'interprteur pour valuer une expression Python qui lui est transmise dans une chane de caractres. Le rsultat de l'valuation est fourni en retour. Exemple :

 
Sélectionnez

chaine = "(25 + 8)/3"            # chane contenant une expression mathmatique
res = eval(chaine)               # valuation de l'expression contenue dans la chane
print res +5                     # => le contenu de la variable res est numrique

str() transforme une expression numrique en chane de caractres. Nous devons faire appel cette fonction parce que la prcdente renvoie une valeur numrique, que nous convertissons nouveau en chane de caractres pour pouvoir l'incorporer au message Rsultat = .

get() est une mthode associe aux widgets de la classe Entry. Dans notre petit programme exemple, nous utilisons un widget de ce type pour permettre l'utilisateur d'entrer une expression numrique quelconque l'aide de son clavier. La mthode get() permet en quelque sorte d'extraire du widget entree la chane de caractres qui lui a t fournie par l'utilisateur.

Le corps du programme principal contient la phase d'initialisation, qui se termine par la mise en route de l'observateur d'vnements (mainloop). On y trouve l'instanciation d'une fentre Tk(), contenant un widget chaine instanci partir de la classe Label(), et un widget entree instanci partir de la classe Entry().

Attention, prsent : afin que ce dernier widget puisse vraiment faire son travail, c'est--dire transmettre au programme l'expression que l'utilisateur y aura encode, nous lui associons un vnement l'aide de la mthode bind()35 :

 
Sélectionnez

entree.bind("<Return>",evaluer)

Cette instruction signifie : Lier l'vnement <pression sur la touche Return> l'objet <entree>, le gestionnaire de cet vnement tant la fonction <evaluer> .

L'vnement prendre en charge est dcrit dans une chane de caractres spcifique (dans notre exemple, il s'agit de la chane <Return> ). Il existe un grand nombre de ces vnements (mouvements et clics de la souris, enfoncement des touches du clavier, positionnement et redimensionnement des fentres, passage au premier plan, etc.). Vous trouverez la liste des chanes spcifiques de tous ces vnements dans les ouvrages de rfrence traitant de Tkinter.

Profitons de l'occasion pour observer encore une fois la syntaxe des instructions destines mettre en oeuvre une mthode associe un objet :

objet.mthode(arguments)

On crit d'abord le nom de l'objet sur lequel on dsire intervenir, puis le point (qui fait office d'oprateur), puis le nom de la mthode mettre en oeuvre ; entre les parenthses associes cette mthode, on indique enfin les arguments qu'on souhaite lui transmettre.

34 La mthode configure() peut s'appliquer n'importe quel widget prexistant, pour en modifier les proprits.

35 En anglais, le mot bind signifie "lier"

8.3.4. Exemple graphique : dtection et positionnement d'un clic de souris

Dans la dfinition de la fonction evaluer de l'exemple prcdent, vous aurez remarqu que nous avons fourni un argument event (entre les parenthses).

Cet argument est obligatoire. Lorsque vous dfinissez une fonction gestionnaire d'vnement qui est associe un widget quelconque l'aide de sa mthode bind(), vous devez toujours l'utiliser comme premier argument. Il s'agit d'un objet Python standard, cr automatiquement, qui permet de transmettre au gestionnaire d'vnement un certain nombre d'attributs de cet vnement :

  • le type d'vnement : dplacement de la souris, enfoncement ou relchement de l'un de ses boutons, appui sur une touche du clavier, entre du curseur dans une zone prdfinie, ouverture ou fermeture d'une fentre, etc.
  • une srie de proprits de l'vnement : l'instant o il s'est produit, ses coordonnes, les caractristiques du ou des widget(s) concern(s), etc.

Nous n'allons pas entrer dans trop de dtails. Si vous voulez bien encoder et exprimenter le petit script ci-dessous, vous aurez vite compris le principe.

 
Sélectionnez

# Dtection et positionnement d'un clic de souris dans une fentre :
 
from Tkinter import *
 
def pointeur(event):
    chaine.configure(text = "Clic dtect en X =" + str(event.x) +\
                            ", Y =" + str(event.y))
 
fen = Tk()
cadre = Frame(fen, width =200, height =150, bg="light yellow")
cadre.bind("<Button-1>", pointeur)
cadre.pack()
chaine = Label(fen)
chaine.pack()
 
fen.mainloop()
Le script fait apparatre une fentre contenant un cadre (frame) rectangulaire de couleur jaune ple, dans lequel l'utilisateur est invit effectuer des clics de souris.

La mthode bind() du widget cadre associe l'vnement <clic l'aide du premier bouton de la souris> au gestionnaire d'vnement pointeur .

Ce gestionnaire d'vnement peut utiliser les attributs x et y de l'objet event gnr automatiquement par Python, pour construire la chane de caractres qui affichera la position de la souris au moment du clic.
Image non disponible

Exercice :

8.11. Modifiez le script ci-dessus de manire faire apparatre un petit cercle rouge l'endroit o l'utilisateur a effectu son clic (vous devrez d'abord remplacer le widget Frame par un widget Canvas).

8.4. Les classes de widgets Tkinter

Note : Au long de ce cours, nous vous prsenterons petit petit le mode d'utilisation d'un certain nombre de widgets. Comprenez bien cependant qu'il n'entre pas dans nos intentions de fournir ici un manuel de rfrence complet sur Tkinter. Nous limiterons nos explications aux widgets qui nous semblent les plus intressants d'un point de vue didactique, c'est--dire ceux qui pourront nous aider mettre en vidence des concepts importants, tel le concept de classe. Veuillez donc consulter la littrature (voir page 8) si vous souhaitez davantage de prcisions.

Il existe 15 classes de base pour les widgets Tkinter :

Widget Description
Button Un bouton classique, utiliser pour provoquer l'excution d'une commande quelconque.
Canvas Un espace pour disposer divers lments graphiques. Ce widget peut tre utilis pour dessiner, crer des diteurs graphiques, et aussi pour implmenter des widgets personnaliss.
Checkbutton Une case cocher qui peut prendre deux tats distincts (la case est coche ou non). Un clic sur ce widget provoque le changement d'tat.
Entry Un champ d'entre, dans lequel l'utilisateur du programme pourra insrer un texte quelconque partir du clavier.
Frame Une surface rectangulaire dans la fentre, o l'on peut disposer d'autres widgets.
Cette surface peut tre colore. Elle peut aussi tre dcore d'une bordure.
Label Un texte (ou libell) quelconque (ventuellement une image).
Listbox Une liste de choix proposs l'utilisateur, gnralement prsents dans une sorte de bote. On peut galement configurer la Listbox de telle manire qu'elle se comporte comme une srie de boutons radio ou de cases cocher.
Menu Un menu. Ce peut tre un menu droulant attach la barre de titre, ou bien un menu pop up apparaissant n'importe o la suite d'un clic.
Menubutton Un bouton-menu, utiliser pour implmenter des menus droulants.
Message Permet d'afficher un texte. Ce widget est une variante du widget Label, qui permet d'adapter automatiquement le texte affich une certaine taille ou un certain rapport largeur/hauteur.
Radiobutton Reprsente (par un point noir dans un petit cercle) une des valeurs d'une variable qui peut en possder plusieurs. Cliquer sur un bouton radio donne la valeur correspondante la variable, et "vide" tous les autres boutons radio associs la mme variable.
Scale Vous permet de faire varier de manire trs visuelle la valeur d'une variable, en dplaant un curseur le long d'une rgle.
Scrollbar ascenseur ou barre de dfilement que vous pouvez utiliser en association avec les autres widgets : Canvas, Entry, Listbox, Text.
Text Affichage de texte formatt. Permet aussi l'utilisateur d'diter le texte affich. Des images peuvent galement tre insres.
Toplevel Une fentre affiche sparment, par-dessus .

Ces classes de widgets intgrent chacune un grand nombre de mthodes. On peut aussi leur associer (lier) des vnements, comme nous venons de le voir dans les pages prcdentes. Vous allez apprendre en outre que tous ces widgets peuvent tre positionns dans les fentres l'aide de trois mthodes diffrentes : la mthode grid(), la mthode pack() et la mthode place().

L'utilit de ces mthodes apparat clairement lorsque l'on s'efforce de raliser des programmes portables (c'est--dire susceptibles de fonctionner indiffremment sur des systmes d'exploitation aussi diffrents que Unix, MacOS ou Windows), et dont les fentres soient redimensionnables.

8.5. Utilisation de la mthode grid() pour contrler la disposition des widgets

Jusqu' prsent, nous avons toujours dispos les widgets dans leur fentre, l'aide de la mthode pack(). Cette mthode prsentait l'avantage d'tre extraordinairement simple, mais elle ne nous donnait pas beaucoup de libert pour disposer les widgets notre guise. Comment faire, par exemple, pour obtenir la fentre ci-contre ? Image non disponible

Nous pourrions effectuer un certain nombre de tentatives en fournissant la mthode pack() des arguments de type side = , comme nous l'avons dj fait prcdemment, mais cela ne nous mne pas trs loin. Essayons par exemple :

 
Sélectionnez

from Tkinter import *
 
fen1 = Tk()
txt1 = Label(fen1, text = 'Premier champ :')
txt2 = Label(fen1, text = 'Second :')
entr1 = Entry(fen1)
entr2 = Entry(fen1)
txt1.pack(side =LEFT)
txt2.pack(side =LEFT)
entr1.pack(side =RIGHT)
entr2.pack(side =RIGHT)
 
fen1.mainloop()

... mais le rsultat n'est pas vraiment celui que nous recherchions !!! :

Image non disponible

Pour mieux comprendre comment fonctionne la mthode pack(), vous pouvez encore essayer diffrentes combinaisons d'options, telles que side =TOP, side =BOTTOM, pour chacun de ces quatre widgets. Mais vous n'arriverez certainement pas obtenir ce qui vous a t demand. Vous pourriez peut-tre y parvenir en dfinissant deux widgets Frame() supplmentaires, et en y incorporant ensuite sparment les widgets Label() et Entry(). Cela devient fort compliqu.

Il est temps que nous apprenions utiliser une autre approche du problme. Veuillez donc analyser le script ci-dessous : il contient en effet (presque) la solution :

from Tkinter import * fen1 = Tk() txt1 = Label(fen1, text = 'Premier champ :') txt2 = Label(fen1, text = 'Second :') entr1 = Entry(fen1) entr2 = Entry(fen1) txt1.grid(row =0) txt2.grid(row =1) entr1.grid(row =0, column =1) entr2.grid(row =1, column =1) fen1.mainloop() Image non disponible

Dans ce script, nous avons donc remplac la mthode pack() par la mthode grid(). Comme vous pouvez le constater, l'utilisation de la mthode grid() est trs simple. Cette mthode considre la fentre comme un tableau (ou une grille). Il suffit alors de lui indiquer dans quelle ligne (row) et dans quelle colonne (column) de ce tableau on souhaite placer les widgets. On peut numroter les lignes et les colonnes comme on veut, en partant de zro, ou de un, ou encore d'un nombre quelconque : Tkinter ignorera les lignes et colonnes vides. Notez cependant que si vous ne fournissez aucun numro pour une ligne ou une colonne, la valeur par dfaut sera zro.

Tkinter dtermine automatiquement le nombre de lignes et de colonnes ncessaire. Mais ce n'est pas tout : si vous examinez en dtail la petite fentre produite par le script ci-dessus, vous constaterez que nous n'avons pas encore tout fait atteint le but poursuivi. Les deux chanes apparaissant dans la partie gauche de la fentre sont centres, alors que nous souhaitions les aligner l'une et l'autre par la droite. Pour obtenir ce rsultat, il nous suffit d'ajouter un argument dans l'appel de la mthode grid() utilise pour ces widgets. L'option sticky peut prendre l'une des quatre valeurs N, S, W, E (les quatre points cardinaux en anglais). En fonction de cette valeur, on obtiendra un alignement des widgets par le haut, par le bas, par la gauche ou par la droite. Remplacez donc les deux premires instructions grid() du script par :

 
Sélectionnez

txt1.grid(row =0, sticky =E)
txt2.grid(row =1, sticky =E)

... et vous atteindrez enfin exactement le but recherch.


Analysons prsent la fentre suivante :

Image non disponible

Cette fentre comporte 3 colonnes : une premire avec les 3 chanes de caractres, une seconde avec les 3 champs d'entre, et une troisime avec l'image. Les deux premires colonnes comportent chacune 3 lignes, mais l'image situe dans la dernire colonne s'tale en quelque sorte sur les trois.

Le code correspondant est le suivant :

 
Sélectionnez

from Tkinter import *
 
fen1 = Tk()
 
# cration de widgets 'Label' et 'Entry' :
txt1 = Label(fen1, text ='Premier champ :')
txt2 = Label(fen1, text ='Second :')
txt3 = Label(fen1, text ='Troisime :')
entr1 = Entry(fen1)
entr2 = Entry(fen1)
entr3 = Entry(fen1)
 
# cration d'un widget 'Canvas' contenant une image bitmap :
can1 = Canvas(fen1, width =160, height =160, bg ='white')
photo = PhotoImage(file ='Martin_P.gif')
item = can1.create_image(80, 80, image =photo)
 
# Mise en page  l'aide de la mthode 'grid' :
txt1.grid(row =1, sticky =E)
txt2.grid(row =2, sticky =E)
txt3.grid(row =3, sticky =E)
entr1.grid(row =1, column =2)
entr2.grid(row =2, column =2)
entr3.grid(row =3, column =2)
can1.grid(row =1, column =3, rowspan =3, padx =10, pady =5)
 
# dmarrage :
fen1.mainloop()

Pour pouvoir faire fonctionner ce script, il vous faudra probablement remplacer le nom du fichier image (Martin_P.gif) par le nom d'une image de votre choix. Attention : la bibliothque Tkinter standard n'accepte qu'un petit nombre de formats pour cette image. Choisissez de prfrence le format GIF.


Nous pouvons remarquer un certain nombre de choses dans ce script :

  1. La technique utilise pour incorporer une image :
    Tkinter ne permet pas d'insrer directement une image dans une fentre. Il faut d'abord installer un canevas, et ensuite positionner l'image dans celui-ci. Nous avons opt pour un canevas de couleur blanche, afin de pouvoir le distinguer de la fentre. Vous pouvez remplacer le paramtre bg ='white' par bg ='gray' si vous souhaitez que le canevas devienne invisible. tant donn qu'il existe de nombreux types d'images, nous devons en outre dclarer l'objet image comme tant un bitmap GIF, partir de la classe PhotoImage()36.

  2. La ligne o nous installons l'image dans le canevas est la ligne :

     
    Sélectionnez
    
    item = can1.create_image(80, 80, image =photo)
    

    Pour employer un vocabulaire correct, nous dirons que nous utilisons ici la mthodecreate_image() associe l'objet can1 (lequel objet est lui-mme une instance de la classeCanvas). Les deux premiers arguments transmis (80, 80) indiquent les coordonnes x et y du canevas o il faut placer le centre de l'image. (Les dimensions du canevas tant de 160x160, notre choix aboutira donc un centrage de l'image au milieu du canevas).

  3. La numrotation des lignes et colonnes dans la mthode grid() :
    On peut constater que la numrotation des lignes et des colonnes dans la mthode grid() utilise ici commence cette fois partir de 1 (et non partir de zro comme dans le script prcdent). Comme nous l'avons dj signal plus haut, ce choix de numrotation est tout fait libre. On pourrait tout aussi bien numroter : 5, 10, 15, 20... puisque Tkinter ignore les lignes et les colonnes vides. Numroter partir de l augmente probablement la lisibilit de notre code.

  4. Les arguments utiliss avec grid() pour positionner le canevas :

     
    Sélectionnez
    
    can1.grid(row =1, column =3, rowspan =3, padx =10, pady =5)
    

    Les deux premiers arguments indiquent que le canevas sera plac dans la premire ligne de la troisime colonne. Le troisime (rowspan =3) indique qu'il pourra s'taler sur trois lignes. Les deux derniers (padx =10, pady =5) indiquent la dimension de l'espace qu'il faut rserver autour de ce widget (en largeur et en hauteur).

  5. Et tant que nous y sommes, profitons de cet exemple de script que nous avons dj bien dcortiqu, pour apprendre simplifier quelque peu notre code ...

36 Il existe d'autres classes d'images, mais pour les utiliser il faut importer dans le script d'autres modules graphiques que la seule bibliothque Tkinter. Vous pouvez par exemple exprimenter la bibliothque PIL (Python Imaging Library).

8.6. Composition d'instructions pour crire un code plus compact

Du fait que Python est un langage de programmation de haut niveau, il est souvent possible (et souhaitable) de retravailler un script afin de le rendre plus compact.

Vous pouvez par exemple assez frquemment utiliser la composition d'instructions pour appliquer la mthode de mise en page des widgets (grid(), pack() ou place()) au moment mme o vous crez ces widgets. Le code correspondant devient alors un peu plus simple, et parfois plus lisible. Vous pouvez par exemple remplacer les deux lignes :

 
Sélectionnez

txt1 = Label(fen1, text ='Premier champ :')
txt1.grid(row =1, sticky =E)

du script prcdent par une seule, telle que :

 
Sélectionnez

Label(fen1, text ='Premier champ :').grid(row =1, sticky =E)

Dans cette nouvelle criture, vous pouvez constater que nous faisons l'conomie de la variable intermdiaire txt1. Nous avions utilis cette variable pour bien dgager les tapes successives de notre dmarche, mais elle n'est pas toujours indispensable. Le simple fait d'invoquer la classe Label () provoque en effet l'instanciation d'un objet de cette classe, mme si l'on ne mmorise pas la rfrence de cet objet dans une variable (Tkinter la conserve de toute faon dans sa reprsentation interne de la fentre). Si l'on procde ainsi, la rfrence est perdue pour le restant du script, mais elle peut tout de mme tre transmise une mthode de mise en page telle que grid() au moment mme de l'instanciation, en une seule instruction compose. Voyons cela un peu plus en dtail :

Jusqu' prsent, nous avons cr des objets divers (par instanciation partir d'une classe quelconque), en les affectant chaque fois des variables. Par exemple, lorsque nous avons crit :

 
Sélectionnez

txt1 = Label(fen1, text ='Premier champ :')

nous avons cr une instance de la classe Label(), que nous avons assigne la variable txt1.

La variable txt1 peut alors tre utilise pour faire rfrence cette instance, partout ailleurs dans le script, mais dans les faits nous ne l'utilisons qu'une seule fois pour lui appliquer la mthode grid (), le widget dont il est question n'tant rien d'autre qu'une simple tiquette descriptive. Or, crer ainsi une nouvelle variable pour n'y faire rfrence ensuite qu'une seule fois (et directement aprs sa cration) n'est pas une pratique trs recommandable, puisqu'elle consiste rserver inutilement un certain espace mmoire.

Lorsque ce genre de situation se prsente, il est plus judicieux d'utiliser la composition d'instructions. Par exemple, on prfrera le plus souvent remplacer les deux instructions :

 
Sélectionnez

somme = 45 + 72
print somme

par une seule instruction compose, telle que :

 
Sélectionnez

print 45 + 72

on fait ainsi l'conomie d'une variable.

De la mme manire, lorsque l'on met en place des widgets auxquels on ne souhaite plus revenir par aprs, comme c'est souvent le cas pour les widgets de la classe Label(), on peut en gnral appliquer la mthode de mise en page (grid() , pack() ou place()) directement au moment de la cration du widget, en une seule instruction compose.

Cela s'applique seulement aux widgets qui ne sont plus rfrencs aprs qu'on les ait crs. Tous les autres doivent imprativement tre assigns des variables, afin que l'on puisse encore interagir avec eux ailleurs dans le script.

Et dans ce cas, il faut obligatoirement utiliser deux instructions distinctes, l'une pour instancier le widget et l'autre pour lui appliquer ensuite la mthode de mise en page. Vous ne pouvez pas, par exemple, construire une instruction compose telle que :

 
Sélectionnez

entree = Entry(fen1).pack()              # faute de programmation !!!

En apparence, cette instruction devrait instancier un nouveau widget et l'assigner la variable entree, la mise en page s'effectuant dans la mme opration l'aide de la mthode pack(). Dans la ralit, cette instruction produit bel et bien un nouveau widget de la classe Entry(), et la mthode pack() effectue bel et bien sa mise en page dans la fentre, mais la valeur qui est mmorise dans la variable entree est la valeur de retour de la mthode pack() : ce n'est pas la rfrence du widget. Et vous ne pouvez rien faire de cette valeur de retour : il s'agit d'un objet vide (None).

Pour obtenir une vraie rfrence du widget, vous devez utiliser deux instructions :

 
Sélectionnez

entree = Entry(fen1)             # instanciation du widget
entree.pack()                    # application de la mise en page

Note : Lorsque vous utilisez la mthode grid(), vous pouvez simplifier encore un peu votre code, en omettant l'indication de nombreux numros de lignes et de colonnes. A partir du moment o c'est la la mthode grid() qui est utilise pour positionner les widgets, Tkinter considre en effet qu'il existe forcment des lignes et des colonnes37. Si un numro de ligne ou de colonne n'est pas indiqu, le widget correspondant est plac dans la premire case vide disponible.


Le script ci-dessous intgre les simplifications que nous venons d'expliquer :

 
Sélectionnez

from Tkinter import *
 
fen1 = Tk()
 
# cration de widgets Label(), Entry(), et Checkbutton() :
Label(fen1, text = 'Premier champ :').grid(sticky =E)
Label(fen1, text = 'Second :').grid(sticky =E)
Label(fen1, text = 'Troisime :').grid(sticky =E)
entr1 = Entry(fen1)
entr2 = Entry(fen1)               # ces widgets devront certainement
entr3 = Entry(fen1)               # tre rfrencs plus loin :
entr1.grid(row =0, column =1)     # il faut donc les assigner chacun
entr2.grid(row =1, column =1)     #  une variable distincte
entr3.grid(row =2, column =1)
chek1 = Checkbutton(fen1, text ='Case  cocher, pour voir')
chek1.grid(columnspan =2)
 
# cration d'un widget 'Canvas' contenant une image bitmap :
can1 = Canvas(fen1, width =160, height =160, bg ='white')
photo = PhotoImage(file ='Martin_P.gif')
can1.create_image(80,80, image =photo)
can1.grid(row =0, column =2, rowspan =4, padx =10, pady =5)
 
# dmarrage :
fen1.mainloop()

37 Surtout, n'utilisez pas plusieurs mthodes de positionnement diffrentes dans la mme fentre !
Les mthodes grid(), pack() et place() sont mutuellement exclusives.

8.7. Modification des proprits d'un objet - Animation

A ce stade de votre apprentissage, vous souhaitez certainement pouvoir faire apparatre un petit dessin quelconque dans un canevas, et puis le dplacer volont, par exemple l'aide de boutons. Veuillez donc crire, tester, puis analyser le script ci-dessous :

from Tkinter import * # procdure gnrale de dplacement : def avance(gd, hb): global x1, y1 x1, y1 = x1 +gd, y1 +hb can1.coords(oval1, x1, y1, x1+30, y1+30) # gestionnaires d'vnements : def depl_gauche(): avance(-10, 0) def depl_droite(): avance(10, 0) def depl_haut(): avance(0, -10) def depl_bas(): avance(0, 10) #------ Programme principal ------- # les variables suivantes seront utilises de manire globale : x1, y1 = 10, 10 # coordonnes initiales # Cration du widget principal ("matre") : fen1 = Tk() fen1.title("Exercice d'animation avec Tkinter") # cration des widgets "esclaves" : can1 = Canvas(fen1,bg='dark grey',height=300,width=300) oval1 = can1.create_oval(x1,y1,x1+30,y1+30,width=2,fill='red') can1.pack(side=LEFT) Button(fen1,text='Quitter',command=fen1.quit).pack(side=BOTTOM) Button(fen1,text='Gauche',command=depl_gauche).pack() Button(fen1,text='Droite',command=depl_droite).pack() Button(fen1,text='Haut',command=depl_haut).pack() Button(fen1,text='Bas',command=depl_bas).pack() # dmarrage du rceptionnaire d'vnements (boucle principale) : fen1.mainloop() Image non disponible

Le corps de ce programme reprend de nombreuses lments connus : nous y crons une fentre fen1, dans laquelle nous installons un canevas contenant lui-mme un cercle color, plus cinq boutons de contrle. Veuillez remarquer au passage que nous n'instancions pas les widgets boutons dans des variables (c'est inutile, puisque nous n'y faisons plus rfrence par aprs) : nous devons donc appliquer la mthode pack() directement au moment de la cration de ces objets.

La vraie nouveaut de ce programme rside dans la fonction avance() dfinie au dbut du script. Chaque fois qu'elle sera appele, cette fonction redfinira les coordonnes de l'objet cercle color que nous avons install dans le canevas, ce qui provoquera l'animation de cet objet.

Cette manire de procder est tout fait caractristique de la programmation oriente objet : On commence par crer des objets, et puis on agit sur ces objets en modifiant leurs proprits, par l'intermdiaire de mthodes.

En programmation procdurale l'ancienne (c'est--dire sans utilisation d'objets), on anime des figures en les effaant un endroit pour les redessiner ensuite un petit peu plus loin. En programmation oriente objet , par contre, ces tches sont prises en charge automatiquement par les classes dont les objets drivent, et il ne faut donc pas perdre son temps les reprogrammer.


Exercices :

8.12. Ecrivez un programme qui fait apparatre une fentre avec un canevas. Dans ce canevas on verra deux cercles (de tailles et de couleurs diffrentes), qui sont censs reprsenter deux astres. Des boutons doivent permettre de les dplacer volont tous les deux dans toutes les directions. Sous le canevas, le programme doit afficher en permanence : a) la distance sparant les deux astres; b) la force gravitationnelle qu'ils exercent l'un sur l'autre (Penser afficher en haut de fentre les masses choisies pour chacun d'eux, ainsi que l'chelle des distances). Dans cet exercice, vous utiliserez videmment la loi de la gravitation universelle de Newton (cfr. exercice 42, page 61, et votre cours de Physique gnrale).

8.13. En vous inspirant du programme qui dtecte les clics de souris dans un canevas, modifiez le programme ci-dessus pour y rduire le nombre de boutons : pour dplacer un astre, il suffira de le choisir avec un bouton, et ensuite de cliquer sur le canevas pour que cet astre se positionne l'endroit o l'on a cliqu.

8.14. Extension du programme ci-dessus. Faire apparatre un troisime astre, et afficher en permanence la force rsultante agissant sur chacun des trois (en effet : chacun subit en permanence l'attraction gravitationnelle exerce par les deux autres !).

8.15. Mme exercice avec des charges lectriques (loi de Coulomb). Donner cette fois une possibilit de choisir le signe des charges.

8.16. Ecrivez un petit programme qui fait apparaitre une fenetre avec deux champs : l'un indique une temperature en degres Celsius, et l'autre la meme temperature exprimee en degres Fahrenheit. Chaque fois que l'on change une quelconque des deux temperatures, l'autre est corrigee en consequence. Pour convertir les degres Fahrenheit en Celsius et vice-versa, on utilise la formule TF=TC * 1,80 + 32 (cfr. cours de Physique generale). Revoyez aussi le petit programme concernant la calculatrice simplifiee.

8.17. crivez un programme qui fasse apparatre une fentre avec un canevas. Dans ce canevas, placez un petit cercle cens reprsenter une balle. Sous le canevas, placez un bouton. Chaque fois que l'on clique sur le bouton, la balle doit avancer d'une petite distance vers la droite, jusqu' ce qu'elle atteigne l'extrmit du canevas. Si l'on continue cliquer, la balle doit alors revenir en arrire jusqu' l'autre extrmit, et ainsi de suite.

8.18. Amliorez le programme ci-dessus pour que la balle dcrive cette fois une trajectoire circulaire ou elliptique dans le canevas (lorsque l'on clique continuellement). Note : pour arriver au rsultat escompt, vous devrez ncessairement dfinir une variable qui reprsentera l'angle dcrit, et utiliser les fonctions sinus et cosinus pour positionner la balle en fonction de cet angle.

8.19. Modifiez le programme ci-dessus, de telle manire que la balle en se dplaant laisse derrire elle une trace de la trajectoire dcrite.

8.20. Modifiez le programme ci-dessus de manire tracer d'autres figures. Consultez votre professeur pour des suggestions (courbes de Lissajous).

8.21. Ecrivez un programme qui fasse apparatre une fentre avec un canevas et un bouton. Dans le canevas, tracez un rectangle gris fonc, lequel reprsentera une route, et par-dessus, une srie de rectangles jaunes censs reprsenter un passage pour pitons. Ajoutez quatre cercles colors pour figurer les feux de circulation concernant les pitons et les vhicules. Chaque utilisation du bouton devra provoquer le changement de couleur des feux :

Image non disponible

8.22. crivez un programme qui montre un canevas dans lequel est dessin un circuit lectrique simple (gnrateur + interrupteur + rsistance). La fentre doit tre pourvue de champs d'entre qui permettront de paramtrer chaque lment (c'est--dire choisir les valeurs des rsistances et tensions). L'interrupteur doit tre fonctionnel (Prvoyez un bouton Marche/arrt pour cela). Des tiquettes doivent afficher en permanence les tensions et intensits rsultant des choix oprs par l'utilisateur.

8.8. Animation automatique - Rcursivit

Pour conclure cette premire prise de contact avec l'interface graphique Tkinter, voici un dernier exemple d'animation, qui fonctionne cette fois de manire autonome ds qu'on l'a mise en marche.

 
Sélectionnez

from Tkinter import *
 
# dfinition des gestionnaires
# d'vnements :
 
def move():
    "dplacement de la balle"
    global x1, y1, dx, dy, flag
    x1, y1 = x1 +dx, y1 + dy
    if x1 >210:
        x1, dx, dy = 210, 0, 15
    if y1 >210:
        y1, dx, dy = 210, -15, 0
    if x1 <10:
        x1, dx, dy = 10, 0, -15
    if y1 <10:
        y1, dx, dy = 10, 15, 0
    can1.coords(oval1,x1,y1,x1+30,y1+30)
    if flag >0:
        fen1.after(50,move)         # => boucler aprs 50 millisecondes
 
 
def stop_it():
    "arret de l'animation"
    global flag
    flag =0
 
def start_it():
    "dmarrage de l'animation"
    global flag
    if flag ==0:         # pour ne lancer qu'une seule boucle
    flag =1
    move()
 
#========== Programme principal =============
 
# les variables suivantes seront utilises de manire globale :
x1, y1 = 10, 10         # coordonnes initiales
dx, dy = 15, 0          # 'pas' du dplacement
flag =0                 # commutateur
 
# Cration du widget principal ("parent") :
fen1 = Tk()
fen1.title("Exercice d'animation avec Tkinter")
# cration des widgets "enfants" :
can1 = Canvas(fen1,bg='dark grey',height=250, width=250)
can1.pack(side=LEFT, padx =5, pady =5)
oval1 = can1.create_oval(x1, y1, x1+30, y1+30, width=2, fill='red')
bou1 = Button(fen1,text='Quitter', width =8, command=fen1.quit)
bou1.pack(side=BOTTOM)
bou2 = Button(fen1, text='Dmarrer', width =8, command=start_it)
bou2.pack()
bou3 = Button(fen1, text='Arrter', width =8, command=stop_it)
bou3.pack()
# dmarrage du rceptionnaire d'vnements (boucle principale) :
fen1.mainloop()
Image non disponible

La seule nouveaut mise en oeuvre dans ce script se trouve tout la fin de la dfinition de la fonction move() : vous y noterez l'utilisation de la mthode after(). Cette mthode peut s'appliquer un widget quelconque. Elle dclenche l'appel d'une fonction aprs qu'un certain laps de temps se soit coul. Ainsi par exemple, window.after(200,qqc) dclenche pour le widget window un appel de la fonction qqc() aprs une pause de 200 millisecondes.

Dans notre script, la fonction qui est appele par la mthode after() est la fonction move() ellemme. Nous utilisons donc ici pour la premire fois une technique de programmation trs puissante, que l'on appelle rcursivit. Pour faire simple, nous dirons que la rcursivit est ce qui se passe lorsqu'une fonction s'appelle elle-mme. On obtient bien videmment ainsi un bouclage, qui peut se perptuer indfiniment si l'on ne prvoit pas aussi un moyen pour l'interrompre.

Voyons comment cela fonctionne dans notre exemple :

La fonction move() est invoque une premire fois lorsque l'on clique sur le bouton Dmarrer . Elle effectue son travail (c'est--dire positionner la balle), puis elle s'invoque ellemme aprs une petite pause. Elle repart donc pour un second tour, puis s'invoque elle-mme nouveau, et ainsi de suite indfiniment...

C'est du moins ce qui se passerait si nous n'avions pas pris la prcaution de placer quelque part dans la boucle une instruction de sortie. En l'occurrence, il s'agit d'un simple test conditionnel : chaque itration de la boucle, nous examinons le contenu de la variable flag l'aide d'une instruction if. Si le contenu de la variable flag est zro, alors le bouclage ne s'effectue plus et l'animation s'arrte. flag tant une variable globale, nous pouvons aisment changer sa valeur l'aide d'autres fonctions, celles que nous avons associes aux boutons Dmarrer et Arrter .

Nous obtenons ainsi un mcanisme simple pour lancer ou arrter notre animation :

Un premier clic sur le bouton Dmarrer assigne une valeur non-nulle la variable flag, puis provoque immdiatement un premier appel de la fonction move(). Celle-ci s'excute et continue ensuite s'appeler elle-mme toutes les 50 millisecondes, tant que flag ne revient pas zro. Si l'on continue cliquer sur le bouton Dmarrer , la fonction move() ne peut plus tre appele tant que la valeur de flag vaut 1. On vite ainsi le dmarrage de plusieurs boucles concurrentes.

Le bouton Arrter remet flag zro, et la boucle s'interrompt.


Exercices :

8.23. Dans la fonction start_it(), supprimez l'instruction if flag == 0: (et l'indentation des deux lignes suivantes). Que se passe-t-il ? (Cliquez plusieurs fois sur le bouton Dmarrer ).
Tchez d'exprimer le plus clairement possible votre explication des faits observs.

8.24. Modifiez le programme de telle faon que la balle change de couleur chaque virage .

8.25. Modifiez le programme de telle faon que la balle effectue des mouvements obliques comme une bille de billard qui rebondit sur les bandes ( en zig-zag ).

8.26. Modifiez le programme de manire obtenir d'autres mouvements. Tchez par exemple d'obtenir un mouvement circulaire. (Comme dans les exercices de la page 104).

8.27. Modifiez ce programme, ou bien crivez-en un autre similaire, de manire simuler le mouvement d'une balle qui tombe (sous l'effet de la pesanteur), et rebondit sur le sol. Attention : il s'agit cette fois de mouvements acclrs !

8.28. A partir des scripts prcdents, vous pouvez prsent crire un programme de jeu fonctionnant de la manire suivante :
Une balle se dplace au hasard sur un canevas, vitesse faible. Le joueur doit essayer de cliquer sur cette balle l'aide de la souris. S'il y arrive, il gagne un point mais la balle se dplace dsormais un peu plus vite, et ainsi de suite. Arrter le jeu aprs un certain nombre de clics et afficher le score atteint.

8.29. Variante du jeu prcdent : chaque fois que le joueur parvient l'attraper , la balle devient plus petite (elle peut galement changer de couleur).

8.30. crivez un programme dans lequel voluent plusieurs balles de couleurs diffrentes, qui rebondissent les unes sur les autres ainsi que sur les parois.

8.31. Perfectionnez le jeu des prcdents exercices en y intgrant l'algorithme ci-dessus. Il s'agit prsent pour le joueur de cliquer seulement sur la balle rouge. Un clic erron (sur une balle d'une autre couleur) lui fait perdre des points.

8.32. crivez un programme qui simule le mouvement de 2 plantes tournant autour du soleil sur des orbites circulaires diffrentes (ou deux lectrons tournant autour d'un noyau d'atome...).

8.33. crivez un programme pour le jeu du serpent : un serpent (constitu en faite d'une courte ligne de carrs) se dplace sur le canevas dans l'une des 4 directions : droite, gauche, haut, bas. Le joueur peut tout moment changer la direction suivie par le serpent l'aide des touches flches du clavier. Sur le canevas se trouvent galement des proies (des petits cercles fixes disposs au hasard). Il faut diriger le serpent de manire ce qu'il mange les proies sans arriver en contact avec les bords du canevas. A chaque fois qu'une proie est mange, le serpent s'allonge d'un carr, le joueur gagne un point, et une nouvelle proie apparat ailleurs. La partie s'arrte lorsque le serpent touche l'une des parois, ou lorsqu'il a atteint une certaine taille.

8.34. Perfectionnement du jeu prcdent : la partie s'arrte galement si le serpent se recoupe . Grard Swinnen


prcdentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Licence Creative Commons
Le contenu de cet article est rédigé par Gérard Swinnen et est mis à disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2013 Developpez.com.