Boîtes de dialogue, MessageDialogs


Dernière modification : le 8/12/96

Table des matières :

Introduction

Généralités

Les widgets de type Dialog permettent le dialogue entre l'application et l'utilisateur, ce sont des "boîtes de dialogue". Elles doivent se suffir à elles-mêmes et leur existence est généralement éphémère. Avec les TopLevel, se sont les "autres" fenêtres manipulables par le Window Manager.

Les différents types de Dialog widgets

Pas de classe Dialog en Motif

En effet, lorsqu'on parle de dialog widgets, de boîtes de dialogue, on fait un abus de langage. Nous verrons que les widgets Motif standard héritent de deux classes : MessageBox et SelectionBox. Ces deux classes ne correspondnt pas à des widgets de type Shell comme les top-levels, par conséquent ils ne sont pas manipulables directement par le window manager.

En réalité, les fonctions de création de widgets standards fournies par Motif créent automatiquement des MessageBox ou des SelectionBox dans des DialogShell. Donc, sans le savoir, lorsque vous créez par exemple un InformationDialog à l'aide d'une fonction comme XmCreateInformationDialog(), sans le savoir, vous venez de créer un MessageBox dans un DialogShell. Motif positionne même sans vous le dire les ressources les plus importantes de ces deux widgets.

Attention : les fonctions de création Motif retournent un pointeur sur le widget qui se trouve à l'intérieur du DialogShell, pas sur le DialogShell lui-même !

Les widgets de la classe MessageDialog

Description des six types de MessageDialogs

Les widgets de ce type sont les plus simples, mais ils sont très utiles. Ils jouent surtout un rôle informatif ou bien permettent de poser une question très simple ("Etes-vous sûr de vouloir quitter l'application ?"). Ils sont destinés à rester ouverts le temps d'un acquiescement.

Tous héritent de la classe MessageBox.

MessageDialog = DialogShell + MessageBox + widgets à l'intérieur du MessageBox

Les MessageDialogs contiennent un message, une icône (presque toujours) et trois boutons poussoirs. La plupart du temps ces boutons ont pour label par défaut Ok, Cancel et Help, sauf dans le cas des QuestionDialog ou les boutons sont Yes, No et Help. Ils sont cependant considérés comme étant un seul et unique widget par Motif. Pour changer le label du bouton Ok, nous positionnerons des ressources du dialog, pas du bouton !

Rôle des MessageDialogs : afficher de l'information, un message d'erreur, des warnings, signaler qu'une opération longue est en cours d'exécution, etc....

Ainsi, pour chacun de ces usages MOTIF propose un type de widget différent :

TemplateDialog
Affiche un message sans icône particulière.
ErrorDialog
Affichage de messages d'erreur. Un panneau ressemblant à un stationnement interdit (???) illustre le propos !
InformationDialog

Ce widget ressemble énormément à celui de type WarningDialog. La seule différence est le dessin de l'icône qui est affiché juste avant le message (Un grand "i" au lieu d'un point d'exclamation).

En général les InformationDialog sont utilisés pour afficher une petite aide en ligne ou bien des informations concernant l'application (par exemple un menu contenant une entrée "about" pourra popper un InformationDialog widget contenant le numéro de la version, les auteurs, etc...").

Pour créer un InformationDialog, on procède de la même manière que pour le WarningDialog, excepté que l'on utilise la fonction XmCreateInformationDialog().

Exemple d'InformationDialog widget :

QuestionDialog
Demande à l'utilisateur d'effectuer un choix de type Oui/Non. Icône en forme de point d'interrogation.
WarningDialog

Ce widget sert à signaler une erreur à l'utilisateur. Il peut s'agir d'une erreur du programme lui-même ou d'une erreur de manipulation de la part de l'utilisateur.

Un exemple typique d'utilisation : lorsque l'utilisateur clicke sur le bouton "Quit" d'une application, il vaut mieux lui demander s'il est bien sûr de vouloir effectuer cette action (surtout s'il n'a pas sauvegardé le travail en cours).
WorkingDialog
Avertit l'utilisateur que l'application est en train d'effectuer une opération. Affiche une petite montre.

Utilisation des MessageDialogs

Dans cette section nous allons étudier l'utilisation et le comportement des MessageDialogs, cependant la totalité des techniques que nous décrivons s'appliquent également aux autres types de Dialogs.

Avant d'aller plus loin, il est intéressant de remarquer que tous les widgets de dialogues comportent deux parties : le haut, que l'on apelle la control area, et le bas, où se trouvent les boutons déclenchant les actions principales et la fermeture du widget, l'action area. Tous les widgets de dialogue doievent distinguer ces deux zones pour respecter le Motif Style Guide.

Les boutons par défaut de l'action area possèdent des callbacks et des actions par défaut. Pour les MessageDialog :

Compilation, création des MessageDialog

Pour compiler il faut inclure <Xm/MesageB.h> puisque les MessageDialogs héritent tous de la classe MessageBox. En réalité, tous les widgets de type MessageDialog sont des MessageBox avec quelques ressources positionnées différemment, en particulier la ressource XmNdialogType.

On crée les différents MessageDialog à l'aide des focntions suivantes :

Il existe également la fonction XmCreateMessageBox() qui permet de créer le même type de widget mais non inclu dans un DialogShell. Cette fonction est utile dans le cas où l'on désire créer un dialog "à la main".

On peut facilement écrire une fonction de création générique à l'aide de XmCreateMessageDialog(). Voici un exemple (incomplet, il n'y a pas de callbacks) de fonction générique :

Widget GenericDialog(Widget pere, int type, char *msg)
{
    Widget dialog;
    XmString msg_motif;
 
    dialog = XmCreateMessageDialog(pere, "dialog", NULL, 0);
    msg_motif = XmStringCreateLocalized(msg);
    XtVaSetValues(dialog,
        XmNdialogType, type,
        XmNmessageString, msg_motif,
        NULL);
    XmSTringFree(msg_motif);

    XtManageChild(dialog);
    XtPopup(XtParent(dialog), XtGrabNone);

    return(dialog)
}

Ressources le plus utiles

Voici une sélection des ressources des MessageDialogs les plus utiles :

Attention : se sont les ressources du widget de dialogue. Ne pas confondre XmNokLabelString qui change le label du bouton Ok, avec la ressource XmNlabelString de la classe PushButton.

Comment faire apparaître ("pop up") un widget de dialogue ?

Management des MessageDialogs

Nous allons étudier dans cette section comment faire apparaître une boîte de dialogue sur l'écran!

Tout d'abord, vous avez dû remarquer que les fonctions de création des widgets sont toutes des fonctions Motif de type XmCreate... pas des fonctions Xt comme XtVaCreateManagedWidget(). Par conséquent, aucun des dialog n'est créé managé.

Pour faire apparaître un dialog, il suffit d'appeler XtManageChild() après sa création. Sitôt managé, le widget se "poppe" à l'écran.Inversement, il suffit de faire XtUnmanageChild() pour faire disparaître le dialog ("pop down")

Ce mécanisme est cependant en conflit avec les spécifications de la librairie Xt, car Motif "cache" le fait que les dialogs possèdent un DialogShell. En outre, sans rien dire à personne, Motif assure que les widgets se "popent" sitôt qu'ils sont managés. Xt de son côté dit que pour faire apparaître un dialog il faut utiliser la fonction XtPopUp() sur un DialogShell, et que pour le faire disparaître il faut exécuter XtPopDown(). Le fait que XtUnmanageChild() fasse disparaître le DialogShell n'est pas du tout prévu par Xt, c'est Motif qui fait que le dialog, lorsqu'il est unmanagé, fait disparaître son DialogSHell de père.

Si on utilise la méthode préconisée par Xt, il faudra donc passer en paramètre à XtPopUp() ou XtPopDown() le père du widget créé par XmCreateInformationDialog() par exemple.

Les deux méthodes peuvent cohabiter, pour assurer une compatibilité maximale avec Motif et les Intrinsincs. C'est ce que recommande la littérature.

Voici un exemple de code faisant apparaître un InformationDialog :

...
xm_string = XmStringCreateLocalized("hello!");
XtSetArg(args[0), XmNmessageString(xm_string);
/* Creation de l'InformationDialog */
dialog = XmCreateInformationDIalog(push_button, "info", args, 1);

XmStringFree(xm_string);
XtAddCallback(dialog, XmNokCallback, activate, NULL);
/* Popper le dialog, Mix des deux méthodes */
XtManageChild(dialog);
XtPopUp(XtParent(dialog), XtGrabNone));

Informations complémentaires sur XtPopUp() et XtPopDown()

Comment faire disparaître ("pop down") un widget de dialogue

Comportement par défaut des boutons Ok et Cancel

Dans le cas des MessageDialogs, par défaut, l'activation du bouton Ok ou du bouton Cancel ferme automqtiquement la boîte de dialogue. Dans certains cas, cependant, il peut arriver que ce comportement ne convienne pas. Par exemple, pour quitter un programme un QuestionDialog convient bien ("Etes-vous sûr de vouloir quitter le programme ?"), mais si les données n'ont pas été sauvegardées il peut être souhaitable que le bouton Ok appelle un autre dialogue demandant de sauvegarder les données.

Modifier ce comportement

Ce comportement est réglable à l'aide de la ressource XmNautoUnmanage. Elle vaut True par défaut. Si on la met à False, il faudra unmanager ou détruire le DialogShell père du dialog dans le callback des boutons Ok et Cancel.

Pour faire disparaître un dialogue, faut-il le unmanager, le détruire ?

Tout dépend de l'application que vous êtes en train d'écrire. Si le widget de dialogue risque d'être affiché régulièrement, il est préférable de le créer une seule fois puis de le manager/unmanager pour le faire apparaître ou disparaître. Pour celà, une technique reconnue consiste à stocker le widget dans une variable statique et à tester sa valeur avant de le re-créer. S'il est non nul on se contente de le manager, sinon on le crée puis on le manage.

Si l'apparition du dialogue est exceptionnelle et si l'application est très gourmande en mémoire, il peut être utile de le détruire après utilisation.

Et si l'utilisateur ferme le dialogue à l'aide du Window Manager ?

Il est vrai que bien souvent le Window Manager propose un menu de manipulation dans la barre de titre qui décore le haut des fenêtres. Ce menu comporte la plupart du temps une entrée "close" qui permet de fermer la fenêtre. Que se passe-t-il lorsque cette entrée est séléctionnée ?

Eh bien tout dépend de la valeur de la ressource XmNdeleteResponse. Trois valeurs sont possibles :

  1. XmUNMAP : valeur par défaut. La fenêtre est unmanagée.
  2. XmDESTROY : attention, la fenêtre et tous ses enfants sont détruits et le cla fonction de allback spécifiée par la ressource XmNdestroyCallback appelé.
  3. XmDO_NOTHING : on n'autorise pas le Window Manager à fermer la fenêtre.

Apparition et disparition de dialogues : callbacks spéciaux

Il est possible d'appeler des callbacks spéciaux lorsqu'un dialogue est affiché ou bien lorsqu'il disparaît. Il est ainsi possible de programmer des effets visuels ou sonores lors de l'affichage des fenêtres de dialogue : effets "guerre des étoilesé à la windows95, etc...

Ces effets sont possibles à l'aide des callbacks XmNpopupCallback et XmNpopdownCallback.

Ces deux callbacks existent aussi pour les PulldownMenus.

Astuce concernant les callbacks des boutons Ok, Cancel et Help

Une seule fonction de callback pour les trois boutons

Il est possible d'associer à ces trois boutons la même fonction de callback. Dans ce cas, le champ reason de la structure de callback passée en troisième et dernier paramètre de la fonction permet de distinguer lequel de ces boutons a provoqué l'appel de la fonction.

Valeurs possible pour le champ reason : CR_OK, CR_CANCEL, CR_HELP

Structure générique du dernier paramètre des fonctions de callback :

typedef struct {
    int reason;
    XEvent *event;
} XmANyCallbackStruct;

Autres ressources des MessageDialogs

Bouton par défaut

Chaque MessageDialog possède un bouton par défaut. Si au lieu d'utiliser la souris on appuie sur la touche <RETURN>, c'est comme si on avait cliqué sur le bouton par défaut. Pour les MessageDialog, ce bouton par défaut est le bouton Ok.

Encore une fois, ce comportement n'est peut-être pas le plus adéquat dans certaines situations. La ressource XmNdefaultButtonType permet de spécifier un comportement personalisé.

Valeurs possibles :

Taille des boutons

Par défaut tous les boutons de l'action area(Ok, Cancel, Help) ont une taille différente. Le bouton Ok est plus petit que le bouton Cancel car son label comporte moins de lettres. Il est possible de changer ce comportement à l'aide de la ressource XmNminimumSizeButtons.

Valeurs possibles :

Titre de la boîte de dialogue

La ressource XmNdialogTitle permet de changer le titre du widget de dialogue. Ce titre appraît dans la fenêtre de décoration située au-dessus de la fenêtre de dialogue.

Je ne veux pas que la boîte de dialogue soit retaillable

La ressource XmNnoResize permet de règler ce comportement. Elle vaut False par défaut.

Polices de caractères

Il est possible de modifier les polices de caractères utilisées par les MessageDialogs et leurs enfants. Tout d'abord n'oubliez pas que les MessageDialogs héritent de la classe BulletinBoard. Toutes les ressources de cette dernière sont utilisables.

Les MessageDialogs deisposent cependant de ressources particulières permettant de modifier globalement les polices utilisées par les différents widgets qu'ils contiennent :

Remarque : il est toujours possible de modifier individuellement les ressources XmNfontList de chaque bouton. Pourcelà, il faut être capable d'adresser chaque bouton individuellement. Nous allons voir dans la section suivante comment réaliser cette opération.

Accès aux widgets internes à une boîte de dialogue

Nous prendrons ici comme exemple les widgets de type MessageDialog cependant la technique est exactement la même en ce qui concerne les autres types de dialogues (SelectionDialog, FileSelectionDialog, etc...). Seul le nom de la fonction permettant d'obtenir un pointeur sur les widgets interne change.

L'accès aux widgets internes d'un MessageDialog s'effectue à l'aide de la fonction XmMessageBoxGetChild(Widget w, unsigned char child).

Attention, le premier paramètre, w, représente le widget de dialogue, pas le DialogShell qui le contient. Le second paramètre, child, permet d'indiquer le widget interne à la boîte de dialogue que l'on désire manipuler par la suite. Valeurs possibles pour child :

Exemple d'utilisation de XmMessageBoxGetChild() :

...
dialog = XmCreateInformationDialog(parent, "message", ), NULL);
XtSetSensitive(XmMessageBoxGetChild(dialog, XmDIALOG_HELP_BUTTON), False);
XtUnManageChild(XmMessageBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON));
....

Dans cet exemple, le bouton Help apparaîtra en fantôme (en grisé, inactif) et le bouton Cancel n'apparaîtra pas dans le dialogue.

Créer son propre widget de dialogue

Il est possible de créer des DialogShell pas à pas, à la main :

  1. Créer un DIalogShell à l'aide de la fonction XtCreatePopupShell(). Ne pas oublier d'inclure <Xm/DialogS.h>
  2. Créer un MessageBox dans le DialogShell
  3. Positionner la ressource XmNdialogType du dialog obtenu à l'étape 2
  4. Installer XmNdestroycallback pour le dialog, qui détruit automatiquement le DialogShell père.

Différences entre un DialogShell et un ApplicationShell

Ces deux objets sont assez proches, cependant il est important de connaître leurs différences :


Programme d'exemple : dialog1.c


Nous allons bientôt étudier ensemble le petit programme dialog1.c qui met en place certains des nimécanismes d'utilisation des boîtes de dialogue étudiés jusqu'à présent. Il manipule un WarningDialog et un InformationDialog.

Remarque : tout ce que le WarningDialog contient est une chaîne de caractères de type XmString qui informe l'utilisateur sur la nature du message.

Différentes étapes pour créer le WarningDialog

Initialiser la variable de type XmString avec la valeur du message d'information. En l'occurence "Are you sure you want to quit ?".

Etant donné que ces widgets sont dérivés de la classe MessageBox, nous devons inclure le fichier <Xm/MessageB.h>.

Programme dialog1.c



#include <Xm/Xm.h>
#include <Xm/MainW.h>
#include <Xm/CascadeB.h>
#include <Xm/MessageB.h>
#include <Xm/PushB.h>
#include <Xm/RowColumn.h>

/*---------------------------*/
main(int argc, char *argv[])
/*---------------------------*/
{
  XtAppContext app;
  Widget top_wid, main_w, menu_bar, info, quit;


  /* callback for the pushbuttons.  pops up dialog */
  void info_pop_up(), quit_pop_up();  
        
  top_wid = XtVaAppInitialize(&app, "Demos", NULL, 0,
                              &argc, argv, NULL, NULL);

  main_w = XtVaCreateManagedWidget("main_window",
                                   xmMainWindowWidgetClass,   top_wid, 
                                   XmNheight, 300,
                                   XmNwidth,300,
                                   NULL);

  menu_bar = XmCreateMenuBar(main_w, "main_list", 
                             NULL, 0);        
  XtManageChild(menu_bar);


  /* create quit widget + callback */
        
  quit = XtVaCreateManagedWidget( "Quit",
                                  xmCascadeButtonWidgetClass, menu_bar,
                                  XmNmnemonic, 'Q',
                                  NULL);
        
  /* Callback has data passed to */
  XtAddCallback(quit, XmNactivateCallback, quit_pop_up, 
                "Are you sure you want to quit?");


    
  /* create help widget + callback */
  info = XtVaCreateManagedWidget( "Info",
                                  xmCascadeButtonWidgetClass, menu_bar,
                                  XmNmnemonic, 'I',
                                  NULL);    

  XtAddCallback(info, XmNactivateCallback, info_pop_up, 
                "Dialog widgets added to give info and check quit choice"); 
    
  XtRealizeWidget(top_wid);
  XtAppMainLoop(app);
}

/*-------------------------------------------------*/
void info_pop_up(Widget cascade_button, char *text, 
                 XmPushButtonCallbackStruct *cbs)
/*-------------------------------------------------*/
{
  Widget dialog;
  XmString xm_string;
  extern void info_activate();
  Arg args[1];
  int n;

  /* label the dialog */
  xm_string = XmStringCreateSimple(text);

  n=0;
  XtSetArg(args[n], XmNmessageString, xm_string); n++;
  /* Create the InformationDialog as child 
     of cascade_button passed in */
  dialog = XmCreateInformationDialog(cascade_button, 
                                     "info", args, n);

  XmStringFree(xm_string);

  XtAddCallback(dialog, XmNokCallback, info_activate, 
                NULL);

  XtManageChild(dialog);
  XtPopup(XtParent(dialog), XtGrabNone);
}

/*-------------------------------------------------*/
void quit_pop_up(Widget cascade_button, char *text, 
                 XmPushButtonCallbackStruct *cbs)
/*-------------------------------------------------*/
{
  Widget dialog;
  XmString xm_string;
  void quit_activate();
  Arg args[1];
  int n;

  /* label the dialog */
  xm_string = XmStringCreateSimple(text);

  n=0;
  XtSetArg(args[n], XmNmessageString, xm_string); n++;
  /* Create the WarningDialog */
  dialog = XmCreateWarningDialog(cascade_button, "quit", 
                                 args, n);

  XmStringFree(xm_string);

  XtAddCallback(dialog, XmNokCallback, quit_activate, 
                NULL);

  XtManageChild(dialog);
  XtPopup(XtParent(dialog), XtGrabNone);
}

/* callback routines for dialogs */

/*-------------------------------------------------*/
void info_activate(Widget dialog)
/*-------------------------------------------------*/
{
  printf("Info Ok was pressed.\n");
}


/*-------------------------------------------------*/
void quit_activate(Widget dialog)
/*-------------------------------------------------*/
{
  printf("Quit Ok was pressed.\n");
  exit(0);
}


Modifier dialog1.c pour ne pas afficher tous les boutons par défaut

Par défaut lorsque vous créez un des Dialog widget , MOTIF propose les trois boutons Ok, Cancel et Help.

Cependant dans certains cas on aimerait bien se débarraser d'un ou deux de ces boutons; par exemple ne conserver que le bouton Ok. Nous avons vu qu'il fallait utiliser la fonction XmMessageBoxGetChild(). Utilisons-là pour supprimer un bouton :

Par exemple, dans le programme dialog1.c, si l'on ne veut conserver que le bouton Ok dans l'InformationDialog, on ajoutera les lignes suivantes justes après l'appel de la fonction XmCreateInformationDialog() :



    Widget button;
    .
    .
    .
    dialog = XmCreateInformationDialog(cascade_button, "info", args, 1);
        
    button = XmMessageBoxGetChild(dialog, 
                                  XmDIALOG_CANCEL_BUTTON);
    XtUnmanageChild(button);

    button = XmMessageBoxGetChild(dialog, 
                                  XmDIALOG_HELP_BUTTON);
    XtUnmanageChild(button);


Le résultat est présenté dans le fichier dialog2.c

Ci-dessous l'InformationDialog ne possède plus qu'un bouton.


Copyright Michel Buffa 1996 (michel.buffa@unice.fr)