Introduction à Perl/Tk

Interfaces graphiques avec Perl


précédentsommairesuivant

XV. Widgets composites

Pour l'instant, nous avons présenté chaque widget de base séparément, mais la distribution de Perl/Tk contient également plusieurs widgets composites, qui sont des combinaisons de widgets dont le fonctionnement dépend de la façon dont ils sont combinés. En voici quelques exemples :

Optionmenu

  • Construit autour d'un bouton de menu, ce widget permet de choisir parmi une liste d'options située sur le menu.

LabEntry

  • Construit autour d'un cadre, ce widget est une zone de saisie comportant une étiquette configurable.

Dialog

  • Construit autour d'un widget de premier niveau, affiche un bitmap et un message permettant d'informer l'utilisateur.

Ces exemples ont été choisis parce qu'ils montrent bien ce que peuvent être les widgets composites. Ceux-ci peuvent être construits à partir d'un widget (un bouton de menu, dans notre exemple), d'un cadre contenant des widgets, ou d'un widget de premier niveau contenant d'autres widgets et formant une fenêtre complète.

Lorsque j'ai commencé à étudier les widgets composites, je pensais toujours passer à côté de quelque chose. Si je lisais le code du coin de l'œil, tout semblait être correct. Cependant, si je le consultais à tête reposée, j'étais alors complètement embrouillée et n'étais plus sûre de ce que ce code réalisait vraiment. Il est important de savoir qu'un certain nombre d'actions se passent en coulisses et que nous pourrons tirer parti de celles-ci lorsque nous créerons un widget composite.

Le but de ce chapitre n'est pas que vous sachiez écrire le widget composite le plus complexe qui soit, mais que vous compreniez comment ils fonctionnent, ce qui sera déjà suffisant : vous pourrez alors continuer à progresser lentement. La meilleure démarche consiste à lire entièrement ce chapitre, puis consulter les exemples livrés avec la distribution de Perl/Tk. Les widgets composites fournis avec le module Tk sont complets, ils ont été vérifiés par de nombreuses personnes différentes et fonctionneront lorsque vous les lancerez (habituellement, ils sont documentés avec le système documentaire de Perl, au format pod).

XV-A. Détour par un exemple

Je l'admets : j'aime les exemples. Ce sont des points de repère où l'on peut revenir lorsque l'on passe aux choses sérieuses. Comme il y a beaucoup de points épineux avec les widgets composites, nous commencerons par des exemples simples et progresserons à partir d'eux.

En consultant le code des widgets composites cités plus haut, on constate que celui du widget LabEntry est le plus court. Voici le contenu de LabEntry.pm(37) :

 
Sélectionnez
# Copyright (c) 1995-1997 Nick Ing-Simmons. Tous droits réservés.
# Ce programme est un logiciel libre ; vous pouvez le redistribuer
# et/ou le modifier selon les mêmes conditions que Perl lui-même.
# Classe LabeledEntry (zone de saisie étiquetée)
package Tk::LabEntry;
require Tk::Frame;
@ISA = qw(Tk::Frame);
Construct Tk::Widget 'LabEntry';

sub Populate {
    require Tk::Entry;

    # Constructeur d'un LabeledEntry (zone de saisie étiquetée)
    #
    my ( $cw, $args ) = @_;
    $cw->SUPER::Populate($args);

    # Advertised subwidgets: entry.
    my $e = $cw->Entry();
    $e->pack( -expand => 1, -fill => 'both' );
    $cw->Advertise( 'entry' => $e );
    $cw->ConfigSpecs( DEFAULT => [$e] );
    $cw->Delegates( DEFAULT => $e );
    $cw->AddScrollbars($e) if ( exists $args->{-scrollbars} );
}
1;

Il s'agit du code commenté et complet. La ligne @ISA = qw(Tk::Frame) indique qu'il s'agit d'un widget composite construit autour d'un cadre. Nous pouvons nous reporter à Programming Perl (O'Reilly, 1997)(38) pour savoir à quoi sert le tableau @ISA : « À l'intérieur de chaque package, un tableau spécial appelé @ISA indique à Perl où chercher une méthode s'il ne parvient pas à la trouver dans ce package. » On trouve, par la suite, de nombreux détails sur cette implantation de l'héritage, mais je ne voudrais pas abuser des mots qu'il emploie, uniquement pour expliquer un concept simple : pour que votre widget composite fonctionne, il faut insérer cette ligne dans votre code(39). Toutes les autres explications vont trop dans les détails(40).

L'étape suivante consiste à comprendre comment intervient le widget de saisie. On sait qu'il est créé, car lorsque l'on utilise une zone de saisie étiquetée, on constate qu'elle contient une zone de saisie. On remarquera que le fichier ne contient qu'une fonction, nommée Populate. Elle n'est jamais appelée directement, mais elle l'est. Les paramètres de cette fonction sont deux scalaires : une référence au cadre lui-même et une référence à un hachage contenant tous les couples de paramètres utilisés pour créer le widget. Voici un exemple de création d'un widget LabEntry :

 
Sélectionnez
$saisie_etiquettee = $mw->LabEntry(
    -textvariable => \$texte,
    -label        => "Entrez votre nom :",
    -labelPack    => [ -side => 'left' ]
)->pack();

Si vous examinez le code de LabEntry.pm, vous saurez qu'il crée une zone de saisie grâce à la ligne my $e = $cw->Entry(). Viennent ensuite plusieurs opérations lancées par Advertising, ConfigSpecs et Delegates. Pour l'instant, il nous suffit de dire que ces fonctions permettent à la zone de saisie de se comporter comme on le souhaite.

L'étiquette de la zone de saisie étiquetée est automatiquement créée, car on utilise l'option -label lors de sa création. Si l'on se souvient du chapitre 12, Cadres, on sait que l'utilisation de cette option avec un cadre crée automatiquement une étiquette. Ce qui rend ce widget composite simple est qu'il utilise l'étiquette déjà comprise dans un widget cadre.

XV-B. Emplacement des fichiers

Lorsque l'on construit ses propres widgets composites, on crée un fichier portant le même nom (à la casse près) que le widget et terminé par .pm. Si, par exemple, on créait un nouveau widget composite nommé BoutonListe, son code serait placé dans un fichier BoutonListe.pm.

Si les fichiers des widgets composites sont placés dans le même répertoire que ceux

de l'application, il suffit d'ajouter la ligne use BoutonListe après la ligne use Tk commençant le code utilisant le nouveau widget. Si les fichiers des widgets sont placés ailleurs, il faut ajouter la ligne suivante, avant toute instruction use ou require :

 
Sélectionnez
use lib ("repertoire_1", "repertoire_2");

afin d'indiquer les répertoires utilisés pour BoutonListe.pm.

XV-C. Création d'un widget composite à partir d'un cadre

La création d'un widget composite utilisant un cadre est légèrement différente de celle d'un widget utilisant un widget de premier niveau. Je donnerai un bref exemple pour chacun d'eux afin que vous puissiez vous rendre compte de ce que vous pouvez faire.

Si vous comptez créer un widget composite nommé MonWidget, les lignes suivantes doivent absolument être placées au début du code de celui-ci :

 
Sélectionnez
package MonWidget; 
require Tk::Frame; 
@ISA = qw(Tk::Frame);
 
Construct Tk::Widget 'MonWidget'; 

sub Populate 
{ 
    ... 
}

Il faut déclarer le nouveau widget comme étant son propre paquetage, d'où la ligne package MonWidget (si vos widgets se trouvent dans un sous-répertoire, utilisez SousRep::MonWidget). Les deux lignes suivantes sont faciles à comprendre : require Tk::Frame assure que l'on a chargé les informations nécessaires à la manipulation d'un cadre, et @ISA = qw(Tk::Frame) ajoute le widget Tk::Frame au tableau @ISA. La ligne qui suit appelle la méthode Construct de Tk::Widget (nous aurions également pu écrire Tk::Widget-> Construct("MonWidget")) en lui passant le nom de notre widget. Dans cet appel, on n'ajoute pas le nom du répertoire où se trouve le widget.

L'appel à Construct crée un constructeur pour le nouveau widget ; cela vous permettra de créer un nouveau MonWidget en appelant la méthode MonWidget :

 
Sélectionnez
$nouveau_widget = $mw->MonWidget(…);

Lorsque l'on crée un widget composite à partir d'un cadre, il faut utiliser la fonction Populate pour créer les sous-widgets et réaliser les autres configurations nécessaires.

XV-C-1. Populate détaillé

Il est préférable d'ajouter une instruction require pour chacun des autres widgets créés dans le widget composite. Ainsi, dans le code de LabEntry se trouve la ligne require Tk::Entry, car ce widget composite crée une zone de saisie.

Populate est appelée avec deux paramètres : une référence au widget composite et une référence à un hachage, que l'on affecte à deux variables afin de les utiliser plus tard :

 
Sélectionnez
my ($widget_comp, $params) = @_;

L'étape suivante consiste à gérer les options spécifiques qui s'appliquent au widget entier. Pour cela, il suffit de les extraire du hachage référencé par $params et de vérifier si une valeur a été définie :

 
Sélectionnez
$valeur_option = delete $params->"-option" ; 
if (defined $valeur_option) { 
  ...
}

Supposons que vous souhaitiez utiliser l'option -filename afin de placer la valeur associée à celle-ci dans la variable $nom_fic :

 
Sélectionnez
$nom_fic = delete $params->{"-filename"}; 
if (defined $nom_fic) { 
    # Ouvrir le fichier... 
}

Après avoir traité tous les paramètres souhaités, il est conseillé d'appeler SUPER:: Populate :

 
Sélectionnez
$widget_comp->SUPER::Populate($params);

Puis, vous pouvez créer les widgets que vous souhaitez dans le widget composite. Ainsi, si vous désirez créer une boîte de liste disposant de plusieurs boutons, il suffit d'appeler les méthodes appropriées pour chacun d'eux. Si vous souhaitez que l'utilisateur puisse manipuler ceux-ci, appelez la méthode Advertise sur chacun d'eux.

XV-C-2. Appel de Advertise

Advertise vous permet d'utiliser la méthode Subwidget pour désigner directement un des widgets inclus dans le composite, plus tard dans votre programme. Ainsi, lorsque le widget composite a été créé, on obtient une référence à la zone de saisie de la façon suivante :

 
Sélectionnez
$saisie_etiquette = $mw->LabEntry(
    -textvariable => \$texte,
    -label        => "Entrez votre nom :",
    -labelPack    => [ -side => 'left' ]
)->pack();
$saisie      = $saisie_etiquette->Subwidget("entry");
$texte_saisi = $saisie->get();

Lorsque l'on crée un widget composite, il ne faut pas oublier d'ajouter un appel à Advertise pour chacun des widgets. Si, par exemple, on crée une zone de saisie et un bouton, on devra faire deux appels :

 
Sélectionnez
$cw->Advertise('entry' => $saisie); 
$cw->Advertise('button' => $bouton);

XV-C-3. Appel de Delegates

Un widget composite est, essentiellement, une combinaison de deux ou trois widgets afin de n'en former qu'un seul. Lorsque l'on invoque des méthodes sur ce widget composite, il faut définir celles qui seront vraiment appelées à l'aide de la méthode Delegates, à laquelle il suffit de fournir une référence au widget que l'on souhaite utiliser :

 
Sélectionnez
$cw->Delegates(DEFAULT => $saisie);

Toutes les autres méthodes des widgets formant le widget composite devront être accédées via la méthode Subwidget.

Delegates permet également d'appeler une méthode d'un sous-widget :

 
Sélectionnez
$cw->Delegates(
    'insert' => $liste_defil, 
    'delete => $liste_defil,
    DEFAULT => $saisie
);

Dans cet exemple, si l'appel $composite->insert(…) a lieu, il sera pris en charge par $liste_defil->insert. Les méthodes déjà définies par le widget composite ne seront pas déléguées ; si celui-ci utilise sa propre méthode insert, il faudra passer manuellement le contrôle au sous-widget.

XV-C-4. Appel de ConfigSpecs

Lorsque l'on crée un widget composite, on veut pouvoir appeler la méthode configure sur celui-ci. Pour ce faire, on utilise ConfigSpecs, qui s'appelle de trois façons différentes : pour créer une option et pour la gérer, pour faire d'une option un synonyme d'une autre, ou pour préciser un widget par défaut qui prendra en charge tous les appels à configure.

Un widget composite simple, comme LabEntry, n'appellera ConfigSpecs que pour indiquer le widget par défaut qui sera chargé de toute la configuration :

 
Sélectionnez
$cw->ConfigSpecs(DEFAULT => [$saisie]);

On indique DEFAULT comme premier paramètre, puis on passe une liste anonyme contenant le widget à utiliser. Cet appel permettra de configurer la zone de saisie à chaque appel de configure avec la référence du widget composite.

XV-C-4-a. Création d'un synonyme

ConfigSpecs permet de créer un synonyme pour une option, pour disposer d'une version courte et longue de celle-ci, par exemple. Si l'on souhaite que l'option -file ait exactement la même signification que -filename, il suffit d'écrire :

 
Sélectionnez
$cw->ConfigSpecs('-file' => '-filename');

On indique d'abord le synonyme, puis l'option à laquelle il correspond.

XV-C-4-b. Définition d'options

Pour définir une nouvelle option et lui associer une action, on écrira :

 
Sélectionnez
$cw->ConfigSpecs(-nouvelle_option => [ <action>,
                                       "nouvelle_Option",
                                       "Nouvelle_Option",
                                       <valeur_defaut> ]);

On précise d'abord la nouvelle option pour laquelle on veut créer une action, puis on utilise une liste anonyme de quatre éléments. Le premier est l'action à réaliser et doit être "DESCENDANTS", "ADVERTISED", "SELF", "CHILDREN", "PASSIVE", "METHOD", "CALLBACK", ou une référence à un sous-widget. Les deux éléments suivants concernent la base de données des options et sont, éventuellement, vides. Le dernier élément est la valeur par défaut que prendra cette option : undef, "" ou n'importe quelle valeur que vous souhaitez affecter à cette option lorsqu'elle n'est pas spécifiée explicitement.

Les valeurs possibles de l'action sont définies ainsi :

DESCENDANTS

  • La configuration de cette option sera appliquée récursivement à tous les descendants du widget composite.

ADVERTISED

  • La configuration sera appliquée à tous les sous-widgets avertis par Advertise.

SELF

  • La configuration sera appliquée au widget de base (ici un cadre, mais cela peut-être un autre widget composite).

CHILDREN

  • La configuration sera appliquée à tous les fils.

PASSIVE

  • La valeur sera stockée dans $params. C'est de cette façon que l'on utilisera ConfigSpecs pour toutes les options utilisables au moment de la création ou par les méthodes personnalisées du widget composite.

METHOD

  • Appellera la méthode portant le même nom que l'option. Si, par exemple, on utilise $cw->ConfigSpecs(-nouvelle_option => ["METHOD", "","", undef]) et que -nouvelle_option est utilisée, cela invoquera la méthode nouvelle_option (qui doit être définie quelque part dans le fichier). Cette action fonctionne lorsqu'aucune des autres ne peut être utilisée pour définir une option.

CALLBACK

  • Si la nouvelle option est configurée ou utilisée à la création du widget, cette action invoquera une méthode du widget composite.

     
    Sélectionnez
    $cw->ConfigSpecs(-mon_opt => ["CALLBACK", "ma_Methode",
                                  "Ma_Methode", undef]) ;
  • par exemple, appellera la méthode ma_Methode lorsque l'option -mon_opt sera utilisée (à titre d'exemple, étudiez également les appels à ConfigSpecs dans le fichier BrowseEntry.pm ci-dessous).

$reference

  • Force un appel à $reference->configure(-option => valeur). Habituellement, $reference est un sous-widget du widget composite (une zone de saisie, par exemple).

XV-C-4-c. Exemple d'appel à ConfigSpecs

Voici l'appel à ConfigSpecs dans la version Tk8.0 de BrowseEntry.pm :

 
Sélectionnez
$w->ConfigSpecs(
-listwidth  => [qw/PASSIVE listWidth ListWidth/, undef],
-listcmd    => [qw/CALLBACK listCmd ListCmd/, undef],
-browsecmd  => [qw/CALLBACK browseCmd BrowseCmd/, undef],
-choices    => [qw/METHOD choices Choices/, undef],
-state      => [qw/METHOD state State normal/],
-arrowimage => [{-image => $b}, qw/arrowImage ArrowImage/, undef],
-variable   => "-textvariable",
DEFAULT     => [$e] );

Comme vous pouvez le constater, on utilise ici plusieurs paires d'informations. Il n'y a qu'une option PASSIVE, deux CALLBACK, et deux METHOD. Tous les autres appels à configure seront redirigés vers le sous-widget $e. Examinez le code complet afin de savoir ce que font les méthodes utilisées dans ConfigSpecs.

XV-C-5. Résumé sur les widgets construits à partir d'un cadre

Voici un pseudo-code résumant la création d'un widget composite à partir d'un cadre :

 
Sélectionnez
$package NouveauWidget;
@ISA = qw(Tk::Frame);

Tk::Widget->Construct('NouveauWidget');

sub Populate()
{
    my ($cw, $params) = @_;
    
    # Ne traite que les options de création seulement
    my $valeur = delete $params->{-option};
    if (defined $valeur) {}

    # Création de tous les sous-widgets souhaités…
    $widget = $cw->Widget(…);

    $cw->Delegates();
    $cw->ConfigSpecs( … );
}

sub mon_option {}

1;

XV-D. Widgets composites construits à partir d'un widget de premier niveau

Il n'existe qu'une seule petite différence entre un widget composite construit à partir d'un cadre et un construit à partir d'un widget de premier niveau. Si l'on souhaite utiliser la méthode new pour créer la fenêtre, il faut définir InitObject au lieu de Populate. Cependant, la plupart des widgets composites fournis avec la distribution de Tk ne le font pas. Examinez les fichiers ColorEditor.pm et DialogBox.pm pour avoir des exemples de création de tels widgets composites. Toutes les règles d'utilisation de ConfigSpecs restent identiques.


précédentsommairesuivant
NDT Nous avons traduit les commentaires du code original.
NDT Traduit en français sous le titre Programmation en Perl (O'Reilly, 1997).
Il n'est pas nécessaire d'hériter d'un cadre, mais beaucoup le font. Cela simplifie la tâche, car l'on dispose alors d'un conteneur automatique pour le widget composite.
Ce souci du détail peut aller jusqu'à suivre pas à pas le code Perl/Tk afin de savoir quelle méthode a été appelée, et d'où. Nous n'avons pas besoin d'une telle précision, ici.

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

  

Licence Creative Commons
Le contenu de cet article est rédigé par Nancy Walsh et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Pas de Modification 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2018 Developpez.com.