Next: Separator Widget Up: Motif Tutorial Previous: Callbacks

Working with mutiple widgets

Theoretically you are now set - you know about the existence of widgets, and you know that all widgets have resources and callbacks associated with them. But you need to know one more thing - how to put widgets together. Any application will use a number of widgets all put together in a window, so you need to know how to do that. There are two places where this skill is handy: 1) when building an application, and 2) when building a dialog box that needs to have several different static and editable areas.

To get a feel for the multiple-widget thing, here's a piece of code to type in. The purpose of this code is to display three widgets in a window instead of one. In this case we'll use a label widget, a push button widget, and a new widget called a "separator". The purpose of a separator widget is to sit on the screen as a line separating two other widgets.


        #include <X11/Intrinsic.h>
        #include <Xm/Xm.h>
        #include <Xm/PushB.h>
        #include <Xm/Label.h>
        #include <Xm/BulletinB.h>
        #include <Xm/Separator.h>

        void handle_button(Widget w,
                           int client_data,
                           XmAnyCallbackStruct *call_data)
        {
           printf("ouch\n");
        }

        main(int argc, char *argv[])
        {
          Widget toplevel, button, bb, label, sep;
          Arg al[10];
          int ac;

          toplevel=XtInitialize(argv[0],"",NULL,0,&argc,argv);

          /* resize the window */
          ac=0;
          XtSetArg(al[ac],XmNheight,200); ac++;
          XtSetArg(al[ac],XmNwidth,200); ac++;
          XtSetValues(toplevel,al,ac);

          /* create a bulletin board to hold widgets */
          ac=0;
          bb=XtCreateManagedWidget("bb",xmBulletinBoardWidgetClass,
                toplevel,al,ac);

          /* create a push button */
          ac=0;
          XtSetArg(al[ac],XmNlabelString,
                XmStringCreate("I'm a button",
                XmSTRING_DEFAULT_CHARSET)); ac++;
          button=XtCreateManagedWidget("button",xmPushButtonWidgetClass,
                bb,al,ac);
          XtAddCallback(button,XmNactivateCallback,handle_button,1);

          /* create a separator */
          ac=0;
          sep=XtCreateManagedWidget("sep",xmSeparatorWidgetClass,
                bb,al,ac);

          /* create a label */
          ac=0;
          XtSetArg(al[ac],XmNlabelString,
                XmStringCreate("I'm a label",
                XmSTRING_DEFAULT_CHARSET)); ac++;
          label=XtCreateManagedWidget("label",xmLabelWidgetClass,
                bb,al,ac);

          /* put new code here */

          XtRealizeWidget(toplevel);
          XtMainLoop();
        }

Type this in and run it and you will find that it doesn't work at all. You will probably see only one widget. There's also that "bulletin board" widget: what does it do for a living?

Here's what is going on. To handle multiple widgets, Motif defines what are called "container widgets". A bulletin board widget is an example. It holds groups of widgets, and you basically "tack on" other widgets like you would tack papers on a bulletin board. Since we haven't moved anything around though, all three widgets are piled on top of each other. The following code solves this problem. You would place it at the comment that says, "Place new code here".


  ac=0;
  XtSetArg(al[ac],XmNx,10); ac++;
  XtSetArg(al[ac],XmNy,10); ac++;
  XtSetValues(button,al,ac);

  ac=0;
  XtSetArg(al[ac],XmNx,1); ac++;
  XtSetArg(al[ac],XmNy,100); ac++;
  XtSetArg(al[ac],XmNwidth,200); ac++;
  XtSetValues(sep,al,ac);

  ac=0;
  XtSetArg(al[ac],XmNx,10); ac++;
  XtSetArg(al[ac],XmNy,150); ac++;
  XtSetValues(label,al,ac);

If you make these changes and rerun it, you will find that the three widgets are set up correctly. All that the above code does is place things. However, you will notice that the separator sits on the screen 10 pixels over. Why? This occurs because the bulletin board has a resource called marginWidth that is set to 10. If you don't like that, you can change it.

Now try to resize the window. You will see there's a problem here. Users tend to resize windows, but a bulletin board doesn't handle it very well. To get around this problem, you can either set the window so that it can't be resized, or you can use a "form widget". A form widget is called a "constraint widget", because it constrains objects on the form rather than just tacking them on. But a form widget has a bulletin board as its ancestor, so it can also act like a bulletin board if you like.

To try using a form widget, you can modify the first piece of code. Replace the #include for BulletinB.h with Form.h. Replace the variable "bb" everywhere with form (not necessary, but it makes things clearer). Fix the XtCreateManagedWidget line so that it creates a FormWidgetClass widget. Finally, replace the "place new code here" comment with the following code:


  ac=0;
  XtSetArg(al[ac], XmNtopAttachment,    XmATTACH_FORM); ac++;
  XtSetArg(al[ac], XmNrightAttachment, XmATTACH_FORM); ac++;
  XtSetArg(al[ac], XmNleftAttachment,   XmATTACH_FORM); ac++;
  XtSetArg(al[ac], XmNbottomAttachment,    XmATTACH_POSITION); ac++;
  XtSetArg(al[ac], XmNbottomPosition, 50); ac++;
  XtSetValues(button,al,ac);

  ac=0;
  XtSetArg(al[ac], XmNtopAttachment,    XmATTACH_WIDGET); ac++;
  XtSetArg(al[ac], XmNtopWidget, button); ac++;
  XtSetArg(al[ac], XmNrightAttachment, XmATTACH_FORM); ac++;
  XtSetArg(al[ac], XmNleftAttachment,   XmATTACH_FORM); ac++;
  XtSetValues(sep,al,ac);

  ac=0;
  XtSetArg(al[ac], XmNtopAttachment,    XmATTACH_WIDGET); ac++;
  XtSetArg(al[ac], XmNtopWidget, sep); ac++;
  XtSetArg(al[ac], XmNrightAttachment, XmATTACH_FORM); ac++;
  XtSetArg(al[ac], XmNleftAttachment,   XmATTACH_FORM); ac++;
  XtSetArg(al[ac], XmNbottomAttachment,   XmATTACH_FORM); ac++;
  XtSetValues(label,al,ac);

Run the new program and you will find that as you resize the window, all of the widgets on the form resize appropriately. A form widget allows other widgets to be attached to it, and these attachments cause the attached widgets to follow the form as it changes size and shape. The button is attached on its top, left and right sides, to the form. The bottom of the button is attached to a position 50is attached to the bottom of the button, and then the label is attached to the separator (the separator may be a little hard to see being so close to the button, but it's there-attach its top to a 60if you want to convince yourself that it's there).

Generally, a form widget is used as the container of choice if resizing of the window is allowed. If no resizing is allowed (for example, in a dialog box), then a bulletin board is often used instead.

It is easy to create bugs and weirdness when attaching things on a form widget. Work from the top down, and from left to right, and these problems can be avoided. It is also common for widgets to attach themselves automatically when placed in a form. For example, a scroll bar widget will automatically attach itself to the left side of widgets spontaneously. To stop this, set the attachment point to xmATTACH_NONE.

There is one last topic that must be covered before we can start creating full applications. Most applications include a menu bar, and a set of user interface devices known as "dialog boxes", that are used to get specific pieces of information from the user. Tutorial 6 will cover these widgets.

--------------------------




Next: Separator Widget Up: Motif Tutorial Previous: Callbacks


morbe@enstb.enst-bretagne.fr