• Aucun résultat trouvé

Penser en Tkinter

N/A
N/A
Protected

Academic year: 2022

Partager "Penser en Tkinter"

Copied!
44
0
0

Texte intégral

(1)

Penser en Tkinter

par Stephen Ferg

date de dernière modification : 2005-07-17

Ce fichier contient l'intégralité du code source pour tous les exemples de la série Thinking in Tkinter.

Si vous imprimez ce fichier avec une police de petite taille, vous pourrez l'imprimer en mode portrait sans tronquer les fins de ligne. Si vous l'imprimez avec une petite police, ET que vous tronquez les lignes de code, essayez avec une police plus petite,

ou imprimez en landscape.

La partie imprimée occupe de 40 à 60 pages, en fonction de vos réglages d'impression. Vous pouvez voir à quoi ressemblera le résultat et le nombre de pages

requis avec Print Preview avant d'imprimer le fichier.

tt000.py

Subject: "Penser en Tkinter"

Auteur : Stephen Ferg

A propos de " Penser en Tkinter "

J'ai essayé d'enseigner Tkinter à partir de plusieurs livres, et j'ai trouvé que c'était plus difficile que prévu.

Le problème est que les auteurs se précipitent pour lister tous les widgets de la boîte à outils de Tkinter, mais n'expliquent pas les concepts de base. Ils n'expliquent pas comment "Penser en Tkinter".

Les courts programmes suivants expliquent comment penser en Tkinter. Dans ces programmes, je n'essaie pas de passer en revue tous les types de widgets, attributs, et méthodes qui sont disponibles dans Tkinter. Et je n'essaie pas de fournir une introduction exhaustive à Tkinter. J'essaie juste de vous mettre sur la voie afin de comprendre quelques concepts de base de Tkinter.

Notez que cette discussion est consacrée uniquement à Tkinter pack (ou "packer") geometry manager. Je ne parle pas de grid ou de geometry managers.

Les Quatre Tâches de base en programmation d'interfaces GUI

Quand vous développez un interface utilisateur (UI), il y a une série de tâches à accomplir.

1) Vous devez indiquer l'apparence *voulue* pour cet interface utilisateur. Le code que vous allez écrire va déterminer ce que l'utilisateur verra sur son écran.

(2)

2) Vous devez décider ce que *fera* l'interface utilisateur (UI). Pour cela, il faudra écrire le code qui accomplira les tâches de ce programme.

3) Vous devez associer "l'apparence" avec "l'action". Pour cela, vous devez écrire le code qui associe ce que l'utilisateur voit avec les routines qui exécuteront les tâches du programme.

4) Enfin, vous devez écrire le code qui attend les entrées de l'utilisateur.

(3)

Un peu de jargon de programmation avec un Gui

La programmation avec un GUI (graphical user interface, ou interface utilisateur graphique) a un jargon spécifique associé avec ces tâches.

1) Nous indiquons l'apparence que nous voulons donner au GUI en décrivant les

"widgets" que nous voulons afficher, et leurs relations dans l'espace (c'est-à-dire, si un widget est au-dessus ou en dessous d'un autre, ou à droite ou à gauche d'autres widgets). Le mot "widget" est un non-sens qui est devenu un terme commun pour

"élément de l'interface graphique". Les Widgets incluent des choses comme des fenêtres, des boutons, des menus, des éléments de menus, des icones, des listes déroulantes, des barres de défilement et ainsi de suite.

2) Les routines qui font le travail du GUI sont appelées les "callback handlers" ou

"event handlers". "Events" sont les évènements comme les clicks sur la souris ou le fait d'appuyer sur un touche du clavier. Ces routines sont appelées des "handlers"

parce que elles "gèrent (handle)" (c'est-à-dire, répondent à) de tels évènements.

3) On appelle "binding" le fait d'associer un event handler avec un widget. En gros, le binding implique trois choses différentes :

(a) un type d'évènement (par exemple un click gauche sur la souris, ou appuyer sur la touche ENTER du clavier),

(b) un widget (c'est-à-dire un bouton), et (c) une routine de gestion d'évènement.

Par exemple, nous pouvons associer (a) un (seul) click gauche de la souris (b) au bouton/widget "CLOSE" sur l'écran (c) à la routine "closeProgram", qui ferme la fenêtre et arrête le programme.

4) Le code qui tourne et attend une saisie est appelé l'"event loop".

A propos de l'Event Loop

Si vous en croyez les films, il y a dans chaque village une vieille dame qui passe sa journée devant sa fenêtre, à REGARDER. Elle voit tout ce qui se passe dans le voisinage. La plupart de ce qu'elle voit est inintéressant, des personnes qui vont et viennent. Mais certaines choses sont intéressantes, comme une dispute d'un couple de jeunes mariés dans la rue. Quand il se passe des choses intéressantes, cette femme prévient immédiatement la police ou ses voisins.

L'event loop ressemble beaucoup à cette vieille dame. L'event loop passe tout son temps à regarder ce qui se passe, et il voit tout. La plupart des évènements ne sont pas intéressants et quand il les voit, il ne fait rien. mais quand il voit quelque chose d'intéressant, un évènement qu'il sait intéressant, parce qu'un event handler à été rattaché à l'évènement alors il contacte immédiatement l'event handler et l'informe de ce qui s'est passé.

Comportement Du Programme

Ce programme vous simplifie la programmation d’une interface utilisateur en vous montrant comment trois concepts de base sont implémentés dans un programme très simple. Ce programme n'utilise pas Tkinter ou une forme de programmation de GUI. Il

(4)

affiche juste un menu sur la console, et fait une saisie au clavier. Même de cette manière, il exécute les quatre tâches de base de la programmation de user-interface.

[date de dernière mise à jour: 23-02-2003]

(5)

Program Source Code

#--- tâche 2: définir les routines de l'event handler--- def handle_A():

print "Faux ! Essayez encore !"

def handle_B():

print "Tout à fait exact ! Trillium est une variété de fleur !"

def handle_C():

print "Faux ! Essayez encore !"

# --- tâche 1: définir l'apparence de l'écran --- print "\n"*100 # nettoyer l'écran

print " Jeu de devinette très difficile"

print "========================================================"

print "Tapez la lettre de votre réponse, puis appuyez sur la touche ENTREE."

print

print " A. Animal"

print " B. Légume"

print " C. Minéral"

print

print " X. Quitter ce programme"

print

print "========================================================"

print "Dans quelle catégorie est 'Trillium'?"

print

# ---- tâche 4: l'event loop. Nous bouclons en permanence, en scrutant les évènements.

--- while 1:

# Nous attendons le prochain évènement.

answer = raw_input().upper()

# ---

# Tâche 3: Associer des évènements claviers intéressants avec leurs # event handlers. Un forme simple d'association.

# --- if answer == "A": handle_A()

if answer == "B": handle_B() if answer == "C": handle_C() if answer == "X":

# nettoyer l'écran et sortir de l'event loop print "\n"*100

break

# Notez que les autres évènements ne sont pas intéressants et sont donc ignorés.

(6)

tt010.py

Le Plus Court programme Tkinter -- Trois Instructions !

Concernant les quatre tâches de base d'un GUI dont nous avons parlé dans le programme précédent, ce programme en exécute juste une, l'event loop.

(1) La première instruction importe Tkinter, pour qu'il soit disponible. Notez que la forme de cet import ("from Tkinter import *") implique que nous n'aurons pas à préciser quoi que ce soit venant de Tkinter avec un préfixe "Tkinter.".

(2) La deuxième instruction crée une fenêtre "toplevel" . Techniquement, la deuxième instruction crée une instance de la classe "Tkinter.Tk".

La fenêtre d'avant-plan est le composant de plus haut niveau du GUI dans n'importe quelle applciation Tkinter. Par convention, on appelle cette fenêtre "root".

(3) La troisième instruction exécute la "boucle principale (mainloop)" (c'est-à-dire, l'event loop), qui est une méthode de l'objet "root" . Tant que l'on est dans la boucle principale, on attend un évènement qui se passe dans (la fenêtre) root. Quand un évènement arrive, il est traité et la boucle continue, en attente de l'évènement

suivant. La boucle continue à s'exécuter jusqu'à ce qu'un évènement "destroy" arrive dans le fenêtre d'avant-plan. Un évènement "destroy" ferme une fenêtre. Quand la fenêtre d'avant-plan est détruite, la fenêtre est fermée et on sort de l'event loop.

Comportement Du Programme

Pendant l'exécution de ce programme, vous verrez que (grâce à Tk) la fenêtre

d'avant-plan a des widgets pour réduire ou augmenter la taille de la fenêtre, et fermer la fenêtre. Essayez-les, vous constaterez qu'ils fonctionnent vraiment.

Clicker sur le widget "close" (dans la boîte "x" à la droite de la barre de titre)

déclenche un évènement "destroy" . L'évènement destroy termine la boucle principale d'évènement. Et comme il n'y a pas d'instructions après "root.mainloop()", le

programme n'a plus rien à faire et donc se termine.

[date de dernière mise à jour: 2003-02-23]

Program Source Code

from Tkinter import * ### (1) root = Tk() ### (2) root.mainloop() ### (3)

(7)

tt020.py

Maintenant nous allons nous attaquer à une de nos quatre tâches du GUI -- indiquer à quoi le GUI doit ressembler.

Avec ce programme, nous allons introduire trois concepts importants de la programmation Tkinter :

* créer un objet GUI et l'associer avec ses parents

* le packing

* les containers par opposition aux widgets

A partir de maintenant, je vais faire la distinction entre un "container component" et un widget. Quand j'utiliserai ces termes, un "widget" sera un composant du GUI qui est (en général) visible. Un "container" est simplement un container -- un panier -- dans lequel nous plaçons des widgets.

Tkinter propose de nombreux containers. "Canvas" est un container pour des applications de dessin. Le container le plus utilisé est le " Frame"(cadre).

Les Frames sont fournis par Tkinter dans une classe appelée "Frame". Une expression comme :

Frame(myParent)

crée une instance de la classe Frame (c'est-à-dire, crée un cadre), et associe l'instance Frame avec son parent, myParent. Une autre façon de voir : une telle expression

ajoute un frame enfant au composant myParent.

Donc dans ce programme, l'instruction (1):

myContainer1 = Frame(myParent)

crée un cadre (frame) dont le parent est myParent (c'est-à-dire, root), et lui donne le nom "myContainer1". En résumé, il crée un container dans lequel nous pouvons placer des widgets. (Nous ne plaçons pas de widget dans ce programme. Nous le ferons dans d'autres programmes.)

Notez que la relation parent/enfant ici est une relation LOGIQUE, pas une relation visuelle. Cette relation existe pour supporter des choses comme l'évènement destroy -- afin que quand un composant parent (comme root) est détruit, le parent sait quels sont ses enfants, et peut les détruire avant de se détruire lui-même.

(2) L'instruction suivante "packe (remplit)" myContainer1.

myContainer1.pack()

décrit simplement, "packing" consiste à mettre en place une relation VISUELLE entre un composant du GUI et son parent. Il faut faire un pack (insertion) d'un composant, sinon vous ne le verrez jamais.

(8)

"Pack" appelle le geometry manager "pack" de Tkinter . Un geometry manager est surtout une API -- une façon de communiquer avec Tkinter -- pour indiquer à Tkinter comment les containers et widgets doivent être visuellement présentés. Tkinter supporte trois geometry managers: pack, grid, et place. Pack et (dans une moindre mesure) grid sont les plus utilisés, parce qu'ils sont les plus faciles à utiliser. Tous les exemples dans "Penser en Tkinter" utilisent le pack geometry manager.

(9)

Nous avons donc ici un motif de base pour la programmation Tkinter que nous verrons à nouveau de nombreuses fois.

(1) une instance (d'un widget ou d'un container) est créée, et associée avec son parent

(2) l'instance est packée (insérée).

Comportement Du Programme

Quand vous exécutez ce programme, il ressemblera beaucoup au précédent, avec la différence qu'il affiche moins de choses. C'est parce que ...

Les Frames (cadres) Sont Elastiques

Une frame (un cadre) est fondamentalement un container. L'intérieur d'un container -- le "space" à l'intérieur du container -- est appelé la "cavity". ("Cavity(creux)" est un terme que Tkinter a pris à Tk.)

La cavity (creux) "est stretchy" ou élastique, comme un élastique. A moins d'indiquer un taille minimum ou maximum pour le frame (cadre), la cavity va s'étirer ou se réduire pour s'adapter dans le frame (cadre).

Dans le programme précédent, le root s'est affiché avec la taille par défaut, car nous n'avions rien précisé.

Mais dans ce programme, nous *avons* placé quelque chose dans la cavity de root -- nous y avons placé Container1. Donc le cadre root se réduit pour s'adapter à la taille de Container1. Mais comme nous n'avons pas mis de widgets dans Container1, et pas indiqué une taille minimum pour Container1, la cavity de root se réduit à rien. Voilà pourquoi il n'y a rien à voir en dessous de la title bar.

Dans les programmes suivants, nous placerons des widgets et autres containers dans Container1, et vous verrez comment Container1 s'étire pour s'adapter.

[date de dernière mise à jour: 2003-02-24]

Program Source Code

from Tkinter import * root = Tk()

myContainer1 = Frame(root) ### (1) myContainer1.pack() ### (2) root.mainloop()

(10)

tt030.py

Dans ce programme, nous créons notre premier widget, et nous le plaçons dans myContainer1.

(1) Le widget sera un bouton -- c'est-à-dire, il sera une instance de la classe "Button"

de Tkinter. L'instruction :

button1 = Button(myContainer1)

crée le bouton, lui donne le nom "button1", et l'associe avec son parent, l'objet container appelé myContainer1.

(2)(3) Les widgets possèdent de nombreux attributs, qui sont rangés dans le local namespace dictionary. Les Button widgets ont des attributs pour contrôler leur taille, leur couleurs d'arrière-plan et d'avant-plan, le texte qu'ils affichent, l'apparence de leur bord, et ainsi de suite. Dans cet exemple, nous allons juste positionner deux des attributs de button1 : la couleur d'arrière-plan et le texte. Nous faisons cela en

donnant les valeurs dans le dictionnaire du bouton avec les clés "text" et

"background".

button1["text"]= "Bonjour, Monde !"

button1["background"] = "green"

(4) Et bien sur, nous insérons (ce qui se fait avec le verbe pack) button1.

button1.pack()

Quelques Termes Techniques

Quelques fois la relation entre un container et le widget qu'il contient est décrite comme une relation "parent/enfant" , d'autres fois comme une relation

"maitre/esclave".

Comportement Du Programme

Quand vous lancez ce programme, vous verrez que Container1 contient maintenant un bouton vert avec le texte "Bonjour, Monde !". Quand vous clickez dessus, il ne se passe rien, car nous n 'avons pas encore indiqué ce qui doit se passer quand ce bouton est clické. (Nous ferons cela plus tard.)

Pour l'instant, il faut fermer la fenêtre, comme précédemmment, en clickant sur l'icône CLOSE de la barre de titre.

Notez comme myContainer1 s'est étiré pour accueillir button1.

[date de dernière mise à jour: 2002-10-01]

Program Source Code

(11)

from Tkinter import * root = Tk()

myContainer1 = Frame(root) myContainer1.pack()

button1 = Button(myContainer1) ### (1) button1["text"]= "Bonjour, Monde !" ### (2) button1["background"] = "green" ### (3) button1.pack() ### (4) root.mainloop()

tt035.py

Utilisation D'une Structure De Classe

Dans ce programme, nous introduisons un concept, structurer une application Tkinter en tant qu'un ensemble de classes.

Dans ce programme, nous avons ajouté une classe appelée MyApp et déplacé une partie du code du programme précédent dans la méthode constructor (__init__). Dans cette version modifiée, nous faisons trois choses différentes :

(1) Dans notre code, nous déclarons une classe (MyApp) qui indique à quoi notre GUI doit ressembler. Il définit le look de notre GUI et le genre de choses que nous voulons faire avec. Tout ce code est placé dans le contructeur de la méthode (__init__) de la classe. (1a)

(2) Quand le programme s'exécute, il commence par créer une instance de la classe.

L'instruction qui crée l'instance est

myapp = MyApp(root)

Notez que le nom de la classe est "MyApp" (notez la majuscule) et le nom de l'instance est "myapp" (notez l'absence de majuscule).

Notez aussi que cette instruction passe "root" en tant que paramètre dans le

constructor method (__init__) de MyApp. Le constructor method reconnait le root sous le nom "myParent". (1a)

(3) Enfin, nous exécutons mainloop sur le root.

Pouquoi Structurer Votre Application En Tant Que Classe ?

Une des raisons pour utiliser une structure de classe dans votre programme est

simplement de mieux contrôler le programme. Un programme structuré en classes est sans doute -- spécialement s'il s'agit dans très gros programme -- bien plus facile à comprendre qu'un programme qui n'est pas structuré.

Un point plus important est que structurer votre application en tant que classe vous aide à éviter d'utiliser des variables globales. A la fin, au fur et à mesure que votre

(12)

programme grossit, vous voudrez probablement que vos event handlers soient capables de partager des informations entre eux. Une manière de faire est d'utiliser les variables globales, mais c'est une manière confuse. Une bien meilleure manière est d'utiliser les instances (c'est-à-dire, les variables "self." ), et pour cela, vous devez donner à votre application une structure de classe. Nous approfondirons ce sujet dans un programme ultérieur.

Quand Introduire Le Class Structuring

Nous avons présenté récemment la notion de structure de classe pour des

programmes Tkinter, dans le but de l'expliquer et de passer à d'autres sujets. Mais au cours de vos développements, vous pouvez choisir d'agir différemment.

Souvent, un programme Tkinter commence comme un simple script. Tout le code est en ligne, comme pour notre programme précédent. Puis, le programme grandit. Au bout d'un certain temps, vous avez BEAUCOUP de code. Vous avez commencé à utiliser des variables globales... peut-être BEAUCOUP de variables globales. Le

programme commence à être difficile à comprendre et à modifier. Quand cela arrive, il est temps de refactoriser votre programme, et le re-structurer en utilisant des classes.

D'un autre côté, si vous êtes à l'aise avec les classes, et que vous avez une idée précise de ce à quoi ressemblera votre programme, vous pouvez choisir de structurer votre programme en utilisant des classes dès le tout début.

Mais d'un autre côté, au début du développement (comme Gerrit Muller l'a noté), vous ne savez pas encore quelle sera la meilleure structure de classe à utiliser -- au début du développement, vous n'avez pas encore une compréhension suffisante de ce qu'il y a à faire et comment le faire. Commencer à utiliser des classes trop tôt peut ajouter une structure inutile qui encombre le code, gêne la compréhension, et éventuellement nécessite plus de refactoring.

Donc cela dépend de vos préférences, de votre expérience et des circonstances.

Faites ce qui vous semble adapté. Et -- quoi que vous choisissiez de faire -- n'ayez pas peur de faire une refactorisation importante quand cela est nécessaire.

Comportement Du Programme

Quand vous lancez ce programme, il semblera complètement identique au précédent.

Les fonctionnalités n'ont pas changé, la seule différence, c'est la manière dont le code est structuré.

[date de dernière mise à jour: 2003-02-23]

Program Source Code

from Tkinter import *

class MyApp: ### (1) def __init__(self, myParent): ### (1a) self.myContainer1 = Frame(myParent) self.myContainer1.pack()

self.button1 = Button(self.myContainer1) self.button1["text"]= "Bonjour, Monde !"

self.button1["background"] = "green"

(13)

self.button1.pack() root = Tk()

myapp = MyApp(root) ### (2) root.mainloop() ### (3)

tt040.py

(1) Dans le programme précédent, nous avons créé un objet de type bouton, button1, puis nous avons positionné son texte et sa couleur d'arrière-plan de manière simple.

self.button1["text"]= "Bonjour, Monde !"

self.button1["background"] = "green"

Dans ce programme, nous ajoutons trois boutons à Container1, en utilisant des méthodes légèrement différentes.

(2) Pour button2, nous faisons pratiquement la même chose que pour button1, mais au lieu d'accéder au dictionnaire du bouton, nous utilisons la méthode intégrée

"configure" du bouton.

(3) Pour button3, nous voyons que la méthode configure peut prendre plusieurs mots- clés en paramètres, donc nous pouvons positionner plusieurs options avec une seule instruction.

(4) Dans les exemples précédents, positionner un bouton se faisait en deux étapes : d'abord nous créons le bouton, puis nous positionnons ses propriétés. Mais il est possible d'indiquer les propriétés du bouton à sa création. Le widget "Button" (comme tous les widgets) attend que son premier paramètre soit son parent. Ceci est un paramètre positionnel, pas un paramètre mot-clé. Mais après cela, vous pouvez, si vous le souhaitez, ajouter un ou plusieurs mots-clé en paramètres, qui vont

positionner des propriétés du widget.

Comportement Du Programme

Quand vous lancez ce programme, vous verrez que Container1 contient maintenant, en plus du bouton vert qu'il avait déjà, trois autres boutons.

Notez comment myContainer1 s'est étiré pour recevoir ces autres boutons.

Notez aussi que les boutons sont empilés les uns sur les autres. Dans le programme suivant, nous verrons pourquoi ils se placent de cette manière, et comment les placer différemment.

Program Source Code

from Tkinter import * class MyApp:

def __init__(self, parent):

self.myContainer1 = Frame(parent) self.myContainer1.pack()

(14)

self.button1 = Button(self.myContainer1)

self.button1["text"] = "Bonjour, Monde !" ### (1) self.button1["background"] = "green" ### (1) self.button1.pack()

self.button2 = Button(self.myContainer1)

self.button2.configure(text="Off to join the circus!") ### (2) self.button2.configure(background="tan") ### (2) self.button2.pack()

self.button3 = Button(self.myContainer1)

self.button3.configure(text="Tu me rejoins ?", background="cyan") ### (3) self.button3.pack()

self.button4 = Button(self.myContainer1, text="Au revoir !", background="red")

### (4)

self.button4.pack()

root = Tk()

myapp = MyApp(root) root.mainloop()

tt050.py

Dans le dernier programme, nous avons vu deux boutons, empilés l'un sur l'autre.

Nous avons sans doute envie de les voir côte à côte. Dans ce programme, nous

faisons cela, et nous nous commençons à voir ce que nous pouvons faire avec pack().

(1) (2) (3) (4)

Le Packing est une façon de contrôler la relation VISUELLE entre les composants. Nous allons maintenant utiliser l'option quot;side" de pack pour aligner les boutons côte à côte plutot que empilés les uns sur les autres. Nous faisons cela avec le paramètre

"side" de l'instruction pack(), par exemple:

self.button1.pack(side=LEFT)

Notez que LEFT (comme RIGHT, TOP, et BOTTOM) sont des constantes user-friendly définies dans Tkinter. "LEFT" est en fait "Tkinter.LEFT" -- mais, en raison de la manière dont nous avons importé Tkinter, nous n'avons pas besoin de fournir le préfixe

"Tkinter.".

Pourquoi les boutons sont empilés verticalement dans le dernier programme

Vous vous souvenez que dans le dernier programme, nous avons juste "packed"

(inséré, placé) les boutons sans indiquer la moindre option"side", et les boutons ont été empilés les uns sur les autres. C'est parce que l'option par défaut de "side" est

"side=TOP".

Donc quand nous remplissons button1, il est placé en haut de la cavity à l'intérieur de myContainer1. Cela laisse la cavity pour myContainer1 positionnée en dessous de

(15)

button1. Ensuite nous plaçons button2. Il est placé au sommet de la cavity, donc juste en dessous de button1. Et la cavity est maintenant positionnée en dessous de

button2.

Si nous avions placé les boutons dans un ordre différent -- par exemple, si nous avions placé d'abord button2, puis ensuite button1 -- leurs positions auraient été inversées et button2 aurait été en haut.

Donc, comme vous pouvez le voir, une des manières de contrôler l'apparence de votre GUI est de contrôler l'ordre dans lequel vous placez les widgets à l'intérieur des

containers.

Quelques Termes Techniques -- "orientation"

"Vertical" orientation inclut les côtés TOP et BOTTOM. "Horizonal" orientation inclut les côtés LEFT et RIGHT.

Quand vous remplissez les widgets et les containers, il est possible de mélanger les deux orientations. Par exemple, nous pouvons remplir un bouton avec une orientation verticale (disons, TOP) et l'autre bouton avec une orientation horizontale (disons, LEFT).

Mais mélanger les orientations à l'intérieur d'un container ce cette façon n'est pas une bonne idée. Si vous mélangez les orientations, alors il sera délicat de prédire le

résultat final, et vous serez sans doute surpris par l'apparence du GUI si la fenêtre est re-taillée.

Donc une bonne pratique de design est de ne jamais mélanger les orientations à l'intérieur d'un container. La manière de gérer des GUI compliqués, dans lesquels vous voulez vraiment utiliser des orientations différentes, est d'emboîter des containers à l'intérieur de containers. Nous explorerons ce sujet dans un programme plus tard.

Comportement Du Programme

Quand vous lancez ce programme, vous verrez les deux boutons, côte à côte.

[date de dernière mise à jour : 2002-10-01]

Program Source Code

from Tkinter import * class MyApp:

def __init__(self, parent):

self.myContainer1 = Frame(parent) self.myContainer1.pack()

self.button1 = Button(self.myContainer1) self.button1["text"]= "Bonjour, Monde !"

self.button1["background"] = "green"

self.button1.pack(side=LEFT) ### (1)

self.button2 = Button(self.myContainer1)

self.button2.configure(text="Off to join the circus!") self.button2.configure(background="tan")

self.button2.pack(side=LEFT) ### (2)

(16)

self.button3 = Button(self.myContainer1)

self.button3.configure(text="Tu me rejoins ?", background="cyan") self.button3.pack(side=LEFT) ### (3)

self.button4 = Button(self.myContainer1, text="Au revoir !", background="red") self.button4.pack(side=LEFT) ### (4)

root = Tk()

myapp = MyApp(root) root.mainloop()

tt060.py

Maintenant nos boutons vont faire des choses. Nous attirons votre attention sur les deux dernières tâches basiques GUI tasks -- écrire des routines du gestionnaire d'évènement pour faire le travail actuel de notre programme, et lier les routines du gestionnaire d'évènement aux widgets et évènements.

Notez que dans ce programme nous avons abandonné tous les boutons créés dans notre dernier programme, et nous somme revenus à une situation plus simple, dans laquelle notre GUI contient juste deux boutons: "OK" et "Cancel".

Comme vous vous souvenez de la discussion dans notre premier programme, une des tâches de base du GUI est le "binding". "Binding" est le fait de définir une connexion ou une relation entre (en général) :

* un widget,

* un type d'évènement, et

* un "gestionnaire d'évènement".

Un "gestionnaire d'évènement" est une méthode ou une subroutine qui gère les

évènements quand ils arrivent. [En Java, les gestionnaires d'évènements sont appelés

"listeners". J'aime ce nom, parce qu'il suggère exactement ce qu'ils font -- "écouter"

les évènements, et leur répondre.]

En Tkinter, vous créez ce binding avec la méthode bind() qui est une fonctionnalité de tous les widgets Tkinter. Vous utilisez la méthode bind() avec une instruction de la forme :

widget.bind(event_type_name, event_handler_name)

Ce type de binding est appelé "event binding".

[Il y a une autre manière de faire, pour le binding d'un event_handler avec un widget.

On appelle cela "command binding" et nous verrons cela deux programmes plus loin.

Pour l'instant, étudions l'event binding. Quand nous aurons compris l'event binding, il sera facile d'expliquer command binding.]

Avant de commencer, nous devons clarifier un point possible de confusion. Le mot

"button" peut être utilisé pour deux choses complètement différentes: (1) un widget button -- un composant du GUI qui est affiché sur l'écran de l'ordinateur et (2) un

(17)

bouton de votre souris -- le genre de bouton que vous pressez avec votre doigt. Pour éviter tout confusion, je les distinguerai en disant "bouton widget" ou "bouton de la souris" plutôt que "bouton".

(1) Nous lions les évènements "<Button-1>" (click gauche de la souris) au widget button1 de la méthode "self.button1Click". Quand button1 subit un click gauche de la souris, la méthode self.button1Click() sera appelée pour gérer l'évènement.

(3) Notez que, bien que cela ne soit pas indiqué lors du "bind", deux paramètres sont passés à self.button1Click(). Le premier, évidemment, sera "self", qui est toujours le premier paramètre de toute méthode de classe en Python. le deuxième sera une objet event. Cette technique de binding et event (en utilisant la méthode bind() ) passe toujours implicitement un objet évènement en paramètre.

En Python/Tkinter, quand un évènement arrive, il prend la forme d'un objet

évènement. Un objet évènement est très utile, parce qu'il apporte avec lui beaucoup d'informations utiles et de méthodes. Vous pouvez examiner l'objet évènement pour savoir quel évènement a eu lieu, le widget concerné, et d'autres informations utiles.

(4) Donc, quand on clicke sur button1, que voulons-nous qu'il se passe ? Dans ce cas, nous voulons faire quelque chose de simple. Passer la couleur de vert à jaune, et revenir à vert.

(2) Faisons faire à button2 (le bouton "Au Revoir !" ) des choses utiles. Il fermera la fenêtre. Donc nous lions un click gauche de la souris sur button2 à la méthode button2Click(), et

(6) La méthode button2Click() détruira la fenêtre root, la fenêtre parent de myapp.

Cela aura une conséquence en cascade, et va détruire les enfants et les descendants de root. En résumé, chaque partie du GUI sera détruite.

Evidemment, pour faire cela, myapp doit savoir qui est son parent. Donc (7) nous ajoutons du code à son constructeur pour permettre à myapp de se souvenir de son parent.

Comportement Du Programme

Quand vous lancez ce programme, vous voyez deux boutons. Clickez sur le bouton

"OK" va changer sa couleur. Clicker sur le bouton "Cancel" va fermer l'application.

Quand votre GUI est ouvert, si vous appuyez sur la touche TAB du clavier, vous noterez que le focus du clavier alterne entre les deux boutons. mais si vous appuyez sur la touche ENTER/RETURN du clavier, rien ne se passe. C'est parce que nous avec fait un lien sur les clicks de la souris (et pas les évènements du clavier) et nos

boutons. Notre prochaine tâche sera de lier aussi les évènements du clavier aux boutons.

Enfin, notez que comme le texte d'un bouton est plus court que le texte d'un autre bouton, les deux boutons ont des tailles différentes. C'est assez vilain, et nous corrigerons cela dans un programme ultérieur.

[date de dernière mise à jour: 2002-10-01]

Program Source Code

(18)

from Tkinter import * class MyApp:

def __init__(self, parent):

self.myParent = parent ### (7) remember my parent, the root self.myContainer1 = Frame(parent)

self.myContainer1.pack()

self.button1 = Button(self.myContainer1)

self.button1.configure(text="OK", background= "green") self.button1.pack(side=LEFT)

self.button1.bind("<Button-1>", self.button1Click) ### (1) self.button2 = Button(self.myContainer1)

self.button2.configure(text="Cancel", background="red") self.button2.pack(side=RIGHT)

self.button2.bind("<Button-1>", self.button2Click) ### (2) def button1Click(self, event): ### (3)

if self.button1["background"] == "green": ### (4) self.button1["background"] = "yellow"

else:

self.button1["background"] = "green"

def button2Click(self, event): ### (5) self.myParent.destroy() ### (6)

root = Tk()

myapp = MyApp(root) root.mainloop()

tt070.py

Dans le programme précédent, les boutons faisaient quelque chose en clickant dessus avec la souris, mais appuyer sur une touche du clavier ne faisait rien. Dans ce

programme, nous voyons comment faire réagir à des évènements du clavier, et à des évènements de la souris.

D'abord, il faut voir le concept de "input focus", ou simplement "focus".

Si vous connaissez la mythologie grecque (ou si vous avez vu le dessin animé de Disney "Hercules") vous vous souvenez des Parques. Les Parques sont trois vieilles femmes qui contrôlent les destinées des hommes. Chaque vie humaine est un fil dans les mains des Parques, et quand elles coupent le fil, la vie se termine.

Ce qui est remarquable à propos des Parques est qu'elles se partagent un oeil entre elles trois. Celle qui avait l'oeil devait regarder et dire aux deux autres ce qu'elle

voyait. Les Parques pouvaient se passer l'oeil, pour se relayer. Et evidemment, si vous pouvez voler l'oeil, vous avez un atout MAITRE pour négocier avec les Parques.

"Focus" est ce qui permet aux widgets de votre GUI de voir les évènements clavier.

C'est pour les widgets de votre GUI, ce que l'unique oeil était aux Parques.

(19)

Seulement un widget à la fois peut avoir le focus, et le widget qui "a le focus" est le widget qui voit, et répond aux évènements clavier. "Setting focus" sur un widget est le fait de donner le focus au widget.

Dans ce programme, par example, notre GUI a deux boutons: "OK" et "Cancel".

Supposons que j'appuie sur la touche RETURN du clavier. Est-ce que cet appui sur

"Return" sera vu (ou envoyé à) par le bouton "OK", indiquant que l'utilisateur a

accepté ce choix ? Ou bien l'évènement "Return" sera vu (ou envoyé à) par le bouton

"Cancel", indiquant que l'utilisateur a annulé l'opération ? Cela dépend, où est le

"focus". C'est-à-dire, cela dépend quel bouton "a le focus" (si l'un des deux l'a).

Comme l'oeil des Parques, qui pouvait être passé d'une Parque à une autre, le focus peut être passé d'un widget du GUI à un autre. Il y a plusieurs façons de passer, ou déplacer, le focus d'un widget à un autre. Une façon est avec la souris. Vous pouvez faire "set focus" sur un widget en clickant sur le widget avec le bouton gauche de la souris. (Au moins, ce modèle, qui est appelé le modèle "click to type", est la manière dont cela fonctionne sur Windows et Macintosh, et dans Tk et Tkinter. Certains

systèmes utilisent une convention "focus follows mouse" dans laquelle le widget qui est sous la souris a automatiquement le focus, et il n'est pas nécessaire de clicker.

Vous pouvez arriver au même résultat dans Tk en utilsiant la procédure tk_focusFollowsMouse.)

Une autre façon d'avoir le focus est d'utiliser le clavier. Les widgets qui peuvent

recevoir le focus sont rangés dans une liste circulaire (le "traversal order") dans l'ordre de création des widgets. Appuyer sur la touche TAB du clavier déplace le focus de sa position actuelle (qui peut être nulle part) au prochain widget dans la liste. A la fin de la liste, le focus se place sur le widget au sommet de la liste. Et appuyer sur

SHIFT+TAB déplace le focus en arrière, plutôt qu'en avant, dans la liste.

Quand un bouton du GUI a le focus, cela est montré par une dotted box (petite boîte) autour du texte du bouton. Voici comment voir cela. Lancez le programme précédent.

Quand le programme démarre, et que le GUI s'affiche, aucun des boutons n'a le focus, donc vous ne voyez pas la dotted box. maintenant appuyer sur la touche TAB. Vous verrez la dotted box apparaître autour du bouton gauche, montrant que le focus lui a été donné. Maintenant appuyez sur la touche TAB à nouveau, et encore une autre fois.

Vous verrez comme le focus passe au bouton suivant, et quand il arrive au dernier bouton, il revient au premier. (Comme le programme affiche seulement deux boutons, l'effet est que le focus passe d'un bouton à l'autre.)

(0) Dans ce programme, nous voulons que le bouton OK ait le focus dès le début.

Donc nous utilisons la méthode "focus_force()", qui force le focus sur le bouton OK.

Quand vous lancez ce programme, vous verrez que le bouton OK a le focus dès que l'application démarre.

Dans le dernier programme, nos boutons répondaient seulement à un évènement clavier -- appuyer sur la touche TAB -- qui faisait alterner le focus entre les deux boutons. mais si vous appuyez sur la touche ENTER/RETURN du clavier, rien ne se passe. C'est parce que nous avons fait un lien entre les clicks de la souris et nos boutons, pas entre les évènements clavier et nos boutons.

Dans ce programme, nous allons lier les évènements du clavier aux boutons.

(1) (2) Les instructions pour lier les évènements du clavier aux boutons sont simples -- elles ont le même format que les instructions pour lier les évènements de la souris. La

(20)

seule différence est que le nom de l'évènement est le nom d'un évènement clavier (dans ce cas, "<Return>") plutôt qu'un évènement souris.

Nous voulons qu'un appui sur la touche RETURN du clavier et un click gauche du bouton de la souris, aient le même effet sur le widget, donc nous lions le même gestionnaire d'évènements aux deux types d'évènement.

Ce programme montre que vous pouvez lier plusieurs types d'évènements à un seul widget (comme un bouton). Et vous pouvez lier plusieurs combinaisons <widget, event> au même gestionnaire d'évènements.

(3) (4) Maintenant que notre button widget répond à différents évènements, nous pouvons montrer comment retrouver cette information à partir d'un objet évènement.

Nous allons passer les objets évènements à une routine (5) "report_event" qui va (6) afficher notre information à propos de l'évènement obtenu à partir des attributs de l'évènement.

Notez que pour voir ces informations s'afficher sur la console, il vous faut lancer ce programme avec python (et pas pythonw) à partir d'une console.

Comportement Du Programme

Quand vous lancez ce programme, vous verrez deux boutons. Clicker sur le bouton de gauche, ou appuyer sur la touche RETURN quand le bouton a le focus du clavier, changera sa couleur. Clicker sur le bouton de droite, ou appuyer sur la touche RETURN quand le bouton a le focus du clavier, va fermer l'application. Pour n'importe lequel de ces évènements clavier ou souris, vous devez voir un message s'afficher avec l'heure de l'évènement et sa description.

[Date de dernière mise à jour: 2002-09-26]

Program Source Code

from Tkinter import * class MyApp:

def __init__(self, parent):

self.myParent = parent

self.myContainer1 = Frame(parent) self.myContainer1.pack()

self.button1 = Button(self.myContainer1)

self.button1.configure(text="OK", background= "green") self.button1.pack(side=LEFT)

self.button1.focus_force() ### (0)

self.button1.bind("<Button-1>", self.button1Click)

self.button1.bind("<Return>", self.button1Click) ### (1) self.button2 = Button(self.myContainer1)

self.button2.configure(text="Cancel", background="red") self.button2.pack(side=RIGHT)

self.button2.bind("<Button-1>", self.button2Click)

self.button2.bind("<Return>", self.button2Click) ### (2) def button1Click(self, event):

report_event(event) ### (3)

if self.button1["background"] == "green":

self.button1["background"] = "yellow"

(21)

else:

self.button1["background"] = "green"

def button2Click(self, event):

report_event(event) ### (4) self.myParent.destroy()

def report_event(event): ### (5)

"""Affiche une description de l'"vènement, basé sur ses attributs.

"""

event_name = {"2": "KeyPress", "4": "ButtonPress"}

print "Time:", str(event.time) ### (6) print "EventType=" + str(event.type), \ event_name[str(event.type)],\

"EventWidgetId=" + str(event.widget), \ "EventKeySymbol=" + str(event.keysym)

root = Tk()

myapp = MyApp(root) root.mainloop()

tt074.py

Dans un programme précédent, nous avons introduit l'"event binding". Il y a une autre façon de lier un event_handler à un widget. C'est le "command binding" et nous allons voir cela dans ce programme.

Command Binding

Vous vous souvenez que dans nos programmes précédents, nous avons lié

l'évènement de la souris "<Button-1>" à notre bouton widget. "Button" est un autre nom pour l'évènement souris "ButtonPress". Et un évènement souris "ButtonPress" est différent d'un évènement souris "ButtonRelease". L'évènement "ButtonPress" est le fait d'appuyer sur le bouton de la souris, MAIS DE NE PAS le RELACHER. L'évènement

"ButtonRelease" est le fait de relâcher le bouton de la souris.

Il nous faut distinguer un ButtonPress de la souris d'un ButtonRelease, afin de supporter des fonctionnalités comme le "drag and drop (glisser et déposer)", dans lequel nous faisons un ButtonPress sur un GUI component, glissons le component quelque part, et puis le "drop (déposer)" dans son nouvel emplacement en relâchant le bouton de la souris.

Mais les button widgets ne sont pas le genre de chose qui peuvent être glissés et déposés. Si un utilisateur pense qu'il peut faire un drag and drop d'un bouton, il pourrait faire un ButtonPress sur le button widget, puis un drag du pointeur de la souris à un autre endroit de l'écran, puis relâcher le bouton de la souris. Ce n'est pas le type d'activité que nous voulons reconnaître comme un "push" (ou -- terme

technique -- "invocation") du button widget. Pour considérer qu'un button widget a été appuyé, l'utilisateur doit avoir fait un ButtonPress sur le widget, et ensuite -- SANS déplacer le pointeur de la souris du widget -- faire un ButtonRelease. *CELA* est ce que nous appelons un button press.

(22)

C'est une notion plus compliquée de button invocation que dans nos précédents programmes, quand nous lions simplement un évènement "Button-1" au button widget avec l'event binding.

Heureusement, il existe une autre forme de binding qui supporte cette notion plus compliquée de widget invocation. On appelle cela "command binding" parce que cela utilise l'option "command" du widget.

Dans ce programme, regardez les lignes avec les commentaires (1) et (2) pour voir comment est fait le command binding. Dans ces lignes, nous utilisons l'option

"command" pour lier button1 au gestionnaire d'évènement "self.button1Click", et lier button2 au gestionnaire d'évènement "self.button2Click".

(3) (4)

Regardez la définition des gestionnaires d'évènements. Notez que -- contrairement aux gestionnaires d'évènements dans le programme précédent -- ils n'attendent PAS un objet évènement en tant que paramètre. C'est parce que le command binding, contrairement à l'event binding, ne passe PAS automatiquement un objet évènement en tant que paramètre. Et évidemment, cela est sensé. Un command binding ne lie pas un simple évènement à un gestionnaire. Il lie plusieurs évènements à un

gestionnaire. Pour un Button widget, par exemple, il lie un motif de ButtonPress suivi d'un ButtonRelease à un gestionnaire. S'il doit passer un évènement au gestionnaire d'évènement, cela sera lequel : ButtonPress ou ButtonRelease? Aucun ne serait complètement correct. C'est pour cette raison, que le command binding,

contrairement à l'event binding, ne passe pas un objet évènement.

Nous verrons plus en détail cette différence dans le programme suivant, mais pour l'instant, lançons le programme.

Comportement Du Programme

Quand vous lancez ce programme, les boutons que vous voyez sont exactement comme dans le programme précédent...mais ils se comportent différemment.

Comparez leur comportement quand vous faites un ButtonPress avec la souris sur l'un des boutons. Par exemple, déplacez le pointeur de la souris jusqu'à ce qu'il soit

positionné au-dessus du button widget "OK", puis appuyez sur le click gauche de la souris, MAIS NE LE RELACHEZ PAS.

Si vous faites cela dans l'exemple précédent, le gestionnaire the button1Click sera immédiatement exécuté et un message s'affichera. Mais si vous faites cela dans ce programme, rien ne se passera... JUSQU'A CE QUE LE BOUTON DE LA SOURIS SOIT RELACHE. A ce moment-là, vous verrez s'afficher un message.

Il y a une autre différence. Comparez leur comportement quand vous appuyez sur la barre d'espace et sur la touche RETURN. Par exemple, utilisez la touche TAB pour mettre le focus sur le bouton "OK", puis appuyez sur la barre d'espace ou la touche RETURN.

Dans le programme précédent (dans lequel nous avons lié le bouton "OK" à un appui sur la touche "Return"), appuyer sur la barre d'espace était sans effet, mais appuyer sur la touche RETURN changeait la couleur du bouton. Dans ce programme, le

(23)

comportement est inversé -- appuyer sur la barre d'espace change la couleur du bouton, mais appuyer sur la touche RETURN est sans effet.

Nous verrons ces comportements dans le programme suivant.

[date de dernière mise à jour : 2002-10-01]

Program Source Code

from Tkinter import * class MyApp:

def __init__(self, parent):

self.myParent = parent

self.myContainer1 = Frame(parent) self.myContainer1.pack()

self.button1 = Button(self.myContainer1, command=self.button1Click) ### (1) self.button1.configure(text="OK", background= "green")

self.button1.pack(side=LEFT) self.button1.focus_force()

self.button2 = Button(self.myContainer1, command=self.button2Click) ### (2) self.button2.configure(text="Cancel", background="red")

self.button2.pack(side=RIGHT)

def button1Click(self): ### (3)

print "button1Click event handler"

if self.button1["background"] == "green":

self.button1["background"] = "yellow"

else:

self.button1["background"] = "green"

def button2Click(self): ### (4)

print "button2Click event handler"

self.myParent.destroy()

root = Tk()

myapp = MyApp(root) root.mainloop()

tt075.py

Dans le programme précédent, nous avons introduit le "command binding" et mis en évidence quelques différences entre l'event binding et le command binding. Dans ce programme, nous verrons plus en détail ces différences.

A quels évènements "command" est lié ?

Dans le programme précédent, si vous utilisez la touche TAB pour mettre le focus sur le bouton "OK", appuyer sur la barre d'espace change la couleur du bouton, mais appuyer sur la touche RETURN est sans effet.

(24)

La raison est que l'option "command" pour un Button widget fournit la conscience des évènements clavier comme des évènements de la souris. L'évènement clavier qu'il écoute est un appui de la barre d'espace, pas de la touche RETURN. Donc, avec le command binding, appuyer sur la barre d'espace changera la couleur du bouton OK, mais appuyer sur la touche RETURN sera sans effet.

Ce comportement me semble (à moi, avec mon passé Windows) inhabituel. Donc la morale ici est que si vous utilisez le command binding, il faut comprendre exactement à quoi vous vous liez. C'est une bonne idée de savoir quels évènements clavier et souris déclenchent la commande invoquée.

Hélas, la seule source fiable d'informations est le code source de Tk lui-même. Pour une information plus accessible, vous pouvez consulter des livres sur Tk (le livre de Brent Welch "Practical Programming in Tcl and Tk" est très bon) ou à propos de Tkinter. La documentation Tk documentation est inégale, mais disponible en ligne.

Pour la version 8.4 de Tcl, la documentation en ligne est disponible à :

http://www.tcl.tk/man/tcl8.4/TkCmd/contents.htm

Vous devez savoir que tous les widgets ne fournissent pas une option "command". La plupart des Button widgets (RadioButton, CheckButton, etc.) la fournissent. Et d'autres proposent des options similaires (par exemple scrollcommand). Mais il vous faudra vraiment étudier chaque type de widget pour trouver si il supporte le command binding. Mais apprenez par tous les moyens les options de "command" pour les widgets que vous utiliserez. Cela améliorera le comportement de votre GUI, et vous simplifiera la vie.

Utiliser ensemble l'Event Binding et le Command Binding

Nous avons noté dans notre dernier programme, que le command binding,

contrairement à l'event binding, ne passe pas automatiquement un objet évènement en tant que paramètre. Cela peut vous compliquer la vie si vous voulez lier un

gestionnaire d'évènement à un widget en utilisant *à la fois* l'event binding *et* le command binding.

Par exemple, dans ce programme, nous voulons que nos boutons répondent à un appui sur la touche RETURN tout comme sur la barre d'espace. Pour y arriver, nous allons utiliser l'event binding avec l'évènement clavier <Return>, comme nous avons fait dans un programme précédent. (1)

Le problème est que le command binding ne va pas passer un objet évènement en tant que paramètre, alors que l'event binding le fera. Donc, devons-nous écrire notre gestionnaire d'évènement ?

Il y a plusieurs solutions à ce problème, mais la plus simple est d'écrire deux

gestionnaires d'évènements. Le "véritable" gestionnaire d'évènement (2) sera celui utilisé par le binding "command" et il n'attendra pas un objet évènement.

L'autre gestionnaire d'évènement (3) sera juste un wrapper pour le véritable

gestionnaire d'évènement. Ce wrapper attendra le paramètre objet évènement , mais l'ignorera et appelera le véritable gestionnaire d'évènement sans paramètre. Nous donnerons au wrapper le même nom que le véritable gestionnaire d'évènement mais avec en plus un suffixe "_a".

(25)

Comportement Du Programme

Si vous lancez ce programme, son comportement sera le même que le programme précédent, avec cette différence que maintenant les boutons vont répondre à un appui sur la touche RETURN comme sur la barre d'espace.

[date de dernière mise à jour : 2002-10-01]

Program Source Code

from Tkinter import * class MyApp:

def __init__(self, parent):

self.myParent = parent

self.myContainer1 = Frame(parent) self.myContainer1.pack()

self.button1 = Button(self.myContainer1, command=self.button1Click) self.button1.bind("<Return>", self.button1Click_a) ### (1)

self.button1.configure(text="OK", background= "green") self.button1.pack(side=LEFT)

self.button1.focus_force()

self.button2 = Button(self.myContainer1, command=self.button2Click) self.button2.bind("<Return>", self.button2Click_a) ### (1)

self.button2.configure(text="Cancel", background="red") self.button2.pack(side=RIGHT)

def button1Click(self): ### (2)

print "button1Click event handler"

if self.button1["background"] == "green":

self.button1["background"] = "yellow"

else:

self.button1["background"] = "green"

def button2Click(self): ### (2)

print "button2Click event handler"

self.myParent.destroy()

def button1Click_a(self, event): ### (3)

print "button1Click_a event handler (a wrapper)"

self.button1Click()

def button2Click_a(self, event): ### (3)

print "button2Click_a event handler (a wrapper)"

self.button2Click()

root = Tk()

myapp = MyApp(root) root.mainloop()

tt076.py

Dans les derniers programmes, nous avons vu des façons de travailler avec des gestionnaires d'évènement.

(26)

Dans ce programme, nous voyons comment partager de l'information entre des gestionnaires d'évènement.

Partager De L'Information Entre Des Fonctions De Gestionnaire D'Evènement

Il y a différents cas dans lesquels vous voulez qu'un gestionnaire d'évènement accomplisse une tâche, puis partage les résultats de cette tâche avec les autres gestionnaires d'évènements de ce programme.

Un cas fréquent est une application avec deux jeux de widgets. Un jeu de widgets met en place, ou choisit des informations, et l'autre fait quelque chose avec cette

information.

Par exemple, vous pouvez avoir un widget qui permet à un utilisateur de choisir un fichier dans une liste, et un autre jeu de widgets qui propose différentes opérations sur le fichier sélectionné -- ouvrir le fichier, le détruire, le copier, le renommer, et ainsi de suite.

Ou vous pouvez avoir un jeu de widgets qui définit des options de configuration pour votre application, ou un autre jeu (des boutons avec les options SAVE et CANCEL, peut-être) qui soit enregistrent ces valeurs sur disque, ou sort sans les enregistrer.

Ou bien vous pouvez avoir un jeu de widgets qui définit des paramètres pour un

programme que vous voulez lancer, et un autre widget (sans doute un bouton avec un nom comme RUN ou EXECUTE) qui lance le programme avec ces paramètres.

Ou vous pouvez avoir besoin d'une invocation d'une fonction de gestionnaire

d'évènement pour passer de l'information à un appel ultérieur de la même fonction.

Considérez un gestionnaire d'évènement qui fait alterner une variable entre deux différentes valeurs. Pour assigner une nouvelle valeur à cette variable, il a besoin de savoir quelle valeur avait cette variable lors de la dernière exécution de la fontion

Le Problème

Le problème ici est que le gestionnaire d'évènement est une fonction séparée. Chaque gestionnaire d'évènement a ses propres variables locales qu'il ne partage pas avec les autres fonctions des gestionnaires d'évènements, ou même avec des futurs appels de cette fonction. Donc le problème est -- comment une fonction de gestionnaire

d'évènement peut partager des données avec d'autres fonctions de gestionnaire d'évènement, si il ne peut pas partager ses variables locales entre les fonctions ? La solution, évidemment, est que les variables à partager ne peuvent être locales aux fonctions de gestionnaire d'évènement. Elles doivent être stockées *en dehors* des fonctions de gestionnaire d'évènement.

Solution 1 -- Utiliser Des Variables Globales

Une technique pour faire cela est de les (les variables que vous voulez partager) rendre globales. Par exemple, dans chaque gestionnaire d'évènement qui a besoin de changer ou voir myVariable1 et myVariable2, vous pouvez mettre l'instruction :

global myVariable1, myVariable2

(27)

Mais l'utilisation de variables globales est potentiellement dangereuse, et est généralement désapprouvé en tant que programmation baclée.

Solution 2 -- Utilisez Des Variables D'Instance

Une technique bien plus propre est d'utiliser des "variables d'instance" (c'est à dire, des variables "self.") pour détenir cette information que vous voulez partager entre les gestionnaires d'évènements. Pour faire cela, évidemment, votre application doit être implémentée en tant que classe, et pas simplement en tant que in-line code.

Cela est une des raisons pour lesquelles (plus tôt dans ces séries) nous avons mis notre application dans une classe. Comme nous avons fait cela plus tôt, maintenant notre application a déjà une structure qui nous permet d'utiliser des variables

d'instance.

Dans ce programme, nous allons mémoriser et partager une information très simple : le nom du dernier bouton appelé. Nous le stockerons dans une variable d'instance appelée "self.myLastButtonInvoked". [voyez ### 1 commentaire]

Et pour montrer que nous mémorisons cette information, quand un gestionnaire de bouton est appelé, il affichera cette information. [voyez ### 2 commentaires]

Comportement Du Programme

Ce programme affiche trois boutons. Quand vous lancez ce programme, si vous

clickez sur n'importe quel bouton, il affichera son nom, et le nom du précédent bouton clické.

Notez qu'aucun de ces boutons ne fermera l'application, donc quand vous voulez le faire, vous devez utiliser le widget CLOSE (l'icône avec un "X" dans la boîte, en haut à droite de la barre de titre).

[Date de dernière mise à jour: 2002-10-05]

Program Source Code

#!/usr/bin/python

# -*- coding: iso-8859-1 -*- from Tkinter import *

class MyApp:

def __init__(self, parent):

### 1 -- Au début, nous n'avons pas encore appelé de gestionanire de bouton.

self.myLastButtonInvoked = None self.myParent = parent

self.myContainer1 = Frame(parent) self.myContainer1.pack()

self.yellowButton = Button(self.myContainer1, command=self.yellowButtonClick) self.yellowButton.configure(text="JAUNE", background="yellow")

self.yellowButton.pack(side=LEFT)

self.redButton = Button(self.myContainer1, command=self.redButtonClick) self.redButton.configure(text="ROUGE", background= "red")

self.redButton.pack(side=LEFT)

(28)

self.whiteButton = Button(self.myContainer1, command=self.whiteButtonClick) self.whiteButton.configure(text="BLANC", background="white")

self.whiteButton.pack(side=LEFT) def redButtonClick(self):

print "bouton ROUGE clické. Le précédent bouton clické était", self.myLastButtonInvoked ### 2

self.myLastButtonInvoked = "RED" ### 1 def yellowButtonClick(self):

print "bouton JAUNE clické. Le précédent bouton clické était", self.myLastButtonInvoked ### 2

self.myLastButtonInvoked = "YELLOW" ### 1 def whiteButtonClick(self):

print "bouton BLANC clické. Le précédent bouton clické était", self.myLastButtonInvoked ### 2

self.myLastButtonInvoked = "WHITE" ### 1

print "\n"*100 # une façon simple de nettoyer l'écran print "Démarrage..."

root = Tk()

myapp = MyApp(root) root.mainloop() print "... Done!"

tt077.py

Dans ce programme nous voyons quelques ...

Fonctionnalités Avancées Du Command Binding

Dans le programme tt075.py, nous avons utilisé l'option "command" pour lier un gestionnaire d'évènement à un widget. Par exemple, dans ce programme, l'instruction

self.button1 = Button(self.myContainer1, command=self.button1Click)

lie la fonction button1Click au widget button1.

Et nous avons utilisé l'event binding pour lier nos boutons à l'évènement clavier

<Return>.

self.button1.bind("", self.button1Click_a)

Dans notre programme précédent, les gestionnaires d'évènements pour les deux boutons faisaient des choses différentes.

Mais supposons un cas différent. Supposons, nous avons plusieurs boutons, qui

doivent tous déclencher le *même* type d'action. La meilleure manière de faire dans ce cas est de lier tous les évènements pour tous les boutons à un seul gestionnaire d'évènement. Chaque bouton va appeler la même handler routine, mais lui passer différents paramètres lui indiquant quoi faire.

C'est ce que nous faisons dans ce programme.

(29)

Command Binding

Dans ce programme, comme vous pouvez le voir, nous avons deux boutons, et nous utilisons l'option "command" pour les lier tous les deux au même gestionnaire

d'évènement, la routine -- "buttonHandler". Nous passons trois paramètres à la

routine buttonHandler : le nom du bouton (dans la variabe button_name), un nombre, et une chaîne de caractères.

self.button1 = Button(self.myContainer1,

command=self.buttonHandler(button_name, 1, "Bien !") )

Dans un programme plus sérieux, la routine buttonHandler routine ferait évidemment un travail, mais dans ce programme, il affiche simplement les paramètres qu'il reçoit.

Event Binding

Nous avons vu le command binding. Et pour l'event binding?

Vous remarquerez que nous avons mis en commentaire les deux lignes qui font l'event binding sur l'évènement <Return>.

# self.button1.bind("", self.buttonHandler_a(event, button_name, 1, "Bien !"))

Ceci est le premier signe d'un problème. L'Event binding passe automatiquement un paramètre event -- mais il n'y a pas de manière d'inclure un paramètre event dans notre liste de paramètres.

Nous reviendrons sur ce problème plus tard. Pour l'instant, lançons le programme et voyons ce qui se passe.

Comportement Du Programme

Quand vous regardez le code, ce programme semble raisonnable. Mais quand vous le lancez, vous verrez qu'il ne fonctionne pas correctement. La routine buttonHandler est appelée avant que le GUI ne s'affiche. En fait, elle est appelée DEUX fois !

Et si vous faîtes un click gauche de la souris sur n'importe lequel des boutons, vous verrez que rien ne se passe -- la routine "eventHandler" n'est *pas* appelée.

Notez que la seule façon de fermer ce programme est de clicker sur l'icône "close" (le X "X" dans la boîte) en haut à droite de la barre de titre.

Donc lanceez le programme maintenant, et voyez ce qui se passe. Puis, dans notre programme, nous verrons pourquoi cela se passe.

[date de dernière mise à jour : 2003-02-23]

Program Source Code

from Tkinter import * class MyApp:

def __init__(self, parent):

self.myParent = parent

Références

Documents relatifs

Déterminez le plus grand entier N divisible par tous les entiers qui ne dépassent pas sa racine septième.. Justifiez

Un joueur qui dit &#34;je ne sais pas&#34; indique que toutes les formes présentes une seule fois dans son tableau quant à son paramètre personnel ne conviennent pas (car sinon,

Quoi qu'il en soit de la précision, on peut conclure que celui des trois qui a la plus forte probabilité de ne pas pouvoir accueillir tous ses clients est la compagnie

[r]

[r]

En cette période de confinement, je suis en train de vous créer des quiz en ligne pour vous aider à réviser les différentes UAA en sciences ; j'essaie également de créer un site

b) conjugaison : connaissance des formes verbales , participe passé irrégulier, comme par exemple 'chiusato' à la place de 'chiuso', ou bien 'costrutto' à la place de

[r]