Widget primitives can be used to construct many varied user interfaces, but complex programs written with them suffer the following drawbacks:
- Large widget applications become difficult to maintain. As an application grows, it becomes more difficult to properly write and test. The resulting program suffers from poor organization.
- Good ideas can be difficult to reuse. Most larger applications are constructed from smaller sub-units. For example, a color table editor might contain control panel, color selection, and color-index selection sub-units. These sub-units are often complicated tools that could be used in other programs. To reuse such sub-units, you must understand the existing application and then transfer the parts of interest into the new program.
Compound widgets solve these problems. A compound widget is a complete, self-contained, reusable widget sub-tree that behaves to a large degree just like a primitive widget. Complex widget applications written with compound widgets are much easier to maintain than the same application written without them. Using compound widgets is analogous to using subroutines and functions in programming languages.
Writing Compound Widgets
Compound widgets are written in the same way as any other widget application. They are distinguished from regular widget applications in the following ways:
Compound widgets usually have a base widget at the root of their hierarchies. This base contains the subwidgets that make up the compound widget. From the user’s point of view, this single widget is the compound widget; its children are not programmatically accessible on their own.
Notice that the base widget at the root of a compound widget is not a top-level base. When used, a compound widget must always have a parent widget.
- It is important that the compound widget not make use of the base’s user value. In order to preserve the illusion that the compound widget works just like any of the widget primitives, the user value of the compound widget’s top-level base should be reserved for use by the caller of the compound widget. Instead, the compound widget should use the user value of one of its child widgets.
- The widget at the root of the compound widget’s hierarchy always has an event handler function associated with it via the EVENT_FUNC keyword to the widget creating function or the WIDGET_CONTROL procedure. This event handler manages events from its sub-widgets and generates events for the compound widget. By swallowing events from the widgets that comprise the compound widget and generating events that represent the compound widget, it presents the illusion that the compound widget is acting like a widget primitive.
- If the compound widget has a value that can be set, it should be assigned a value setting procedure via the PRO_SET_VALUE keyword to the widget creating function or the WIDGET_CONTROL procedure.
- If the compound widget has a value that can be retrieved, it should be assigned a value retrieving function via the FUNC_GET_VALUE keyword to the widget creating function or the WIDGET_CONTROL procedure.
For an example of how a compound widget might be written, see Example: Compound Widget.
The HANDLER Field of the Widget Event Structure
Recall that when WIDGET_EVENT finds an event to return, it moves up the widget hierarchy looking for an event-handling routine registered to the widgets in between its current position and the top-level base of the widget application. If such a routine is found, it is called with the event as its argument, and the HANDLER field of this event is set to the widget ID of the widget where the event routine was found. Since compound widgets have event handlers associated with their root widget, the HANDLER field gives the event handler the widget ID of the root widget. This allows the event handler for a compound widget instance to easily locate the location of its state information relative to this root.
Storing State Information
IDL programmers are often tempted to store the state information directly in the user value of the root widget, but this is not a good idea. The user value of a compound widget is reserved for the user of the widget, just like any basic widget. Therefore, you should store the state information in the user value of one of the child widgets below the root. As a convention, the user value of the first child is often used, leading to event handlers structured as follows:
FUNCTION EVENT_FUNC, event
child = WIDGET_INFO(event.HANDLER, /CHILD)
WIDGET_CONTROL, child, GET_UVALUE=state, /NO_COPY
WIDGET_CONTROL, child, SET_UVALUE=state, /NO_COPY
Sometimes, an application will find that it needs to use the user value of all its child widgets for some other purpose, and there is no convenient place to keep the state information. One way to work around this problem is to interpose an extra base between the root base and the rest of the widgets:
ROOT = WIDGET_BASE(parent)
EXTRA = WIDGET_BASE(root)
In such an approach, the remaining widgets would all be children of EXTRA rather than ROOT.