XIV. Liaisons d'événements▲
Perl/Tk est un langage de programmation événementiel : on conçoit un programme de façon à ce qu'il réponde à des événements générés par celui-ci. Presser un bouton, déplacer la souris ou entrer des caractères au clavier sont des exemples d'événements. La relation entre ces actions et le widget qu'elles concernent s'appelle une liaison.
Tout widget Perl/Tk possède ses propres liaisons prédéfinies. Ainsi, le widget bouton modifie sa couleur lorsque le pointeur de la souris est au-dessus de lui et invoque la fonction de rappel spécifiée lorsqu'il est pressé. Ce sont des liaisons par défaut, qui sont créées en même temps que le widget lui-même.
Vous pouvez faire en sorte que votre programme réponde à d'autres événements en utilisant la méthode bind qui vous permettra d'assigner des fonctions de rappel à différentes séquences d'événements ; sa forme générale est :
$widget-
>
bind
( séquence, fct_rappel );
Vous pouvez aussi surcharger les liaisons prédéfinies en les supprimant ou en créant les vôtres.
XIV-A. La méthode bind▲
Pour utiliser la méthode bind, invoquez-la sur le widget pour lequel vous souhaitez ajouter la liaison. L'instruction $bouton-
>
bind
vous permet, par exemple, d'ajouter une liaison au bouton $bouton. Dans certains cas, vous agirez sur la fenêtre principale de votre application : $mw->bind(...). Il existe plusieurs paramètres possibles pour bind, décrits par la liste suivante :
$widget-
>
bind
();
-
Sans paramètre, bind renvoie la liste des liaisons (<Button-1>, <Key-D>, par exemple) qui ont été créées pour ce widget. Elle ne renvoie pas les liaisons prédéfinies. Voici un exemple :
Sélectionnez$bouton
=
$mw-
>
Button( ... )->
pack
;$bouton-
>
bind
("<Button-3>"
, sub{
...}
);@liaisons
=
$bouton-
>
bind
();print
"Liaisons du bouton :
@liaisons\n
"
;# Cela afficherait :
# Liaisons du bouton : <Button-3>
-
Cet appel renverra une chaîne vide si aucune liaison supplémentaire n'a été définie pour ce widget.
Sélectionnez$widget-
>
bind
(séquence); -
Pour connaître la fonction de rappel associée à une séquence d'événement, il suffit de passer celle-ci comme premier paramètre (« <Button-3> », par exemple) et bind renverra alors la fonction associée. En étendant l'exemple précédent, l'information contenue dans
@liaisons
servira à connaître les fonctions de rappel associées aux différentes liaisons :Sélectionnezforeach (
@liaisons
){
print
"
$_
utilise la fonction de rappel "
,$bouton-
>
bind
($_
),"
\n
"
;}
# <Button-3> utilise la fonction de rappel :
# Tk::Callback=CODE(0x91fdcc)
-
Si vous passez une séquence n'existant pas pour le widget, vous obtiendrez une chaîne vide. De plus, si vous utilisez une séquence qui est considérée comme une liaison prédéfinie (« <Button-1> » sur un widget bouton, par exemple), vous obtiendrez aussi une chaîne vide (sauf si vous avez créé une autre liaison pour celui-ci).
Sélectionnez$widget-
>
bind
(séquence, fct_rappel); -
Pour qu'une fonction de rappel soit invoquée lorsqu'un événement survient, il suffit de l'indiquer à la suite de la séquence dans l'appel à bind. Cette fonction peut être exprimée sous l'une des formes étudiées au chapitre 3. Voici quelques exemples :
-
Pour ôter une liaison pour une séquence donnée, il suffit de passer une chaîne vide comme fonction de rappel.
Sélectionnez$widget-
>
bind
(marqueur [, séquence, fct_rappel ] ); -
Le marqueur permet de désigner un type de widget. On l'utilise lorsque l'on souhaite que tous les widgets d'un type donné partagent le même comportement. Si, par exemple, on veut afficher un menu de recherche lorsque l'on clique avec le bouton droit de la souris dans un widget texte, on écrira :
Sélectionnez$t1
=
$mw-
>
Scrolled("Text"
)->
pack
(-
expand=>
1
,-
fill=>
'both'
);$t2
=
$mw-
>
Scrolled("Text"
)->
pack
(-
expand=>
1
,-
fill=>
'both'
);$menu
=
$mw-
>
Menu(-
menuitems=>
[ ["command"
=>
"Recherche"
,-
command=>
\&
cherche_fic ], ["command"
=>
"Nouvelle recherche"
,-
command=>
\&
cherche_encore ] ],-
tearoff=>
0
);$mw-
>
bind
( Tk::Text,"<Button-3>"
, sub{
$menu-
>
Popup(-
popover=>
'cursor'
,-
popanchor=>
"nw"
);}
); -
Tous les widgets texte de l'application disposeront alors de ce menu. Il faudrait peaufiner un peu le travail des fonctions de recherche pour qu'elles sachent quel est le widget qui les a déclenchés, mais l'intérêt est qu'il n'y a pas besoin de réécrire la même liaison pour chacun des widgets textes.
-
Dans cet exemple, nous avons indiqué la séquence « <Button-3> » à notre liaison. Si nous n'avions pas spécifié de fonction de rappel, nous aurions obtenu la liste de celles qui sont associées à ce marqueur.
- Le marqueur 'all' est spécial : il désigne tous les widgets ainsi que la fenêtre de l'application. Soyez prudent : votre fonction de rappel sera beaucoup plus active que vous ne le pensez !
XIV-B. Paramètres passés à la fonction de rappel▲
Le premier paramètre d'une fonction de rappel assignée par bind est toujours une référence au widget appelant, même si la liaison a lieu sur tout un type de widget. Cette référence permet de connaître le widget ayant invoqué la fonction.
Voici un exemple d'utilisation d'une simple zone de saisie :
$saisie
=
$mw-
>
Entry()->
pack
;
$saisie-
>
bind
("<Return>"
, \&
Entree_pressee);
sub Entree_pressee {
my ($s
) =
@_
;
print
"La zone de saisie contient : "
, $s-
>
get, "
\n
"
;
}
Lorsque bind appelle une fonction de rappel sur tout un type de widget, elle facilite beaucoup le travail consistant à déterminer le widget à l'origine de l'événement :
$mw-
>
Scrolled("Text"
)->
pack
( -
expand =>
1
, -
fill =>
'both'
);
$mw-
>
Scrolled("Text"
)->
pack
( -
expand =>
1
, -
fill =>
'both'
);
$menu
=
$mw-
>
Menu(
-
menuitems =>
[
[
"command"
=>
"Sauver"
,
-
command =>
\&
sauve_fic
],
[
"command"
=>
"Ouvrir"
,
-
command =>
\&
ouvrir_fic
]
],
-
tearoff =>
0
);
$mw-
>
bind
( Tk::Text, "<Button-3>"
, sub {
$menu-
>
Popup( -
popover =>
'cursor'
) }
);
sub sauve_fic {
my ($texte
) =
@_
;
open
( FIC, ">monfic"
) ||
die "Impossible de créer monfic
\n
"
;
print
FIC $texte-
>
get( "1.0"
, "end"
);
close
FIC;
}
L'appel à bind utilise Tk::Text comme premier paramètre. La liaison s'appliquera à tous les widgets texte de l'application. Dans cet exemple, quel que soit le widget texte sur lequel on a cliqué, son contenu sera écrit dans le fichier monfic. L'application pourrait également demander à l'utilisateur d'entrer un nom de fichier afin que tout cela soit un peu plus pratique.
XIV-C. Définition de séquences d'événements▲
Pour l'instant, nous avons utilisé les séquences d'événements <Button-3>, <Button-1> et <Return>, mais nous n'avons pas encore expliqué comment en construire. Bien que les exemples déjà vus semblent simples et triviaux, les séquences d'événements peuvent devenir bien plus compliquées.
Une séquence est construite à partir d'un modificateur éventuel, d'un événement et d'un détail optionnel. Ces parties sont séparées par des tirets et placées entre chevrons.
- <modificateur-événement-détail>
Lorsque nous étudierons les différentes liaisons possibles, souvenez-vous qu'il est possible que plusieurs séquences puissent correspondre à une même liaison. Si une liaison existe pour un bouton précis, et qu'une autre est créée pour tous les boutons, c'est la fonction de rappel de celle qui est spécifique qui sera d'abord appelée ; les fonctions des liaisons plus générales le seront ensuite.
XIV-C-1. Modificateurs ▲
Un modificateur est un événement survenant en même temps que l'événement principal, la pression de la touche 'Ctrl' en même temps qu'un clic avec le bouton de la souris, par exemple. L'événement modificateur doit survenir avant afin que la séquence complète corresponde (on presse d'abord la touche, puis le bouton de la souris).
Les modificateurs possibles sont décrits ci-dessous :
Control
- La touche 'Ctrl' doit être pressée lorsque l'événement principal survient (<Control-Button-1>, par exemple).
Shift
- La touche 'Maj' doit être pressée lorsque l'événement principal survient (<Shift-Button-3>, par exemple).
Lock
- La touche de verrouillage des majuscules doit être pressée afin de passer en majuscules (<Lock-Key-a>, par exemple).
Alt
- La touche 'Alt' doit être pressée lorsque l'événement principal survient (<Alt-Key-x>, par exemple). Les utilisateurs de Microsoft Windows doivent savoir que, parfois, ce système ne permet pas au gestionnaire d'événements d'avertir les applications que la touche 'Alt' gauche a été pressée. Cette touche est, normalement, celle que l'on utilise pour basculer entre les applications en faisant 'Alt-Tab'. Si elle ne fonctionne pas, essayez celle de droite avant d'abandonner. Cet avertissement s'adresse aussi à ceux qui utilisent un serveur X Window (Exceed, par exemple) sous MS Windows pour accéder à un système Unix.
ButtonX où X vaut 1, 2, 3, 4 ou 5 (le raccourci BX est également autorisé)
- Ces modificateurs indiquent que le numéro de bouton spécifié doit être pressé avant que le reste de l'événement ne survienne. Si, par exemple, on souhaite déclencher un événement lorsque l'utilisateur clique sur le bouton 3 pendant qu'il appuie sur le bouton 1, on utilisera la séquence <Button1-Button-3> (ou <B1-Button-3>). L'événement ne sera pas déclenché si l'on clique sur le bouton 1 pendant que le 3 est pressé. L'ordre est important.
On ne peut utiliser simplement <ButtonX> car, s'il n'y a pas le tiret entre Button et le nombre, cela dénote un modificateur d'un autre événement.
Double
- Double est un type de modificateur spécial indiquant que l'événement principal doit survenir deux fois. Il impose une contrainte sur le délai maximum entre les deux occurrences de l'événement principal. Le plus souvent, il sert à indiquer un double clic sur un bouton de la souris. Il est important de noter que <Double-Button-1> n'est pas équivalent à <Button-1><Button-1>. Bien qu'ils semblent signifier la même chose, il n'y a pas de contrainte de délai dans le dernier événement : il signifie « vous avez pressé le bouton 1, puis, un peu plus tard, vous avez recommencé ». <Double-Button-1>, lui, signifie « vous avez pressé le bouton 1, et dans un certain laps de temps, vous avez recommencé ».
Triple
- Ce modificateur ressemble à Double, mais nécessite que l'événement survienne trois fois dans une succession rapide. Un aspect important de Double et Triple est que ces modificateurs peuvent se cumuler. Si l'on clique rapidement cinq fois sur le bouton 1 de la souris, le premier clic correspondra à l'événement <Button-1>, le second à <Double-Button-1>, le troisième à <Triple-Button-1>, le quatrième également, etc. Cela n'est vrai que si l'événement <Triple-Button-1> est défini, si seul <Double-Button-1> l'est, le troisième clic réactivera cette liaison au lieu de celle avec <Triple->. Le chronogramme de la figure 14.1 montre quand sont générés les événements.
Meta (ou M )
- La touche 'Meta' doit être pressée pendant l'événement principal. Cette touche n'existe généralement que sur les systèmes X Window.
ModX (ou MX )
- Ce modificateur n'existe aussi que sur les systèmes X Window. Il existe plusieurs modificateurs (1--5) ; utilisez Ev('K') pour trouver leur emplacement sur votre clavier.
XIV-C-2. Types d'événements (avec détails optionnels) ▲
La partie de la chaîne d'événement consacrée à l'événement principal est ce qui nous intéresse ici. La section précédente vous a expliqué comment ajouter un éventuel modificateur. Lorsque l'on dit qu'un événement est déclenché ou généré, cela signifie qu'il a survenu. Si aucune fonction de rappel ne lui est associée, tout se passera comme si rien n'était vraiment arrivé. La liste suivante présente les différents types d'événements et les détails éventuels qui leur sont applicables.
ButtonPress ou Button
- Cet événement survient lorsqu'un bouton de la souris est pressé. Il désigne n'importe lequel des boutons disponibles, mais vous pouvez ajouter un détail pour faire référence à un bouton précis : <Button-1>, <Button-2>, etc.
ButtonRelease
- Cet événement survient lorsqu'un bouton de la souris est relâché. Certaines opérations peuvent avoir lieu selon le bouton pressé (ButtonPress ou Button) relâché (ButtonRelease). On peut préciser le bouton concerné : <ButtonRelease-1>, <ButtonRelease-2>, etc. Dans le cas contraire, l'événement désigne n'importe quel bouton.
Circulate
- Cet événement est généré lorsque l'application a plus d'une fenêtre et que l'ordre d'empilage de celles-ci est modifié.
Colormap
- Cet événement survient lorsque la palette de couleurs du widget (habituellement de premier niveau) est modifiée.
Configure
- Survient lorsqu'un widget est configuré. Si l'on fait correspondre une fonction de rappel à cet événement, il faut être prudent : elle sera probablement appelée très souvent. À chaque fois que la fenêtre de l'application change de taille, tous les widgets qu'elle contient sont reconfigurés, ce qui provoque la génération de l'événement Configure pour chacun d'eux. Celui-ci survient également lors de la création d'un widget.
Destroy
- Généré lorsque le widget est détruit. On force la destruction d'un widget en faisant
$widget-
>
destroy().
Enter
- Survient lorsque le pointeur de la souris pénètre dans la zone occupée par le widget. Il est important de se souvenir que cet événement ne correspond pas à l'appui de la touche 'Entrée'.
Expose
- Cet événement survient lorsque la fenêtre est mise au premier plan.
FocusIn
- Généré lorsque le widget reçoit le focus clavier consécutivement à une tabulation de la part de l'utilisateur (ou à une instruction
$widget-
>
focus() du programme).
FocusOut
- Cet événement est l'opposé du précédent. Il est déclenché lorsque le widget perd le focus clavier.
Gravity
- Survient lorsque le widget est déplacé consécutivement à une modification de la taille de son parent.
KeyPress (ou Key)
- Lorsqu'une touche du clavier est pressée, cet événement est généré. Pour spécifier une touche précise – 'a', par exemple –, on détaille l'événement : <Key-a>. Si vous souhaitez savoir quelle est la touche qui a déclenché l'événement, utilisez Ev('K') comme paramètre de votre fonction de rappel :
$mw-
>
bind
("<Key>"
, [ \&
verif_touche, Ev('K'
) ]);
- Ceci a pour effet de passer le symbole de la touche pressée en paramètre à verif_touche. Pour connaître les symboles des touches, utilisez ce script :
use Tk;
$mw
=
MainWindow->
new;
$mw-
>
bind
("<Key>"
, [ sub {
print
"Touche :
$_[1]\n
"
; }
,
Ev('K'
)] );
MainLoop;
- Lorsque vous pressez une touche, son symbole s'affiche à l'écran. Notez que les caractères placés sous les chiffres ('&', 'é', '"', etc.) sont désignés par leur nom ("ampersand", "eacute", "quotedbl", etc.).
KeyRelease
- Cet événement est le compagnon du précédent. Il est déclenché lorsque la touche est relâchée. Dans certains cas, il est préférable d'attendre que la touche soit relâchée avant de faire quoi que ce soit.
Leave
- Cet événement survient lorsque le pointeur de la souris quitte la zone occupée par le widget. Utilisez les événements Enter et Leave pour créer deux liaisons pour le même widget et vous pourrez alors faire des choses aussi amusantes que la modification du curseur de la souris lorsqu'il est au-dessus du widget (si le widget dispose de l'option -cursor, utilisez plutôt celle-ci).
Map
- Survient lorsque la fenêtre a été placée ou ouverte (désiconifiée).
Motion
- Cet événement est généré lorsque la souris se déplace, à l'écran, au-dessus de votre application. Il est préférable de ne pas lier cet événement, car la fonction de rappel serait appelée en permanence. Cela dit, si vous ne le liez qu'à un seul widget, vous ne capturerez cet événement que lorsque le pointeur de la souris passera sur celui-ci, mais cela représente encore beaucoup d'appels à la fonction de rappel. Je vous conseille donc de ne le lier que si vous avez de très bonnes raisons pour le faire.
Reparent
- Cet événement survient lorsque le parent du widget lié est modifié.
Unmap
- Généré lorsque la fenêtre liée est mise sous forme d'icône.
Visibility
-
Cet événement est généré lorsqu'un widget s'affiche pour la première fois. Il y a plusieurs façons de rendre un widget visible dans une application :
- au démarrage de l'application, lorsque le widget est placé à l'écran, l'événement Visibility est généré. Ce ne sera pas le cas si vous créez un widget sans le placer sur l'écran ;
- lorsque le widget est ôté par pack('forget'), puis replacé ;
- lors d'une modification de la taille de la fenêtre et que le widget apparaît alors dans la partie visible de celle-ci (ce qui est habituellement le cas lorsqu'une fenêtre, qui avait été réduite, est agrandie) ;
- lorsque le widget est à l'intérieur d'un autre (tel qu'un widget texte ou un canevas) et que le parcours de ce dernier fait apparaître le widget à l'écran.
XIV-D. Informations sur un événement▲
La méthode Ev permet d'obtenir des informations sur un événement donné. Celle-ci sait utiliser de nombreuses valeurs, documentées sur le site documentaire de Perl/Tk, http://w4.lns.cornell.edu/~pvhp/ptk/doc/bind.htm, maintenu par Peter Prymmer. Je ne présenterai ici que les valeurs utilisées dans 99.9 % des cas. Certaines de celles-ci ne fonctionnent qu'avec des événements précis et, si vous en utilisez une qui ne convient pas, vous obtiendrez une valeur non définie.
XIV-D-1. Coordonnées ▲
On utilisera les appels Ev('x') et Ev('y') pour connaître l'emplacement où est survenu un événement. Ceux-ci renvoient des coordonnées relatives à la fenêtre dans laquelle l'événement a été généré. Si l'on préfère obtenir des coordonnées relatives à la fenêtre principale du système de fenêtrage (celle du bureau sous Windows, la fenêtre racine sous X), il suffit d'utiliser les majuscules X et Y : Ev('X') et Ev('Y').
Attention : cette dernière forme ne fonctionne que pour les événements ButtonPress, ButtonRelease, KeyPress, KeyRelease, ou Motion.
XIV-D-2. Numéro du bouton de la souris ▲
L'appel Ev('b') renvoie le numéro du bouton pressé de la souris. Il ne fonctionne que pour les événements ButtonPress ou ButtonRelease.
XIV-D-3. Hauteur et largeur ▲
Les paramètres 'h' et 'w' permettent d'obtenir, respectivement, la hauteur et la largeur associées à un événement : celles du widget. Si, par exemple, on souhaite connaître la nouvelle taille d'un bouton après un agrandissement manuel de la fenêtre, on écrira :
$bouton-
>
bind
("<Configure>"
,
[ sub {
print
"Haut :
$_[1]
, Larg :
$_[2]\n
"
; }
,
Ev('h'
), Ev('w'
) ]);
La fonction de rappel ne sera appelée que lorsque le widget est configuré, ce qui est le cas lors de sa création et à chaque fois qu'il change de taille.
Les appels Ev('h') et Ev('w') ne fonctionnent qu'avec les événements Configure, Expose et GraphicsExpose.
XIV-D-4. Informations sur le clavier ▲
Plusieurs moyens existent pour retrouver les touches pressées par l'utilisateur. Le paramètre 'K' permet de produire le symbole (keysym) associé à une touche et l'on obtiendra le code ASCII associé en utilisant le paramètre 'k'. Essayez l'instruction suivante pour constater la différence :
$b-
>
bind
("<Key>"
, [ sub {
print
"Params :
@_\n
"
; }
,
Ev('k'
), Ev('K'
) ]);
Les appels Ev('k') et Ev('K') ne fonctionnent que pour les événements KeyPress et KeyRelease.
Pour obtenir le symbole de la touche sous forme décimale, plutôt que sous forme de chaîne, utilisez l'appel Ev('N').
XIV-D-5. Type de l'événement ▲
Un appel à Ev('T') renverra le type de l'événement ayant déclenché la fonction de rappel. S'il s'agit de KeyPress, la chaîne renvoyée sera « KeyPress ». Bien que tout cela semble évident, cette possibilité est utile lorsque l'on utilise la même fonction de rappel pour répondre à plusieurs événements différents.
XIV-E. S'échapper d'une fonction de rappel créée avec bind▲
Pour stopper le traitement effectué par votre fonction de rappel, utilisez l'instruction return pour la quitter. Cela n'empêchera pas le traitement des fonctions de rappel en attente ; pour stopper le traitement de toutes les fonctions de rappel liées à une combinaison widget/événement, on doit utiliser Tk::break au lieu de return, moins catégorique.
XIV-F. La méthode bindtags▲
La méthode bindtags permet de connaître tous les marqueurs associés à un widget :
print
join
(' '
, $bouton-
>
bindtags());
# affiche : Tk::Button . bouton . all
print
join
(' '
, $mw-
>
bindtags());
# affiche : MainWindow . all
Cette méthode nous indique l'ordre dans lequel le widget répondra aux fonctions de rappel des liaisons. La première réponse est toujours faite à la classe du widget : Tk::Button dans le premier exemple, et MainWindow dans le deuxième.
L'information renvoyée par bindtags n'est pas aussi intéressante que quand on lui passe des paramètres. Pour supprimer toutes les liaisons d'un widget, sauf celles qui s'appliquent à 'all' :
$bouton-
>
bindtags(['all'
]);
Désormais, le bouton ne répondra plus s'il est pressé, ni aux mouvements de la souris ni aux liaisons prédéfinies du widget. Comme l'explique la page web de Perl/Tk consacrée à bindtags, on peut inverser l'ordre dans lequel le widget répond aux événements à l'aide de l'instruction suivante :
$b-
>
bindtags(['all'
, $b-
>
toplevel, ref
($b
), $b
]);
Nous savons déjà que 'all' désigne toutes les liaisons associées au marqueur de liaison 'all'. L'appel $b-
>
toplevel renvoie la fenêtre dans laquelle $b
se trouve : MainWindow=HASH(0x9798d8). L'appel ref
(\$b
) donne le paquetage auquel appartient $b
: Tk::Button. Enfin, $b
désigne l'instance spécifique : Tk::Button=HASH(0x99c0cc).
HASH(0x99c0cc) est ce que vous verrez si vous affichez la valeur. Le nombre hexadécimal entre parenthèses n'est que l'emplacement en mémoire physique occupé par ce widget. HASH signifie qu'il est mémorisé sous la forme d'un hachage.
XIV-G. Utilisations de bind▲
L'utilisation de bind constitue un moyen puissant pour simplifier vos applications. Vous pouvez, par exemple, ajouter une liaison à une boîte de liste pour qu'elle affiche un menu lorsque vous y pressez le bouton droit de la souris. L'utilisation de bind avec les marqueurs de texte vous permet de créer des documents en « pseudoHTML ». L'ajout d'une liaison pour un double clic dans une boîte de liste rend possible une action particulière, déclenchée lorsqu'un élément de cette liste est « double cliqué ». Il y a tant d'autres façons d'utiliser cette méthode que je ne peux toutes les traiter ici. Assurez-vous cependant de ne pas créer une liaison inutilisable (un triple clic avec appui simultané de la touche 'Ctrl', par exemple, est un peu compliqué).