"Tk - la bibliothèque graphique de Tcl"
"Auteur de la version initiale : Arnaud LAPREVOTE"
Tk.
Tk est le compagnon graphique de tcl. Il contient en particulier les widgets suivants :
Ce qui n'est pas directement dans Tk peut être trouvé directement dans des extensions supplémentaires, telles que Blt (widget de tracé de courbes), les Bwidgets, HTML. On commence aussi à trouver des méga-widgets directement écrits en tk (combobox, notepad, arbre).
Premier pas en Tk.
Pour créer une interface, on :
Tk gère intégralement la boucle de suivi des événements. On ne s'en préoccupe pas.
label .nouveau_label -text "Je dis juste bonjour !"
button .say_ok -text "OK ?"
pack .nouveau_label .say_ok -side top
Ou encore plus simplement :
label .nouveau_label -text "Je dis juste bonjour !"
pack .nouveau_label .say_ok -side top
Création d'un widget.
Le nom d'un widget est toujours :
.xxxx.yyyy.zzzz
Dans ce cas on crée le widget zzzz qui se trouve dans le widget yyyy qui se trouve dans le widget xxxx. xxxx pourrait parfaitement être une nouvelle fenêtre ou un widget.
toplevel .nouvelle_fenetre
label .nouvelle_fenetre.label -text "Je dis juste bonjour !"
pack .nouvelle_fenetre.label .nouvelle_fenetre.say_ok -side top
Lors de la création d'un widget, des paramètres sont initialisés. Par exemple, dans :
label .exemple_label -text "Je dis juste bonjour !"
Lors de la création du label .exemple_label, on vient initialiser son paramètre -text à la valeur "Je dis juste bonjour !".
Il est constamment possible de changer un paramètre d'un widget avec la commande configure.
Les valeurs initialisables pour un widget varient d'un widget à l'autre. Cependant il y a un certain nombre de paramètres qui sont systématiquement initialisables pour les widgets. La liste de ces options par défaut est disponible :
Modification des paramètres d'un widget existant.
Reprenons notre petit exemple :
2 solutions existent ici. Si vous tapez directement dans wish, alors
tapez :
Si vous souhaitez plutôt utiliser un programme dans un fichier, alors tapez :
Comme vous pouvez le constater configure a permis de changer la valeur d'un paramètre d'un widget. Si vous souhaitez connaître la liste de tous les paramètres d'un widget, il vous suffit de taper :
.nom_du_widget configure
Si vous souhaitez connaître la valeur d'un paramètre donné, vous pouvez utiliser la commande cget :
"Remarque"
Dis tonton Arnaud, tu nous as bassiné avec le fait qu'une ligne tcl consistait toujours en :
Or je constate ici que l'on a :
Il y a tromperie sur la marchandise, remboursez !!!!!
"Réponse de tonton Arnaud"
Que nenni !!! Lorsque l'on crée un widget, une commande portant le nom du widget est créée et configure est un argument de cette commande comme un autre.
Mort d'un widget.
Tout à une fin. On peut détruire un widget avec la commande :
À vous Cognaq-Jay.
Créez une application contenant un label, un bouton "Quitter", un bouton "Changer". Au départ, sur le label on lit :
Quand on clique sur le bouton "Changer", le label devient "Vous venez de cliquer sur le bouton Changer", si on re-clique, on revient à "Je dis juste bonjour" et ainsi de suite. Si on clique sur le bouton "Quitter", l'application se termine.
Le bouton "Quitter" a un fond rouge, quand le curseur passe dessus se fond devient rose (#ff8080).
Dans une seconde version, vous garderez cette même application, ajouterez les boutons flat, groove, sunken, ridge. Lors de l'appui sur ces boutons, la propriété -relief du label changera et prendra la valeur flat, groove, sunken, ridge.
Je paye personnellement un coca-cola (virtuel) à celui qui réussira à faire ce dernier exercice avec une boucle sur la liste [list flat groove sunken ridge].
Placement des widgets.
3 modes de placement existent :
Le packer :.
Les widgets sont placés en colonnes ou lignes. Les colonnes ou les lignes sont créées les unes après les autres. Pour obtenir des placements par groupe de widget on utilise des frames qui vont contenir des widgets en horizontal ou en vertical.
Le grider :.
Les widgets sont placés sur une grille (comme dans un tableau HTML pour ceux qui connaissent). La taille des colonnes est calculée automatiquement.
Le placer permet de placer les widgets en donnant directement leurs coordonnées. Je ne l'ai jamais utilisé.
Le packer dans le détail - les options de pack.
On peut utiliser la commande pack avec les options :
On choisit l'orientation (horizontal ou vertical) ainsi que l'endroit (de gauche à droite, de droite à gauche, de haut en bas, de bas en haut) dans lequel les widget sont placés.
Défini si les widgets "packés" doivent remplir complètement l'espace disponible ou non en horizontal (x) ou en vertical (y). Par défaut l'option est none.
Lors du redimensionnement de la fenêtre, les widgets "packés" avec cette commande suivront l'expansion de la taille de la fenêtre.
Le nombre de pixels à droite et à gauche du widget courant sera ajouté en horizontal (padx) ou en vertical (pady) autour du widget.
Le nombre de pixels nécessaire est ajouté à l'intérieur du widget à droite et à gauche (-ipady) ou au dessus et en dessous (-ipadx).
On peut faire "oublier" un widget au "packer" en utilisant l'option forget.
Enfin, pour pouvoir bouger un widget dans tous les sens, il faut qu'il soit défini avant un autre.
Exercice.
Votre mission si vous l'acceptez consiste à obtenir successivement les looks suivant avec le "packer" et 2 labels :
img/tk_ex1.gif
img/tk_ex2.gif
img/tk_ex3.gif
img/tk_ex4.gif
img/tk_ex5.gif
img/tk_ex6.gif
Pour ce faire, vous allez lancer wish et vous allez créer les 2 widgets suivants en ligne de commande :
Afin que votre cerveau ne s’autodétruise pas au bout de 15 minutes, voici quelques conseils :
Bien sûr, si votre mission échoue, nous nierons tout lien avec vous et peut-être même vous livrerons au côté obscure de la force dans la salle d'à côté.
Les frames.
Les frames sont comme le sel en cuisine, il en faut des pincées, cela reste invisible, mais sans cela le plat est infect.
Imaginons que nous souhaitions avoir l'interface suivante :
Le code correspondant est le suivant :
Comme vous le voyez, les frames permettent de regrouper des ensembles de widget par ligne horizontale et verticale. Grâce à .frame1.frame2 on a groupé les widgets .frame1.frame2.lab1 et .frame1.frame2.ent1 horizontalement, puis dans .frame1 on a ajouté .ent2 et on les a empilés verticalement. Enfin on a placé .but1 et .frame2 cote à cote.
En jouant judicieusement sur les options -expand et -fill, on arrive à obtenir un comportement sophistiqué de l'application lors des redimensionnements.
Le gridder.
Dans certains cas, le packer est vraiment assez inadapté et oblige à utiliser un nombre incroyable de frame. Des extensions à tcl sont alors apparus qui intégraient des algorithmes de placement basés sur une grille de cases.
Une grille est définie et l'on choisi le ou les cases sur lesquels un widget (ou un ensemble de widget dans une frame) vont être placés. Le gridder est plus verbeux que le packer à l'écriture parce qu'il faut une ligne pour chaque widget. Par contre, il est trivial de générer le code automatiquement.
Les options de grid (au placement ou lors d'un configure) sont les suivantes :
Identification de la case où le widget sera placé
Le widget est placé sur une ou plusieurs colonnes, sur une ou plusieurs lignes.
Strictement identique à ces valeurs dans le "packer".
La manière dont le widget est "collé" aux bords. Si l'on souhaite qu'en cas de redimensionnement le widget voit sa taille augmenté, on utilise -sticky ew ou ns ou ewns.
Il est possible de fixer les attributs d'une colonne ou d'une ligne grâce à 
Assez étrangement, les numéros de lignes et de colonnes commencent à 1 et non pas à 0.
Événements disponibles.
On peut associer à chaque événement d'un widget des actions. Cela se fait avec la commande bind.
La syntaxe de bind est :
La syntaxe des événements est un peu particulière. En première approximation, c'est :
<type_d_evenement-evenement>
"type_d_evenement" peut être :
Button et Key sont d'utilisation très courantes. Il m'est arrivé d'utiliser aussi Enter et Leave.
"evenement" peut être :
En ce qui concerne les touches, attention, il peut y avoir des différences de dénomination entre OS. Utilisez l'exemple plus haut pour connaître le code.
Enfin devant tout cela (type_d_evenement-evenement), il peut y avoir un modificateur. Par exemple, on peut demander à ce que la touche Ctrl soit appuyé, Alt ou Shift, ou vouloir un double clique, ou que le bouton 1 de la souris soit activé. Les autres événements me paraissent moins utiles.
La liste est la suivante :
Enfin, dans le script associé au binding, on peut récupérer diverses informations sur l'événement grâce à des chaines du type %x :
La page de man de bind donne toutes les indications.
Les boutons.
Pour avoir toutes les options, n'hésitez pas à vous reporter à la page de man. Les options me semblant crucialement utiles sont :
Les labels.
Les labels sont les widgets contenant un texte non modifiable interactivement. Évidemment le texte est modifiable via programmation.
Le widget "message" est un label multiligne.
Les cases à cocher.
Une case à cocher permet de savoir si une option est ou non sélectionnée. Cela s'utilise de la manière suivante :
Les options -onvalue -offvalue permettent de forcer une valeur pour la variable selon que la case est ou non cochée.
Les boutons radio.
Les boutons radio sont des cases à cocher dont une seule peut-être active à la fois.
Les menus.
Pour créer un menu avec des listes déroulantes, on utilise simplement une frame (-relief raised), avec à l'intérieur des widget "menubutton". À chaque menubutton on associe un widget fils menu. Ce menu est composé d'entrée de type command (avec option -label, -command), de type radiobutton (option -label, -command, -variable et -value) de type checkbutton (-label -command -variable -onvalue -offvalue) ou de type cascade (option -label -cascade) enchaînant vers un autre menu.
L'option -accelerator permet d'associer une touche d'accélération pour activer l'entrée de menu correspondante. 
Il existe aussi des menus d'options : tk_optionMenu
Ainsi que des menus pop-up.
Les images.
Comme nous l'avons vu précédemment, il est possible d'associer un bitmap (noir et blanc) ou une photo à un widget (bouton, label). 
La procédure est simple :
Prenons le cas de la photo :
Il ne reste plus qu'à associer la photo à un label
Il est évidemment possible de changer de photo par exemple :
Vous pouvez vous reporter au man de la commande image pour voir les commandes disponibles.
Interface graphique contre grosse fatigue.
Nous allons refaire un joyeux exercice, mais cette fois-ci vous allez pouvoir utiliser un builder graphique d'application. Il en existe 3 pour tk.
Le plus ancien est SpecTcl (http://wuarchive.wustl.edu/languages/tcl/SpecTcl/). À sa création par les laboratoires de Sun, s'était un produit commercial. Ils l'ont rapidement mis dans le domaine public et abandonné le développement. Le résultat est un excellent produit stable et facile à utiliser mais qui n'évolue plus (et c'est bien dommage). C'est ce que j'ai utilisé au départ.
L'inconvénient de specTcl est qu'il faut très régulièrement retoucher le code généré. À partir du moment où on touche le code généré par un builder, on ne peut plus du tout continuer à utiliser l'interface graphique et cela perd de son intérêt.
"Nouveau : le développement de specTcl a repris. Il se trouve maintenant sur sourceforge.net."
tkBuilder que l'on peut trouver sur http://scp.on.ca/sawpit, est un outil moins graphique que specTcl, mais très pratique pour le programmeur. C'est un excellent support graphique à la programmation. Grâce à la possibilité de rajouter du code manuel un peu partout, il est très facile de ne pas avoir à modifier le code.
Enfin, Visual Tcl est un logiciel constamment prometteur, certainement le plus ambitieux des 3 (le plus proche de VB ?). On le trouve sur :
 http://vtcl.sourceforge.com
Malheureusement, à chaque fois que je l'utilise, il me claque entre les mains. Je vous propose malgré tout de voir si la dernière version (1.6alpha) tient ou non la route.
Je vous propose de charger l'un des 3 et de faire les exercices suivants avec une interface de construction. À charge pour vous de lire le manuel.
Vous n'allez pas rigoler.
Votre but est de créer l'application suivante :
On donne en haut le nom d'un fichier gif ou jpeg et quand on appuie sur le bouton Ouvrir cette image est affichée dans le label central. Bien sûr, on peut répéter l'opération d'ouverture à chaque fois.
On peut choisir le nom de fichier ou en le tapant dans l'entrée de texte du haut ou en cliquant sur le bouton à côté de l'entrée de texte qui permet d'appeler le widget tk_getOpenFile. On utilise cette fonction de la manière suivante :
tk_getOpenFile est un widget standard de tcl ayant un manuel. 
J'ai utilisé tkBuilder pour créer cette application :
img/tkbuilder.gif
mais n'hésitez pas à utiliser un autre builder.
Bibliographie.
"Graphical Applications with Tcl&Tk" de Eric F. Johnson, M&T Books, ISBN 1-55851-471-6 : le livre avec lequel j'ai appris le tcl/tk. Pas complet, mais très pédagogique et agréable à lire.