Tab widgets create a "tabbed" interface that allows the user to select one of a list of rectangular display areas to be displayed in a single space (the tab set). The displayed interface elements are contained in base widgets - that is, selecting a tab displays the contents of a specified base widget within the tabbed interface. See WIDGET_TAB for a complete description of the function used to create tab widgets.
This section discusses the following topics:
The following procedures build a simple tabbed interface with three tabs containing a variety of other widgets.
| Note |
tab_widget_example1.pro in the examples/widgets subdirectory of the IDL distribution. You can either open the file in an IDL editor window and compile and run the code using items on the Run menu, or simply enter tab_widget_example1
at the IDL command prompt. See Running the Example Code if IDL does not run the program as expected.
; Simple event-handler routine
PRO tab_widget_example1_event, ev
; Retrieve the anonymous structure contained in the user value of
; the top-level base widget.
WIDGET_CONTROL, ev.TOP, GET_UVALUE=stash
; If the user clicked the 'Done' button, destroy the widgets.
IF (ev.ID EQ stash.bDone) THEN WIDGET_CONTROL, ev.TOP, /DESTROY
END
; Widget creation routine.
PRO tab_widget_example1, LOCATION=location
; Create the top-level base and the tab.
wTLB = WIDGET_BASE(/COLUMN, /BASE_ALIGN_TOP)
wTab = WIDGET_TAB(wTLB, LOCATION=location)
; Create the first tab base, containing a label and two
; button groups.
wT1 = WIDGET_BASE(wTab, TITLE='TAB 1', /COLUMN)
wLabel = WIDGET_LABEL(wT1, VALUE='Choose values')
wBgroup1 = CW_BGROUP(wT1, ['one', 'two', 'three'], $
/ROW, /NONEXCLUSIVE, /RETURN_NAME)
wBgroup2 = CW_BGROUP(wT1, ['red', 'green', 'blue'], $
/ROW, /EXCLUSIVE, /RETURN_NAME)
; Create the second tab base, containing a label and
; a slider.
wT2 = WIDGET_BASE(wTab, TITLE='TAB 2', /COLUMN)
wLabel = WIDGET_LABEL(wT2, VALUE='Move the Slider')
wSlider = WIDGET_SLIDER(wT2)
; Create the third tab base, containing a label and
; a text-entry field.
wT3 = WIDGET_BASE(wTab, TITLE='TAB 3', /COLUMN)
wLabel = WIDGET_LABEL(wT3, VALUE='Enter some text')
wText= WIDGET_TEXT(wT3, /EDITABLE, /ALL_EVENTS)
; Create a base widget to hold the 'Done' button, and
; the button itself.
wControl = WIDGET_BASE(wTLB, /ROW)
bDone = WIDGET_BUTTON(wControl, VALUE='Done')
; Create an anonymous structure to hold widget IDs. This
; structure becomes the user value of the top-level base
; widget.
stash = { bDone:bDone }
; Realize the widgets, set the user value of the top-level
; base, and call XMANAGER to manage everything.
WIDGET_CONTROL, wTLB, /REALIZE
WIDGET_CONTROL, wTLB, SET_UVALUE=stash
XMANAGER, 'tab_widget_example1', wTLB, /NO_BLOCK
END
Calling tab_widget_example1 with the LOCATION keyword set to an integer value between 0 and 4 displays the same interface with the tabs placed on different sides.
As with many of the examples in this chapter, this one is designed to merely exhibit the features of the tab widget. Most of the useful things you might do with a tab widget take place in the event handling routines for the individual widgets displayed on each tab; see Example: Retrieving Values for a more complicated example that stores the values of the individual widgets for later use.
The size of the rectangular area of the tab display (where individual widgets are placed) is determined by the size of the largest base widget included in the tab set. The size of the "tab" itself (the curved area that sticks out from the rectangular base and contains the tab's title) is determined by a number of factors, including the size of other tabs, the presence of the LOCATION and MULTILINE keywords, and the platform on which the widget application is running.
IDL attempts to create a tab that is large enough to contain the tab's title (which is set via the TITLE keyword to WIDGET_BASE for the base widget that has the tab widget as its parent). This, coupled with the fact that the value of the MULTILINE keyword has different meanings on different platforms (see WIDGET_TAB for details), leads to the following behaviors:
Tabs are created to show the entire text of the TITLE keyword to WIDGET_BASE.
Setting the LOCATION keyword to WIDGET_TAB equal to zero places the tabs on the top of the tab set; setting LOCATION to one places the tabs on the bottom of the tab set. In either case, if the MULTILINE keyword is set equal to zero, and the width of the tabs exceeds the width of the largest child base widget, the tabs are shown with scroll buttons. This allows the user to scroll through the tabs while the base widget stays immobile.
If the MULTILINE keyword is set to a positive value, the tabs will be placed in as many rows as are necessary in order to display the entire text of each tab (limited by the width of the largest base, see note below).
Setting the LOCATION keyword to WIDGET_TAB equal to two places the tabs on the left edge of the tab set; setting LOCATION equal to three places the tabs on the right edge of the tab set. In either case, a multiline display is always used if the width of the tabs exceeds the height of the largest child base widget, even if the MULTILINE keyword is set equal to zero. Tabs are placed in as many rows as are necessary in order to display the entire text of each tab (limited by the height of the largest base, see note below).
| Note |
Motif platforms interpret the value of the MULTILINE keyword to be the maximum number of tabs to display per row. If the keyword is not specified or is explicitly set equal to zero, all tabs are placed on the same row. Tabs are created to show the entire text of the TITLE keyword to WIDGET_BASE. The text of the tabs is not truncated in order to make the tabs fit the space available, unless the text of a single tab exceeds the width or height of the largest base widget that is a child of the tab widget. This means that if the MULTILINE keyword is set to any value other than one, some tabs may not be displayed.
There is no good way to determine in advance the best setting for the MULTILINE keyword to ensure an appropriate tab display. In most cases, however, the following suggestions should enable you to create a tab display that is useful on both Windows and UNIX platforms.
The following example builds on Example: A Simple Tab Widget by adding the following features:
| Note |
tab_widget_example2.pro in the examples/widgets subdirectory of the IDL distribution. You can either open the file in an IDL editor window and compile and run the code using items on the Run menu, or simply enter tab_widget_example2
at the IDL command prompt. See Running the Example Code if IDL does not run the program as expected.
; Main event-handler routine
PRO tab_widget_example2_event, ev
; Retrieve the anonymous structure contained in the user value of
; the top-level base widget.
WIDGET_CONTROL, ev.TOP, GET_UVALUE=stash
; Retrieve the total number of tabs in the tab widget and
; the index of the current tab.
numTabs = WIDGET_INFO(stash.TopTab, /TAB_NUMBER)
thisTab = WIDGET_INFO(stash.TopTab, /TAB_CURRENT)
; If the current tab is the first tab, desensitize the
; 'Previous' button.
IF (thisTab EQ 0) THEN BEGIN
WIDGET_CONTROL, stash.bPrev, SENSITIVE=0
ENDIF ELSE BEGIN
WIDGET_CONTROL, stash.bPrev, SENSITIVE=1
ENDELSE
; If the current tab is the last tab, desensitize the
; 'Next' button.
IF (thisTab EQ numTabs - 1) THEN BEGIN
WIDGET_CONTROL, stash.bNext, SENSITIVE=0
ENDIF ELSE BEGIN
WIDGET_CONTROL, stash.bNext, SENSITIVE=1
ENDELSE
; If the user clicked either the 'Next' or 'Previous' button,
; cycle through the tabs by calling the 'TWE2_SwitchTab'
; procedure.
IF (ev.ID EQ stash.bNext) THEN $
TWE2_SwitchTab, thisTab, numTabs, stash, /NEXT
IF (ev.ID EQ stash.bPrev) THEN $
TWE2_SwitchTab, thisTab, numTabs, stash, /PREV
; If the user clicked the 'Done' button, print out the values of
; the various widgets, as contained in the 'retStruct'
; structure. In a real application, this step would probably
; adjust settings in the application to reflect the changes made
; by the user. Finally, destroy the widgets.
IF (ev.ID EQ stash.bDone) THEN BEGIN
PRINT, 'BGroup1 selected indices: ', stash.retStruct.BGROUP1
PRINT, 'BGroup2 selected index: ', stash.retStruct.BGROUP2
PRINT, 'Slider value: ', stash.retStruct.SLIDER
PRINT, 'Text value: ', stash.retStruct.TEXT
WIDGET_CONTROL, ev.TOP, /DESTROY
ENDIF
; If the user clicked the 'Cancel' button, print out a message
; and destroy the widgets. In a real application, this step would
; allow the user to discard any changes made via the tabbed
; interface before sending them to the application.
IF (ev.ID EQ stash.bCancel) THEN BEGIN
PRINT, 'Update Cancelled'
WIDGET_CONTROL, ev.TOP, /DESTROY
ENDIF
END
; Event function to store the value of a widget in the correct
; field of the 'retStruct' structure. Note that rather than
; referring to the structure fields by name, we refer to them
; by index. This allows us to save the index value of the
; appropriate structure field in the user value of the widget
; that generates the event, which in turn allows us to use the
; same function to save the values of all of the widgets whose
; values we want to save.
;
FUNCTION TWE2_saveValue, ev
; Get the 'stash' structure.
WIDGET_CONTROL, ev.TOP, GET_UVALUE=stash
; Get the value and user value from the widget that
; generated the event.
WIDGET_CONTROL, ev.ID, GET_VALUE=val, GET_UVALUE=uval
; Set the value of the correct field in the 'retStruct'
; structure, using the field's index number (stored in
; the widget's user value).
stash.retStruct.(uval) = val
; Reset the top-level widget's user value to the updated
; 'stash' structure.
WIDGET_CONTROL, ev.TOP, SET_UVALUE=stash
END
; Procedure to cycle through the tabs when the user clicks
; the 'Next' or 'Previous' buttons.
PRO TWE2_SwitchTab, thisTab, numTabs, stash, NEXT=NEXT, PREV=PREV
; If user clicked the 'Next' button, we can just add one to
; the current tab number and use the MOD operator to cycle
; back to the first tab.
IF KEYWORD_SET(NEXT) THEN nextTab = (thisTab + 1) MOD numTabs
; If the user clicked the 'Previous' button, we must explicitly
; handle the case when the user is on the first tab.
IF KEYWORD_SET(PREV) THEN BEGIN
IF (thisTab EQ 0) THEN BEGIN
nextTab = numTabs - 1
ENDIF ELSE BEGIN
nextTab = (thisTab - 1)
ENDELSE
ENDIF
; Display the selected tab.
WIDGET_CONTROL, stash.TopTab, SET_TAB_CURRENT=nextTab
END
; Widget creation routine.
PRO tab_widget_example2, LOCATION=location
; Create the top-level base and the tab.
wTLB = WIDGET_BASE(/COLUMN, /BASE_ALIGN_TOP)
wTab = WIDGET_TAB(wTLB, LOCATION=location)
; Create the first tab base, containing a label and two
; button groups. For the button groups, set the user value
; equal to the index of the field in the 'retStruct' structure
; that will hold the widget's value. Specify the
; 'TWE2_saveValue' function as the event-handler.
wT1 = WIDGET_BASE(wTab, TITLE='TAB 1', /COLUMN)
wLabel = WIDGET_LABEL(wT1, VALUE='Choose values')
wBgroup1 = CW_BGROUP(wT1, ['one', 'two', 'three'], $
/ROW, /NONEXCLUSIVE, /RETURN_NAME, UVALUE=0, $
EVENT_FUNC='TWE2_saveValue')
wBgroup2 = CW_BGROUP(wT1, ['red', 'green', 'blue'], $
/ROW, /EXCLUSIVE, /RETURN_NAME, UVALUE=1, $
EVENT_FUNC='TWE2_saveValue')
; Create the second tab base, containing a label and
; a slider. For the slider, set the user value equal
; to the index of the field in the 'retStruct' structure
; that will hold the widget's value. Specify the
; 'TWE2_saveValue' function as the event-handler.
wT2 = WIDGET_BASE(wTab, TITLE='TAB 2', /COLUMN)
wLabel = WIDGET_LABEL(wT2, VALUE='Move the Slider')
wSlider = WIDGET_SLIDER(wT2, UVALUE=2, $
EVENT_FUNC='TWE2_saveValue')
; Create the third tab base, containing a label and
; a text-entry field. for the text widget, set the user
; value equal to the index of the field in the 'retStruct'
; structure that will hold the widget's value. Specify the
; 'TWE2_saveValue' function as the event-handler.
wT3 = WIDGET_BASE(wTab, TITLE='TAB 3', /COLUMN)
wLabel = WIDGET_LABEL(wT3, VALUE='Enter some text')
wText= WIDGET_TEXT(wT3, /EDITABLE, /ALL_EVENTS, UVALUE=3, $
EVENT_FUNC='TWE2_saveValue')
; Create a base widget to hold the navigation and 'Done' buttons,
; and the buttons themselves. Since the first tab is displayed
; initially, make the 'Previous' button insensitive to start.
wControl = WIDGET_BASE(wTLB, /ROW)
bPrev = WIDGET_BUTTON(wControl, VALUE='<< Prev', SENSITIVE=0)
bNext = WIDGET_BUTTON(wControl, VALUE='Next >>')
bDone = WIDGET_BUTTON(wControl, VALUE='Done')
bCancel = WIDGET_BUTTON(wControl, VALUE='Cancel')
; Create an anonymous structure to hold the widget value data
; we are interested in retrieving. Note that we will refer to
; the structure fields by their indices rather than their
; names, so order is important.
retStruct={ BGROUP1:[0,0,0], BGROUP2:0, SLIDER:0L, TEXT:'empty'}
; Create an anonymous structure to hold widget IDs and the
; data structure. This structure becomes the user value of the
; top-level base widget.
stash = { bDone:bDone, bCancel:bCancel, bNext:bNext, $
bPrev:bPrev, TopTab:wTab, retStruct:retStruct}
; Realize the widgets, set the user value of the top-level
; base, and call XMANAGER to manage everything.
WIDGET_CONTROL, wTLB, /REALIZE
WIDGET_CONTROL, wTLB, SET_UVALUE=stash
XMANAGER, 'tab_widget_example2', wTLB, /NO_BLOCK
END
The following things about this example are worth noting:
retStruct structure is an example of the kind of information you might pass out of a tab widget, back to a larger widget application. Using an approach like the one here allows the user to set a group of values before sending any of them to the larger application. This may be more efficient than updating the larger application "on the fly" as the user makes changes to the widgets in the tab interface.TWE2_saveValue refers to the fields of the retStruct structure by their indices instead. We do this because while it is not possible to pass the field name in a variable, it is possible to pass the integer index value. Passing the index value of the appropriate field in the retStruct structure as the user value of the widget whose value is being saved allows us to write a single TWE2_saveValue function, rather than one function for each field in the retStruct structure.