These tutorials are excerpted from the book "Motif Programming: The Essentials and More" , by Marshall Brain.
To get a feel for using multiple widgets, here's a piece of code to type in. The purpose of this code is to create a celcius to fahrenheit conversion application by displaying 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 "scale". The purpose of a scale widget is to retrieve integer values between a set range from the user.
#include <Xm/Xm.h> #include <Xm/PushB.h> #include <Xm/Label.h> #include <Xm/Scale.h> #include <Xm/BulletinB.h> XtAppContext context; XmStringCharSet char_set=XmSTRING_DEFAULT_CHARSET; Widget toplevel, button, bb, label, scale; void buttonCB(w, client_data, call_data) Widget w; int client_data; XmPushButtonCallbackStruct *call_data; /* handles the pushbutton's activate callback. */ { exit(0); } void scaleCB(w, client_data, call_data) Widget w; int client_data; XmScaleCallbackStruct *call_data; /* handles the scale's callback. */ { char s[100]; Arg al[10]; int ac; sprintf(s,"farenheit=%d",call_data->value*9/5+32); ac=0; XtSetArg(al[ac],XmNlabelString, XmStringCreate(s,char_set)); ac++; XtSetValues(label,al,ac); } void main(argc,argv) int argc; char *argv[]; { Arg al[10]; int ac; /* create the toplevel shell */ toplevel = XtAppInitialize(&context,"",NULL,0, &argc,argv,NULL,NULL,0); /* resize toplevel */ ac=0; XtSetArg(al[ac],XmNheight,300); ac++; XtSetArg(al[ac],XmNwidth,200); ac++; XtSetValues(toplevel,al,ac); /* create a bulletin board to hold the three widgets */ ac=0; bb=XmCreateBulletinBoard(toplevel,"bb",al,ac); XtManageChild(bb); /* create a push button */ ac=0; XtSetArg(al[ac],XmNlabelString, XmStringCreate("Quit",char_set)); ac++; button=XmCreatePushButton(bb,"button",al,ac); XtManageChild(button); XtAddCallback(button,XmNactivateCallback,buttonCB,NULL); /* create a scale */ ac=0; XtSetArg(al[ac],XmNtitleString, XmStringCreate("Celsius Temperature",char_set)); ac++; XtSetArg(al[ac],XmNorientation,XmHORIZONTAL); ac++; XtSetArg(al[ac],XmNshowValue,True); ac++; scale=XmCreateScale(bb,"scale",al,ac); XtManageChild(scale); XtAddCallback(scale,XmNdragCallback,scaleCB,NULL); /* create a label */ ac=0; XtSetArg(al[ac],XmNlabelString, XmStringCreate("Farenheit = 32",char_set)); ac++; label=XmCreateLabel(bb,"label",al,ac); XtManageChild(label); /* Place new code here */ XtRealizeWidget(toplevel); XtAppMainLoop(context); } Type this in and run it and you will find that it doesn't work at all. You will probably see only one widget.
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 items on a bulletin board. Since we haven't specified where items should go, all three widgets are piled on top of each other in the upper left corner of the bulletin board. The following code solves this problem. You should place it at the comment that says, "Place new code here".
/* position widgets on the bulletin board */ 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++; XtSetValues(scale,al,ac); ac=0; XtSetArg(al[ac],XmNx,10); ac++; XtSetArg(al[ac],XmNy,200); ac++; XtSetValues(label,al,ac);
If you make these changes and rerun the program, you will find that the three widgets are set up correctly. All that the above code does is place things. You may notice that the widgets sit on the screen 10 pixels over from the edge. 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 by changing the resource. You will want to examine the resource lists for both the bulletin board and in particular the scale widget, and then try out some a variety of settings so that you are comfortable with these widgets.
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". It contains an extra list of constraint resources that apply to its children. That is, every child of the form widget picks up 17 new resources from the form widget, and you use those resources to "attach" the children to the form.
To try using a form widget, you can modify the above 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 XmCreateBulletinBoard line so that it calls XmCreateForm instead. Finally, replace the "place new code here" comment with the following code:
/* create a separator */ ac=0; sep=XmCreateSeparator(form,"sep",al,ac); XtManageChild(sep); /* attach the children to the form */ 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, 30); ac++; /* 30 is a percentage */ 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++; XtSetArg(al[ac], XmNbottomAttachment, XmATTACH_NONE); ac++; XtSetValues(scale,al,ac); ac=0; XtSetArg(al[ac], XmNtopAttachment, XmATTACH_WIDGET); ac++; XtSetArg(al[ac], XmNtopWidget, scale); ac++; XtSetArg(al[ac], XmNrightAttachment, XmATTACH_FORM); ac++; XtSetArg(al[ac], XmNleftAttachment, XmATTACH_FORM); ac++; XtSetArg(al[ac], XmNbottomAttachment, XmATTACH_NONE); 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);
Note that this code includes a new widget called a separator. A separator is simply a horizontal or vertical line that visually separates widgets. Include Separator.h at the top of the code.
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 o the button is attached to a position 30% down the form widget. The scale is attached to the bottom of the button, the separator to the bottom of the scale, and then the label is attached to the separator (the separator may not be obvious, but it's there--attach its top to a 80% position if 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. Note also that several widgets must be attached to the form at creation. That is, the attachments for these widgets must be passed into the XmCreate function. These widgets include the scrolled list and scrolled text widgets.
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. The next article will cover these widgets.