Le système X window permet de manipuler des fenêtres [windows] sur les écrans [displays].
Le dessin qui suit représente un écran et ses fenêtres.
On peut avoir un ou plusieurs écrans [screens] sur une même station de travail [display]. Chaque écran montre un ensemble de fenêtres rectangulaires qui ont entre elle une hiérarchie :
Dans X, les coordonnées sont mesurées en pixels par rapport à la fenêtre parent. La coordonnée (0,0) dans une fenêtre est son point supérieur gauche (x augmente vers la droite et y vers le bas).
X permet de créer et détruire des fenêtres, de les déplacer, de les redimensionner à l'intérieur de leur parent respectif et d'y dessiner du texte, des lignes et des bitmaps. Si deux fenêtres se recouvrent, l'une est supposée être au-dessus de l'autre. X montre tout ce qui concerne la partie supérieure d'une fenêtre et seulement la partie non cachée du contenu des autres fenêtres. De plus, la dimension d'une fenêtre parent limite ce qui peut être vu de la fenêtre enfant.
Le serveur X interprète les événements hardware (souris et touches de clavier) en fonction de sa configuration et leur attribue une sémantique [X events] . Par exemple, le serveur X transforme le fait d'avoir appuyé sur le bouton gauche de la souris en <ButtonPress-1>. De même, il gère d'autres types d'événements qui ne sont pas liés à des événements hardware, comme le redimensionnement d'une fenêtre, sa destruction, etc. Il utilise ces événements pour informer les applications intéressées qui pourront alors prendre l'action appropriée.
X n'impose ni une apparence (ou aspect particulier) pour une fenêtre, ni la façon dont l'application doit réagir à un événement donné. X ne fournit pas de support pour un aspect ou un design particulier [look and feel] et ne fournit aucun bouton ou menu pour le contrôle de l'application. C'est le rôle des boîtes à outils [toolkit] de fournir tout cela. Tk en est une au même titre que Xm (de OSF/Motif), AUIS (Andrew Toolkit), etc.
Afin de gérer les toplevels de façon uniforme, on va faire appel à une window manager [wm], application particulière qui dialogue avec le serveur X et les autres applications, gérant:
Il existe un protocole qui définit les interactions entre les applications et le window manager: ICCCM [Inter-Client Communication Convention Manual). Tk respecte ce protocole et devrait donc être compatible avec tous les window managers qui le respectent. Les window managers courants sont mwm, twm, tvtwm, olwm, olvwm et ctwm.
Dans un environnement X, on a donc 3 processus en coopération:
La partie graphique d'une application utilisateur est communément appelée GUI [Graphical User Interface]. Les différentes fenêtres de l'applications sont appelées widgets (raccourci de window gadget). On utilise souvent indifféremment widget et fenêtre [window]. Ces widgets sont aussi ce qu'on a appelé plus haut les internal windows mais aussi les éventuelles différentes toplevel windows d'une même application.
Les widgets sont donc les fenêtres qui correspondent à des boutons, menus, barres de défilement, etc.
Le système X a une structure hiérarchique pour les widgets (au même titre que les fichiers dans un file system), chaque fenêtre pouvant contenir des sous-fenêtres et ainsi de suite. Cette structure hiérarchique permet, comme nous le verrons, des actions à différents niveaux.
La boîte à outils Tk fournit un ensemble de commandes Tcl pour la création et la manipulation des widgets.
La structure hiérarchique des widgets est reflétée dans la façon utilisée par Tk pour nommer l'enchaînement des widgets, le point servant de séparateur et ayant donc un sens de filiation. Par exemple,
.w.frame.button1 .w.frame.button2 .w.frame.canvas .w.label
signifie que dans la fenêtre .w on a mis un label et un frame qui contient 2 boutons et une toile de fond.
Tk fournit l'ensemble des widgets suivants:
Chacun de ces noms est aussi celui de la commande qui crée le widget correspondant. Ces mêmes noms, avec la première lettre en majuscule, se réfèrent à la classe du widget correspondant.
Chaque widget a plusieurs attributs dont on peu modifier la valeur. Tous les attributs ont des valeurs par défaut, ce qui est bien confortable pour éviter d'écrire beaucoup de code. Les attributs dépendent du type de widget mais un grand nombre d'entre eux sont communs. Tous ces attributs sont extrêmement bien résumés dans le Tcl/Tk Reference Guide.
Le programme wish, fourni avec la distribution de Tk, ouvre par défaut une fenêtre toplevel dont le nom hiérarchique est ".".
La construction d'un widget utilisable comprend deux étapes: sa création et son positionnement. Ces deux actions vont déterminer l'aspect du widget au sein de la fenêtre principale de l'application. Depuis la version Tk4, les widgets de Tk ont un look Motif.
La création d'un widget se fait en appelant la commande qui porte le nom du widget (voir supra), suivie du nom hiérarchique du widget, puis d'options pour les attributs et leur valeurs, toujours donnés par paires, suivant le schéma : -attribut valeur.
Par exemple, la commande suivante:
button .hello -text "Hello" -command {puts stdout "Hello World!"}
Aussi longtemps que le geometry manager n'a pas pris connaissance de l'existence du widget, il n'apparaîtra pas à l'écran.
Tk possède plusieurs geometry managers mais nous nous limiterons au plus utilisé, le packer. C'est à lui que nous allons dire où le widget va être placé dans la fenêtre parent et quels seront ses liens géométriques avec cette fenêtre, notamment lors du redimensionnement. De même que pour la création, les options sont toujours données par apires, suivant le shéma: -attribut valeur.
Suivant notre exemple, il suffira de passer la commande suivante au packer (on a choisi ici toutes les options par défaut):
pack .hello
Tk utilise un système basé objet pour créer et nommer les widgets. A chaque classe d'objet est associée une commande qui crée des instances pour cette classe d'objets. Aussitôt qu'un widget est créé, une nouvelle commande Tcl est créée, qui porte le nom hiérarchique du widget et qui agit sur les instances de ce widgets. Les instances possibles dépendent donc de la classe du widget et sont résumées dans le Reference Guide mentionné.
Par exemple, on pourra maintenant agir sur le bouton .hello qu'on vient de créer avec la nouvelle commande .hello et certaines options.
.hello flash .hello invoke .hello configure -background blue
Ainsi, tous les attributs qui ont pu être donnés, y compris par défaut, lors de la création d'un widget peuvent être modifiés par la suite à l'aide de la commande:
widget_name configure -attribut value
L'option configure a aussi pour but de pouvoir renseigner sur la valeur d'un attribut:
.hello configure -background => -background background Background #d9d9d9 blue
Depuis la version TK4, on dispose aussi de l'option cget(qui équivaut à lindex [widget_name configure -attribute] 4):
.hello cget -background => blue
L'option configure permet enfin de se renseigner sur l'ensemble des attributs possibles du widgets considéré (la liste peut être longue!):
.hello configure => {-activebackground activeBackground Foreground #ececec #ececec} {-activeforeground activeFore ground Background Black Black} {-anchor anchor Anchor center center} {-background background Back ground #d9d9d9 blue} {-bd borderWidth} {-bg background} {-bitmap bitmap Bitmap {} {}} {-borderwidth borderWidth BorderWidth 2 2} {-command command Command {} {puts stdout "Hello World!"}} {-cursor cursor Cursor {} {}} {-disabledforeground disabledForeground DisabledForeground #a3a3a3 #a3a3a3} {-fg foreground} {-font font Font -Adobe-Helvetica-Bold-R-Normal--*-120-*-*-*-*-*-* fixed} {-foreground fore ground Foreground Black Black} {-height height Height 0 0} {-highlightbackground highlightBackground HighlightBackground #d9d9d9 #d9d9d9} {-highlightcolor highlightColor HighlightColor Black Black} {-high lightthickness highlightThickness HighlightThickness 2 2} {-image image Image {} {}} {-justify justify Justify center center} {-padx padX Pad 3m 11} {-pady padY Pad 1m 4} {-relief relief Relief raised raised} {-state state State normal normal} {-takefocus takeFocus TakeFocus {} {}} {-text text Text {} Hello} {-textvariable textVariable Variable {} {}} {-underline underline Underline -1 -1} {-width width Width 0 0} {-wraplength wra pLength WrapLength 0 0}
Pour communiquer avec le window manager, la liste des options possibles est longue, toujours documentée dans le Reference Guide. Contentons nous de mentionner les plus fréquemment utilisées:
Un exemple:
wm title . "Premier essai" wm minsize . 100 50 wm maxsize . 200 100 wm iconify .
Les attributs du packer les plus fréquemment utilisés sont:
Si vous ne vous souvenez pas du nom des attributs ou d'une valeur possible, vous verrez qu'en tapant interactivement dans wish, en cas d'erreur, les attributs possibles ou les valeurs admises vous seront proposées.
La commande pack info widget_name permet de connaître les conditions de packing d'un widget:
pack info .hello => -in . -anchor center -expand 0 -fill none -ipadx 0 -ipady 0 -padx 0 -pady 0 -side top
Pour bien comprendre la stratégie de positionnement du packer (l'option -side), il faut raisonner en rectangle disponible. A tout moment, seul un rectangle est la surface restante sur laquelle on va pouvoir placer un nouveau widget. Si on place un widget en l'attachant par le haut (-side top), le rectangle disponible pour la suite sera vers le bas. Si on ajoute un widget dans ce rectangle en l'attachant par la gauche, le rectangle disponible sera maintenant sous le premier widget (qui réserve toute la largeur) et à droite du second widget (qui réserve toute la hauteur restant sous le premier widget). Et ainsi de suite. On se rendra ainsi compte qu'il n'est pas possible de mettre 4 boutons en carré sans passer par des frames intermédiaires, mais qu'une spirale serait possible... On peut se construire des formes hybrides des types suivants (j'utilise une notation ou le numéro indique l'ordre dans lequel le packer a été invoqué et un nom pour dire quel valeur a été donnée à l'option side; l'option -fill both a toujours été utilisée):
Pour éviter des résultats inattendus, il est conseillé d'utiliser des frames différents dès qu'on veut grouper des widgets qui ne sont plus en ligne ou en colonne.
Les événements X (X events), tels que enfoncer/relâcher une touche du clavier ou de la souris, faire entrer ou sortir le curseur d'une fenêtre, changer la dimension d'une fenêtre toplevel, iconifier, déiconifier, détruire un widget, etc, peuvent être reliés aux commandes Tcl: c'est ce qu'on appelle le binding.
Les événements X les plus souvent considérés sont
Une liste plus complète se trouve dans le Reference Guide.
La commande bind peut soit donner des informations sur des bindings existants, soit en définir d'autres. Elle peut être appliquée à un widget ou une classe de widgets. Une classe de widgets porte le même nom que la commande pour créer ce type de widget, sauf que la première lettre est une majuscule, par exemple Button. Si le widget correspond à une toplevel window, le binding s'applique à tous les widgets de cette fenêtre. On peut aussi utiliser all qui correspond à tous les widgets.
Pour communiquer entre X et Tcl, la syntaxe suivante est utilisée dans la commande bind: un % suivi d'une lettre est remplacé par sa valeur avant l'évaluation de la commande par Tcl. Par exemple, %W correspond au nom hiérarchique du widget en cours et %y à la coordonnée y relative de l'événement par rapport au widget. La liste complète se trouve toujours dans le Reference Guide.
La syntaxe générale pour décrire un événement X est
<modifier-modifier-type-detail>
par exemple, <Button-1>, <Shift-Key-a>
Si un détail est donné pour l'événement Key, des abréviations sont possibles et les 4 lignes suivantes sont équivalentes:
<KeyPress r> <Key r> <r> r
Pour les touches de claviers, le détail est aussi connu sous le nom de keysym (terme technique de X).
De même pour l'événement Button, les 3 lignes suivantes sont équivalentes:
<ButtonPress-1> <Button-1> <1>
On peut mentionner ici que <1> correspond à la souris et 1 au clavier. Mais ces raccourcis extrêmes ne simplifient pas la relecture!
A titre d'exercice, faites les quelques essais suivants
bind .hello <Enter> {puts stdout "Entered %W at %x %y"} bind .hello <Leave> {puts stdout "Left %W at %x %y"}
et regardez ce qui se passe lorsque le curseur passe devant le bouton .hello puis s'en va.
L'attribution des ressources X pour un widget donné est faite suivant la hiérarchie:
La base de donnée des ressources X de Tk (Xresource database) est maintenue en mémoire par Tk. Il est possible d'y ajouter des ressources de 3 manières:
Pour spécifier une ressource X, la clef est la structure hiérarchique du widget
Supposons que l'on veuille que par défaut la couleur de fond soit rose:
option add *background pink ;# peu importe le nom de l'application option add Essai*background pink ;# si l'application s'appelle Essai, que ce soit pas tk appname ou le nom de l'exécutable
Si on veut que tous les widgets qui terminent par exit soient jaune:
option add *exit.background yellow ;# peu importe le nom de l'application option add Essai*exit.background yellow ;# si l'application s'appelle Essai
Si on veut que tous les widgets qui appartiennent au frame .top soient vert:
option add *top*background green ;# peu importe le nom de l'application option add Essai.top*background green # si l'application s'appelle Essai