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:

Example: A Simple Tab Widget


The following procedures build a simple tabbed interface with three tabs containing a variety of other widgets.

This example is included in the file tab_widget_example1.pro in the examples/doc/widgets subdirectory of the IDL distribution. Run this example procedure by entering tab_widget_example1 at the IDL command prompt or view the file in an IDL Editor window by entering .EDIT tab_widget_example1.pro. 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 section, 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.

Tab Sizing and Multiline Behavior


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_TABfor details), leads to the following behaviors:

Windows Behavior


Tabs are created to show the entire text of the TITLE keyword to WIDGET_BASE.

If LOCATION = 0 or 1

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).

If LOCATION = 2 or 3

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: The width or height of the tab widget is based on the width or height of the largest base widget that is a child of the tab widget. If the width of the text of one tab exceeds the width or height of the tab widget, the text will be truncated even if the MULTILINE keyword is set.

Motif Behavior


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.

Tips for Tab Layout


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.

  • Keep tab titles short. If you need a long description of the contents of a tab, use a label widget in the tab’s base widget rather than creating a long title.
  • Set the MULTILINE keyword equal to a value greater than one. This allows you to tune the appearance of your tab set to the Motif platform without changing the appearance under Windows, since any value greater than zero will result in a multiline tab display under Windows.
  • If practical, place the tabs along the longest dimension of the tab widget, as determined by the size of the largest base widget.

Example: Retrieving Values


The following example builds on Example: A Simple Tab Widget by adding the following features:

  • “Next” and “Previous” buttons that switch the tab display to the next (or previous) tab in the tab set.
  • A mechanism for saving the values of the widgets in the tab interface. Implementing such a mechanism allows the user to view and change all of the settings accessible via the tab widget before committing any of them.
  • A mechanism for canceling. It exits from the tabbed interface without committing any changes made via the tab interface.

This example is included in the file tab_widget_example2.pro in the examples/doc/widgets subdirectory of the IDL distribution. Run this example procedure by entering tab_widget_example2 at the IDL command prompt or view the file in an IDL Editor window by entering .EDIT tab_widget_example2.pro. 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:

  • The 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.
  • Similarly, if the user’s changes are not sent to the larger application until he or she clicks the “Done” button, it is important to provide a way for the user to cancel the operation entirely, without sending any changes.
  • In most cases, when we refer to a field in a structure, we refer to it by its name. The event function 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.
  • The “Next” and “Previous” buttons in this example imply that there is an order to the actions performed using the tab set. While there is no order in this example, it is easy to imagine a situation in which values from one tab would influence actions taken on another. You could even require that some action be taken on a given tab before a later tab could be displayed.