Categories | Alphabetical | Classes | All Contents | [ < ] | [ > ]

Using Tab Widgets


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.

Note
This example is included in the file 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.

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_TAB for 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.

Example: Retrieving Values

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

Note
This example is included in the file 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:


Categories | Alphabetical | Classes | All Contents | [ < ] | [ > ]