X-Designer - The Leading X/Motif GUI Builder - Click to download a FREE evaluation |
In this chapter:
Chapter: 14
The ComboBox Widget
A ComboBox is a component which combines direct textual entry with the convenience of list selection. The user can either type directly into a TextField, or choose from a set of predefined values available from a popup ScrolledList. The ComboBox widget was introduced in Motif 2.0, and is thus not available in earlier versions of the toolkit.The Motif ComboBox is a manager widget. It automatically creates a TextField and ScrolledList for direct textual entry and list selection respectively. The ComboBox layout can be configured in a limited number of ways, depending upon whether the list of items is to be permanently visible, or whether the ScrolledList is hidden and only displayed on user request. Figure 14-1 displays a ComboBox with the List set to be permanently visible.
Figure 14-1 A ComboBox widget with the List permanently visible
In this configuration, the ScrolledList is simply placed directly underneath the ComboBox, and is sized so that the width of the List is the same as the ComboBox built-in TextField. The user can either type directly into the TextField, or select an item from the List. Selecting from the ScrolledList automatically places the selected item into the TextField, replacing any previous contents. This configuration is perhaps not the usual one associated with a ComboBox. More familiar is the arrangement shown in Figure 14-2. The ComboBox in this case does not display the Scrolled List until requested by the user. In order to display the List, the user clicks on an arrow which the ComboBox adds to the side of the TextField.
Figure 14-2 A ComboBox widget with the List visible on request
There are three basic kinds of ComboBox supported by Motif. The default ComboBox creates a permanently visible Scrolled List, and a TextField which is editable, which means that the user is not restricted in choice by the set of items in the List. A Drop-down-ComboBox, on the other hand, hides the Scrolled List until required, but again the TextField is editable. A ComboBox which is configured as a Drop-down-List also hides the built-in Scrolled List until required, but this time the TextField is not editable, and thus the user must choose from the Scrolled List in order to change the current selection.
The ComboBox is not meant to be used as a general purpose manager, and you are not expected to add any additional children to the widget over and above the built-in components.
Creating a ComboBox
Creating a ComboBox is performed in precisely the same kind of way that other Motif widgets are instantiated. Applications must include the header file associated with the widget class, which in this case is <Xm/ComboBox.h>. A ComboBox can be created in one of the following ways, either using a Motif convenience routine, or the general purpose Xt methods; note that the ComboBox supports more than one convenience routine for its creation:The parent can be a Shell or any manager widget. Since the geometry management involved in creating a ComboBox is minimal, the widget could be created as managed using XtCreateManagedWidget() or similar without incurring a significant performance overhead. See Chapter 8, Manager Widgets, for a discussion of when manager widgets should be created in the managed or unmanaged state. The resource-value parameters control the behavior and visual effects of the ComboBox, as well as its built-in TextField and List children.Widget combo = XmCreateComboBox ( parent, "name", resource-value-array, resource-value-count); ... Widget combo = XmCreateDropDownComboBox ( parent, "name", resource-value-array, resource-value-count); ... Widget combo = XmCreateDropDownList ( parent, "name", resource-value-array, resource-value-count); ... Widget combo = XtCreateWidget ( "name", xmComboBoxWidgetClass, parent, resource-value-list, NULL);The most important resource for configuring the layout of the ComboBox is XmNcomboBoxType. If the value is XmCOMBO_BOX, the ComboBox creates a permanently visible Scrolled List, and the built-in TextField is set to be editable. If the value is XmDROP_DOWN_COMBO_BOX, the Scrolled List is hidden until required, an arrow is drawn by the ComboBox to facilitate List popup, and the TextField is set to be editable. The value XmDROP_DOWN_LIST is similar to XmDROP_DOWN_COMBO_BOX, except that the TextField is not editable, thus forcing the user to display and subsequently select from the Scrolled List in order to change the current selection. The XmNcomboBoxType resource is a create-only attribute: you cannot change the value dynamically, and must specify the type within the resource-value-array argument to the widget creation routine if you are using a general purpose Xt widget creator. The default value is XmCOMBO_BOX; this might be considered somewhat inconvenient since the usual requirement is for a ComboBox with a hidden List.
However, Motif does provide three convenience routines to create the widget, and these internally set the XmNcomboBoxType resource appropriately for the required configuration. The function XmCreateComboBox() is equivalent to setting XmNcomboBoxType to XmCOMBO_BOX, which displays the List permanently. For a hidden List, use either XmCreateDropDownComboBox() or XmCreateDropDownList(), depending on whether you need the TextField to be editable. XmCreateDropDownComboBox() is equivalent to setting XmNcomboBoxType to XmDROP_DOWN_COMBO_BOX, which creates an editable TextField. XmCreateDropDownList() creates a read-only TextField, and is equivalent to setting XmNcomboBoxType to XmDROP_DOWN_LIST.
Example 14-1 creates a ComboBox for selecting from a range of color names.1
In the example, we create the ComboBox using the convenience routine XmCreateDropDownList(). If we wanted the user to be able to directly supply a different value to the colors we add to the ComboBox, we would use the routine XmCreateDropDownComboBox() instead. The appearance of the application is identical in this case; only the editability of the built-in TextField changes. We specify the contents of the ComboBox built-in List through the XmNitems and XmNitemCount resources. These are mirrored resources: the ComboBox arranges to set the items on the List on our behalf, and thus we do not need to gain access to the underlying List component in order to program the set of available choices. The XmNitems resource specifies an array of compound strings, which topic is covered in detail in Chapter 25, Compound Strings./* simple_combobox.c -- demonstrate the combobox widget */ #include <Xm/Xm.h> #include <Xm/ComboBox.h> /* the list of colors */ char *colors[] = { "red", "green", "blue", "orange", "purple", "pink", "white", "black", "yellow" }; main (int argc, char *argv[]) { Widget toplevel, combo; XtAppContext app; Arg args[4]; int count = XtNumber (colors); int i, n; XmStringTable str_list; /* initialize the toolkit */ XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, NULL); /* create the List items */ str_list = (XmStringTable) XtMalloc (count * sizeof (XmString *)); for (i = 0; i < count; i++) str_list[i] = XmStringCreateLocalized (colors[i]); /* create the combobox */ n = 0; XtSetArg (args[n], XmNitems, str_list); n++; XtSetArg (args[n], XmNitemCount, count); n++; combo = XmCreateDropDownList (toplevel, "combo", args, n); for (i = 0; i < n; i++) XmStringFree (str_list[i]); XtFree ((XtPointer) str_list); XtManageChild (combo); XtRealizeWidget (toplevel); XtAppMainLoop (app); }The output from the program is given in Figure 14-3.
ComboBox Resources
The ComboBox provides what is known as a mirror; that is, for each of the important resources which can be set on the built-in TextField and Scrolled List children there is an equivalent implemented in the ComboBox resource table itself, so that access to the underlying children is not necessary for the majority of tasks.List Resources
The contents of the ComboBox popup List can be set using the XmNitems and XmNitemCount resources of the ComboBox. XmNitems specifies an array of compound strings, and XmNitemCount is the number of such strings in the array. The following code fragment shows how to create a ComboBox which displays the names of the days of the week:The vertical size of the ComboBox List is controlled through the XmNvisibleItemCount resource. If the visible item count is less than the number of items in the list, the List automatically displays ScrollBars for navigating to hidden items.extern Widget parent; char *month_names[] = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }; int count, i, n; XmString *xms; Arg args[4]; Widget combo; count = XtNumber (month_names); xms = (XmString *) XtMalloc ((unsigned) count * sizeof (XmString)); for (i = 0; i < count; i++) { xms[i] = XmStringCreateLocalized (month_names[i]); } n = 0; XtSetArg (args[n], XmNitems, xms); n++; XtSetArg (args[n], XmNitemCount, count); n++; combo = XmCreateComboBox (parent, "days", args, n); for (i = 0; i < count; i++) XmStringFree (xms[i]); XtFree ((char *) xms);Just as a List supports automatic keyboard selection through the XmNmatchBehavior resource, so this is mirrored in the ComboBox resource set. If the value is XmQUICK_NAVIGATE, the user can select an item in the List simply by typing the first character of the item. This behaviour is disabled if the value is XmNONE.
The widget ID of the built-in List is available through the ComboBox XmNlist resource. You may fetch, but not set the value of this resource.
TextField Resources
The TextField contains the current selection. This can be fetched or set through the XmNselectedItem resource. Note that this resource specifies a compound string, even though the internals of the Motif Text widgets are not compound-string based.The width of the built-in TextField is controlled by the XmNcolumns resource. This also controls the width of the ComboBox itself, although allowance should be made for the additional space required to draw an arrow.
The widget ID of the built-in TextField can be fetched using the XmNtextField resource. This resource is read-only in nature.
ComboBox Resources
The type of the ComboBox is specified through the XmNcomboBoxType resource. The possible values are:Note that these are create-only resources, so you need to decide in advance whether or not to allow the user to type directly into the TextField.XmCOMBO_BOX XmDROP_DOWN_COMBO_BOX XmDROP_DOWN_LISTThe way in which the ComboBox reports the current selection is controlled through a resource called XmNpositionMode. Normally, a List is manipulated by specifying indexes starting at 1. That is to say, the first item in a List is at position 1, the second item at position 2, and so forth. Position zero is reserved to mean "the end of the list", whatever that index may be. However, the CDE DtComboBox widget was based around a different model: the first item is at position zero, the second at position 1, and so forth. Since CDE pre-dates the release of Motif 2.x, applications may well have included the DtComboBox widget well before the Motif ComboBox became available. For this reason, the resource XmNpositionMode was implemented. If XmNpositionMode is XmONE_BASED, the ComboBox reports positions in its callbacks starting at position 1, which is consistent with normal Motif List behavior. If on the other hand, XmNpositionMode is XmZERO_BASED, positions are reported offset from zero for CDE compatibility. Note that this resource does not affect any of the ComboBox functions described within the next section: it only affects the way in which the various elements of a ComboBox XmNselectionCallback is interpreted, and the value of the resource XmNselectedPosition.
The index of the current selection in the List of choices is given by the XmNselectedPosition resource. As described in the previous paragraph, the interpretation of this resource depends upon the XmNpositionMode value. If XmNpositionMode is XmONE_BASED, an XmNselectedPosition of 1 means that the first item in the List is the selected item. In this mode, a position of zero is reserved to mean that no List items were selected. If XmNpositionMode is XmZERO_BASED, an XmNselectedPosition of zero means that the first List item is selected, a position of 1 means the second was selected, and so forth. It is difficult in these circumstances to work out if no List items were selected other than by directly querying the XmNselectedItems of the internal List itself.
ComboBox Functions
Just as you can program the contents of a List widget either by manipulating the XmNitems and XmNselectedItem resources or by calling any of a whole range of routines for selectively adding or deleting items, so also the ComboBox comes with a set of convenience routines for changing its List contents over and above any manipulations to the ComboBox XmNitems resource you may care to make.2
Adding ComboBox Items
An item can be inserted into the ComboBox built-in List through the function XmComboBoxAddItem(), which takes the following form:The position parameter specifies an index into the ComboBox List where the item is to be inserted. The first item in the List is at position 1. If position is zero, item is appended to the bottom of the List. If unique is True, item is only inserted if it does not already exist in the ComboBox List.void XmComboBoxAddItem ( Widget combo, XmString item, int position, Boolean unique)
Deleting ComboBox Items
You can delete an item at a given position in the ComboBox List through the function XmComboBoxDeletePos(). This routine has the following prototype:Again, the position parameter is interpreted such that the first item in the ComboBox List is at position 1. Deleting position zero deletes the last ComboBox List item. An error message is displayed by the Motif toolkit if no item exists at the specified position.void XmComboBoxDeletePos (Widget combo, int position)
Selecting ComboBox Items
You can programmatically set the current choice in the ComboBox through the routine XmComboBoxSelectItem(). This has the following signature:The routine selects the first occurrence of item in the ComboBox internal List. This also resets the contents of the built-in TextField. There is a side effect such that if item is not amongst the ComboBox List choices, an error message is displayed by the Motif toolkit.void XmComboBoxSelectItem (Widget combo, XmString item)The routine XmComboBoxSetItem() is very similar to XmComboBoxSelectItem() in that it sets the current selection. It also makes the selected choice the first visible item in the ComboBox List. The routine has the following format:
Whether using XmComboBoxSetItem() or XmComboBoxSelectItem(), no callbacks are invoked by the ComboBox as a result of changing the current selection.void XmComboBoxSetItem (Widget combo, XmString item)
Updating the ComboBox
If you change the state of the ComboBox built-in components other than through the convenience routines, it is possible for the ComboBox to get out of step with what it believes is the current contents and state of its children. For example, you might fetch the built-in List through the XmNlist resource of the ComboBox, and set various resources directly on the child rather than going through the ComboBox mirror. This might be performed because the set of ComboBox convenience routines for manipulating the contents of the internal List is considerably smaller than the set provided for directly manipulating a normal List, and so by fetching the widget ID of the List child a much greater range of toolkit functionality becomes available for use. In these circumstances, the ComboBox may need to be synchronized with the state of its children after you have finished manipulating them. The routine XmComboBoxUpdate() is provided for this purpose, and the ComboBox resets its state by refreshing its values read directly from the children. This routine is specified as follows:void XmComboBoxUpdate (Widget combo)
ComboBox Callbacks
The only callback defined specifically by the ComboBox widget class is the XmNselectionCallback. This is called when the user changes the current selection, either by typing into the built-in TextField, or by selecting from the built-in Scrolled List. Each callback of this type is associated with the structure XmComboBoxCallbackStruct, which is defined as follows:The reason element of an XmNselectionCallback will have the value XmCR_SELECT. The item_or_text element is a compound string which represents the current selection. This is temporarily allocated only for the duration of the callback, and so if you need to cache the current choice, you need to copy the element using XmStringCopy() or similar.typedef struct { int reason; XEvent *event; XmString item_or_text; int item_position; } XmComboBoxCallbackStruct;The interpretation of the item_position element depends upon the value of the XmNpositionMode resource. If the mode is XmONE_BASED, an item_position of 1 refers to the first item in the ComboBox List, an item_position of 2 is the second List item, and so forth. An item_position of zero indicates that there was no List selection - in other words, the item_or_text element value must have resulted from the user directly typing into the ComboBox TextField. If the XmNpositionMode resource is XmZERO_BASED, an item_position of zero could either have resulted from direct text entry, or the first List item was selected. An item_position of 1 indicates selection of the second List item, and so forth. In this mode, it can be very difficult to distinguish between the cases where the user has selected the first item in the List, and when the user has directly typed, simply by inspection of the callback data, particularly when the user navigates the built-in List using the keyboard. In both cases, the item_position element is zero, and the event element will report a KeyEvent of some description. If you need to know the difference between List and TextField selection, you should consider switching to a position mode of XmONE_BASED.
Note that the XmNselectionCallback is called every time the selection changes, even if the user has not finished with the current action. For example, if the user browses up and down the List of choices using the arrow keys, the XmNselectionCallback will be invoked each time the choice is moved, even though the user has not made the final selection. Fortunately it is possible to distinguish between browsing around the List and actual selection because the event element of the callback structure is NULL when the user browses the List, and points to a proper KeyPress or ButtonRelease event otherwise.
Example 14-2 is a simple program which prints out the current selection, using an XmNselectionCallback for the purpose.3
/* combo_cb.c -- demonstrate the combobox widget selection callback */ #include <Xm/Xm.h> #include <Xm/ComboBox.h> /* the list of colors */ char *colors[] = { "red", "green", "blue", "orange", "purple", "pink", "white", "black", "yellow" }; /* selection_callback: simply prints out the current ComboBox selection */ void selection_callback (Widget w, XtPointer client_data, XtPointer call_data) { XmComboBoxCallbackStruct *cb = (XmComboBoxCallbackStruct *) call_data; char *choice = (char *) XmStringUnparse (cb->item_or_text, XmFONTLIST_DEFAULT_TAG, XmCHARSET_TEXT, XmCHARSET_TEXT, NULL, 0, XmOUTPUT_ALL); if (cb->event == NULL) { printf ("Browsing: potential choice is %s\n", choice); } else { printf ("New current choice is %s\n", choice); } XtFree (choice); } main (int argc, char *argv[]) { Widget toplevel, combo; XtAppContext app; Arg args[4]; int count = XtNumber (colors); int i, n; XmStringTable str_list; /* initialize the toolkit */ XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, NULL); /* create the List items */ str_list = (XmStringTable) XtMalloc ((unsigned) count * sizeof (XmString *)); for (i = 0; i < count; i++) str_list[i] = XmStringCreateLocalized (colors[i]); /* create the combobox */ n = 0; XtSetArg (args[n], XmNitems, str_list); n++; XtSetArg (args[n], XmNitemCount, count); n++; combo = XmCreateDropDownComboBox (toplevel, "combo", args, n); for (i = 0; i < n; i++) XmStringFree (str_list[i]); XtFree ((XtPointer) str_list); XtAddCallback (combo, XmNselectionCallback, selection_callback, NULL); XtManageChild (combo); XtRealizeWidget (toplevel); XtAppMainLoop (app); }
Summary
The beauty of the ComboBox is the fact that it provides the convenience of List selection while only occupying the screen space of a TextField when inactive. The user normally sees just the current selection, and only needs to inspect the full set of values when the choice is to be changed. Changing the choice is simply a matter of displaying and selecting from the List, or typing the new value. It is the conjunction of economy of space with ease of use which makes this one of the single most important additions to the Motif 2.x widget set.
Exercises
- Create a program which displays a date using a combination of three ComboBoxes for each of the day, month, and year elements. The year ComboBox should offer a simple range of choices for the years 1990 through to 2010.
- Modify your program such that the current date is displayed in the ComboBoxes when the program first starts.
1 XtVaOpenApplication() and the SessionShell widget class are only available in X11R6. XmCreateDropDownList () is only available from Motif 2.0 onwards.2 With the exception of XmComboBoxUpdate(), all of the ComboBox convenience routines are only available as of Motif 2.1. XmComboBoxUpdate() is available from Motif 2.0.
3 XtVaOpenApplication() and the SessionShell widget class are only available in X11R6. XmCreateDropDownComboBox () and XmStringUnparse() are only available from Motif 2.0 onwards.
|