Managing Application State
A widget application is usually divided into at least two separate routines, one that creates and realizes the application and another that handles events. These multiple routines need shared access to certain types of information, such as the widget IDs of the application’s widgets and data being used by the application. This shared information is referred to as the application state.
Techniques for Preserving Application State
The following are some techniques you can use to preserve and share application state data between routines.
Using COMMON Blocks
One obvious answer to this problem is to use a COMMON block to hold the state. However, this solution is generally undesirable because it prevents more than a single copy of the application from running at the same time. It is easy to imagine the chaos that would ensue if multiple instances of the same application were using the same common block without some sort of interlocking.
Using a State Structure in a User Value
A better solution to this problem is to use the user value of one of the widgets to store state information for the application.Using this technique, multiple instances of the same widget code can exist simultaneously. Since this user value can be of any type, a structure can be used to store any number of state-related values.
For example, consider the following example widget code:
PRO my_widget_event, event
WIDGET_CONTROL, event.TOP, GET_UVALUE=state, /NO_COPY
Event-handling code goes here
WIDGET_CONTROL, event.TOP, SET_UVALUE=state, /NO_COPY
END
PRO my_widget
; Create some widgets
wBase = WIDGET_BASE(/COLUMN)
wDraw = WIDGET_DRAW(wBAse, XSIZE=300, YSIZE=300)
; Realize the base widget and retrieve the widget ID
; of the drawable area.
WIDGET_CONTROL, wBase, /REALIZE
WIDGET_CONTROL, wDraw, GET_VALUE=idxDraw
; Create a state structure variable and set the user
; value of the top-level base equal to the state variable.
state = {wDraw:wDraw, idxDraw:idxDraw}
WIDGET_CONTROL, wBase, SET_UVALUE=state
; Use XMANAGER to manage the widgets
XMANAGER, 'my_widget', wBase
END
In this example, we store state information (the widget ID of the draw widget and the index of the drawable area) in a structure variable, and set the user value of the top-level base widget equal to that structure variable. This makes it possible to retrieve the structure using the widget ID contained in the TOP
field of any widget event structure that arrives at the event handler routine.
Notice the use of the NO_COPY keyword to WIDGET_CONTROL in the example. This keyword prevents IDL from duplicating the memory used by the user value during the GET_UVALUE and SET_UVALUE operations. This is an important efficiency consideration if the size of the state data is large. (In this example using NO_COPY is not really necessary, as the state data consists only of the two long integers that represent the widget IDs being passed in the state variable.)
While it is important to consider efficiency, using the NO_COPY keyword does have the side effect of causing the user value of the widget to become undefined when it is retrieved using the GET_UVALUE keyword. If the user value is not replaced before the event handler exits, the next execution of the event routine will fail, since the user value will be undefined.
Using a Pointer to the State Structure
A variation on the above technique uses an IDL pointer to contain the state variable. This eliminates the duplication of data and the need for using the NO_COPY keyword.
Consider the following example widget code:
PRO my_widget_event, event
WIDGET_CONTROL, event.TOP, GET_UVALUE=pState
Event-handling code goes here, accessing the state
structure via the retrieved pointer.
END
PRO my_widget_cleanup, wBase
; This routine is called when the application quits.
; Retrieve the state variable and free the pointer.
WIDGET_CONTROL, wBase, GET_UVALUE=pState
PTR_FREE, pState
END
PRO my_widget
; Create some widgets.
wBase = WIDGET_BASE(/COLUMN)
wDraw = WIDGET_DRAW(wBAse, XSIZE=300, YSIZE=300)
; Realize the base widget and retrieve the widget ID
; of the drawable area.
WIDGET_CONTROL, wBase, /REALIZE
WIDGET_CONTROL, wDraw, GET_VALUE=idxDraw
; Create a state structure variable.
state = {wDraw:wDraw, idxDraw:idxDraw}
; Place the state structure in a pointer and set the user
; value of the top-level base widget equal to the pointer.
pState = PTR_NEW(state, /NO_COPY)
WIDGET_CONTROL, wBase, SET_UVALUE=pState, /NO_COPY
; Call XMANAGER to manage the widgets, specifying the routine
; to be called when the application quits.
XMANAGER, 'my_widget', wBase, CLEANUP='my_widget_cleanup'
END
Notice the following differences between this technique and the technique shown in the previous example:
- This method eliminates the removal of the user value from the top-level base widget by removing using the NO_COPY keyword with the GET_UVALUE keyword to WIDGET_CONTROL. Since only the pointer (a long integer) is passed to the event routine, the efficiency issues connected with copying the value are small enough to ignore. (Note that we do use the NO_COPY keyword when creating the pointer and when initially setting the user value of the top-level base widget; since these statements are executed only once, we don’t worry about the fact that the
state
orpState
variables become undefined.) -
The state structure contained in the pointer must now be referenced using pointer-dereferencing syntax. For example, to refer to the
idxDraw
field of the state structure within the event-handling routine, you would use the syntax(*pState).idxDraw
- The pointer allocated to store the state structure must be freed when the widget application quits. We do this by specifying a cleanup routine via the CLEANUP keyword to XMANAGER. It is the cleanup routine’s responsibility to free the pointer.
Each of the above techniques has advantages. Choose a method based on the complexity of your application and your level of comfort with features like IDL pointers and the NO_COPY keyword.