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

Using Draw Widgets


Draw widgets are graphics windows that appear as part of a widget hierarchy rather than appearing as an independent window. Like other graphics windows, draw widgets can be created to use either Direct or Object graphics. (See "Graphics" in the Using IDL manual for a discussion of IDL's two graphics modes.) Draw widgets allow designers of IDL graphical user interfaces to take advantage of the full power of IDL graphics in their displays. See WIDGET_DRAW for a complete description of the function used to create draw widgets.

This section discusses the following topics:

Using Direct Graphics in Draw Widgets

By default, draw widgets use IDL Direct graphics. (To create a draw widget that uses Object graphics, set the GRAPHICS_LEVEL keyword to WIDGET_DRAW equal to two; see Using Object Graphics in Draw Widgets.) Once created, draw widgets using Direct graphics are used in the same way as standard Direct graphics windows created using the WINDOW procedure.

All IDL Direct graphics windows are referred to by a window number. Unlike windows created by the WINDOW procedure, the window number of a Direct graphics draw widget cannot be assigned by the user. In addition, the window number of a draw widget is not assigned until the draw widget is actually realized, and thus cannot be returned by WIDGET_DRAW when the widget is created. Instead, you must use the WIDGET_CONTROL procedure to retrieve the window number, which is stored in the value of the draw widget, after the widget has been realized.

Unlike normal graphics windows, creating a draw widget does not cause the current graphics window to change to the new widget. You must use the WSET procedure to explicitly make the draw widget the current graphics window. The following IDL statements demonstrate the required steps:

;Create a base widget. 
base = WIDGET_BASE() 
 
;Attach a 256 x 256 draw widget. 
draw = WIDGET_DRAW(base, XSIZE = 256, YSIZE = 256) 
 
;Realize the widgets. 
WIDGET_CONTROL, /REALIZE, base 
 
;Obtain the window index. 
WIDGET_CONTROL, draw, GET_VALUE = index 
 
;Set the new widget to be the current graphics window 
WSET, index 

If you attempt to get the value of a draw widget before the widget has been realized, WIDGET_CONTROL returns the value -1, which is not a valid index.

Using Object Graphics in Draw Widgets

To create a draw widget that uses Object graphics, set the GRAPHICS_LEVEL keyword to WIDGET_DRAW equal to two. Once created, draw widgets using Object graphics are used in the same way as standard IDLgrWindow objects.

All IDL Object graphics windows (that is, IDLgrWindow objects) are referred to by an object reference. Since you do not explicitly create the IDLgrWindow object used in a draw widget, you must retrieve the object reference by using the WIDGET_CONTROL procedure to get the value of the draw widget. As with Direct graphics draw widgets, the window object is not created-and thus the object reference cannot be retrieved-until after the draw widget is realized. If you attempt to retrieve the object reference for a draw widget's IDLgrWindow object before the draw widget is realized, IDL returns a null object.

Scrolling Draw Widgets

Another difference between a draw widget and either a graphics window created with the WINDOW procedure or an IDLgrWindow object is that draw widgets can include scroll bars. Setting the APP_SCROLL keyword or the SCROLL keyword to the WIDGET_DRAW function causes scrollbars to be attached to the drawing widget, which allows the user to view images or graphics larger than the visible area.

Differences Between SCROLL and APP_SCROLL

The amount of memory used by a draw widget is directly related to the size of the drawable area of the widget. If a draw widget does not have scroll bars, the entire drawable area is viewable. In this case, the size of the drawable area is controlled by the XSIZE and YSIZE keywords to WIDGET_DRAW.

With the addition of scroll bars, it is possible to display an image that is larger than the viewable area (the viewport) of the draw widget. IDL provides two options for dealing with images larger than the viewport:

  1. Create the draw widget using the SCROLL keyword. This method creates a draw widget whose drawable area is specified by the XSIZE and YSIZE keywords, and whose viewable area is specified by the X_SCROLL_SIZE and Y_SCROLL_SIZE keywords. Since the entire image is kept in memory, IDL can display the appropriate portions automatically when the scroll bars are adjusted.
  2. Create the draw widget using the APP_SCROLL keyword. This method creates a draw widget whose drawable area is the same size as its viewable area (specified by the X_SCROLL_SIZE and Y_SCROLL_SIZE keywords), but which can be different from the virtual drawable area (specified by the XSIZE and YSIZE keywords) that is equal to the full size of the image. In this case, only the portion of the image that is currently visible in the viewport is kept in memory; the IDL programmer must use viewport events to determine when the scroll bars have been adjusted and display the appropriate portion of the full image.

The concept of a virtual drawable area allows you to display portions of very large images in a draw widget without the need for enough memory to display the entire image. The price for this facility is the need to manually handle display of the correct portion of the image in an event-handling routine.

Example Using SCROLL

The following code creates a simple scrollable draw widget and displays an image.

Note
This example is included in the file draw_scroll.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
   draw_scroll
at the IDL command prompt. See Running the Example Code if IDL does not run the program as expected. You may need to enter DEVICE, RETAIN=2 at the IDL command prompt before running this example.

; Event-handler routine. Does nothing in this example. 
PRO draw_scroll_event, ev 
 
END 
 
; Widget creation routine. 
PRO draw_scroll 
 
  ; Read an image for use in the example. 
  READ_JPEG, FILEPATH('muscle.jpg', $ 
    SUBDIR=['examples', 'data']), image 
 
  ; Create the base widget. 
  base = WIDGET_BASE() 
 
  ; Create the draw widget. The size of the viewport is set to 
  ; 200x200 pixels, but the size of the drawable area is 
  ; set equal to the dimensions of the image array using the 
  ; XSIZE and YSIZE keywords. 
  draw = WIDGET_DRAW(base, X_SCROLL_SIZE=200, Y_SCROLL_SIZE=200, $ 
    XSIZE=(SIZE(image))[1], YSIZE=(SIZE(image))[2], /SCROLL) 
 
  ; Realize the widgets. 
  WIDGET_CONTROL, base, /REALIZE 
 
  ; Retrieve the window ID from the draw widget. 
  WIDGET_CONTROL, draw, GET_VALUE=drawID 
 
  ; Set the draw widget as the current drawable area. 
  WSET, drawID 
 
  ; Load the image. 
  TVSCL, image 
 
  ; Call XMANAGER to manage the widgets. 
  XMANAGER, 'draw_scroll', base, /NO_BLOCK 
 
END 

In this example, the drawable area created for the draw widget is the full size of the displayed image. Since IDL handles the display of the image as the scroll bars are adjusted, no event-handling is necessary to update the display.

Example using APP_SCROLL

We can easily rework the previous example to use the APP_SCROLL keyword rather than the SCROLL keyword. Using APP_SCROLL has the following consequences:

  1. IDL no longer automatically displays the appropriate portion of the image when the scroll bars are adjusted. As a result, we must add code to our event-handling procedure to check for the viewport event and display the appropriate part of the image. Here is the new event-handler routine:
  2. ; Event-handler routine. 
    PRO draw_app_scroll_event, ev 
     
      COMMON app_scr_ex, image 
     
      IF (ev.TYPE EQ 3) THEN TVSCL, image, 0-ev.X, 0-ev.Y 
     
    END 
    

    First, notice that since we need access to the image array in both the widget creation routine and the event handler, we place the array in a COMMON block. This is appropriate since the image data itself is not altered by the widget application.

    Second, we check the TYPE field of the event structure to see if it is equal to 3, which is the code for a viewport event. If it is, we use the values of the X and Y fields of the event structure as the Position arguments to the TVSCL routine to display the appropriate portion of the image array.

  3. We must add the COMMON block to the widget creation routine.
  4. We change the call to WIDGET_DRAW to include the APP_SCROLL keyword rather than the SCROLL keyword. In this context, the values of the XSIZE and YSIZE keywords are interpreted as the size of the virtual drawable area, rather than the actual drawable area.
  5. Note
    The modified example is included in the file draw_app_scroll.pro in the examples/widgets subdirectory of the IDL distribution.

On the surface the two examples appear identical. The difference is that the example using APP_SCROLL uses only the memory necessary to create the smaller drawable area described by the size of the viewport, whereas the example using SCROLL uses the memory necessary to create the full drawable area described by the XSIZE and YSIZE keywords. While the example image is not so large that this makes much difference, if the image contained several hundred million pixels rather than a few hundred thousand, the memory saving could be significant.

Context Events in Draw Widgets

The WIDGET_DRAW function does not have a CONTEXT_EVENTS keyword to specify that context menu events be generated when the user clicks the right mouse button over a drawable area. Instead, the event structure generated by draw widgets when the BUTTON_EVENTS keyword is set includes the PRESS and RELEASE fields, both of which contain information regarding which mouse button was pressed.

See Context-Sensitive Menus for techniques used to simulate the generation of context menu events with draw widgets.

Draw Widget Example

The following example program creates a small widget application consisting of a draw widget and a droplist menu. One of three plots is displayed in the draw widget depending on the selection made from the droplist. To add to dynamic behavior, we will use timer events to change the color table used in the draw window every three seconds.

Note
This example is included in the file draw_widget_example.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
   draw_widget_example
at the IDL command prompt. See Running the Example Code if IDL does not run the program as expected.

; Event-handler routine 
PRO draw_widget_example_event, ev 
 
  ; We need to save the value of the seed variable for the random 
  ; number generator between calls to the event-handling routine.  
  ; We do this using a COMMON block. 
 
  COMMON dwe, seed 
 
  ; Retrieve the anonymous structure contained in the user value of 
  ; the top-level base widget. This structure contains the 
  ; following fields: 
  ;    drawID:   the widget ID of the draw widget 
  ;   labelID:   the widget ID of the label widget that will hold 
  ;              the color table name. 
  ;   sel_index: the index of the current selection in the 
  ;              droplist 
  ;   ctable:    the index of the current color table. 
 
  WIDGET_CONTROL, ev.TOP, GET_UVALUE=stash 
 
  ; Set the draw widget as the current IDL drawable. 
 
  WSET, stash.drawID 
 
  ; Check the type of event structure returned. If it is a timer  
  ; event, change the color table index to a random number between  
  ; 0 and 40, then set the value of the label widget to the name of  
  ; the new color table. 
  ; (See Identifying Widget Type from an Event for 
  ; more on identifying widget types from returned event 
  ; structures.) 
 
  IF (TAG_NAMES(ev, /STRUCTURE_NAME) EQ 'WIDGET_TIMER') THEN BEGIN 
    LOADCT, GET_NAMES=ctnames 
    stash.ctable = FIX(RANDOMU(seed)*41) 
    LOADCT, stash.ctable, /SILENT 
    WIDGET_CONTROL, stash.labelID, $ 
      SET_VALUE='Color Table: ' + ctnames[stash.ctable] 
    WIDGET_CONTROL, ev.ID, TIMER=3.0 
  ENDIF 
 
  ; If the event is a droplist event, change the value of the  
  ; variable 'selection' to the new index value. 
 
  IF (TAG_NAMES(ev, /STRUCTURE_NAME) EQ 'WIDGET_DROPLIST') $ 
    THEN BEGIN 
    stash.sel_index=ev.index 
  ENDIF 
 
  ; Reset the user value of the top-level base widget to the 
  ; modified stash structure. 
 
  WIDGET_CONTROL, ev.TOP, SET_UVALUE=stash 
 
  ; Display a plot, surface, or shaded surface, or destroy the 
  ; widget application, depending on the value of the 'selection' 
  ; variable. 
 
  CASE stash.sel_index OF 
    0: PLOT, DIST(150) 
    1: SURFACE, DIST(150) 
    2: SHADE_SURF, DIST(150) 
    3: WIDGET_CONTROL, ev.TOP, /DESTROY 
  ENDCASE 
 
END 
 
PRO draw_widget_example 
 
  ; Define the values for the droplist widget and define the 
  ; initially selected index to show a shaded surface. 
  select = ['Plot', 'Surface', 'Shaded Surface', 'Done'] 
  sel_index = 2 
 
  ; Create a base widget containing a draw widget and a sub-base 
  ; containing a droplist menu and a label widget. 
  base = WIDGET_BASE(/COLUMN) 
  draw = WIDGET_DRAW(base, XSIZE=350, YSIZE=350) 
  base2 = WIDGET_BASE(base, /ROW) 
  dlist = WIDGET_DROPLIST(base2, VALUE=select) 
  label = WIDGET_LABEL(base2, XSIZE=200) 
 
  ; Realize the widget hierarchy, then retrieve the widget ID of 
  ; the draw widget. 
  WIDGET_CONTROL, base, /REALIZE 
  WIDGET_CONTROL, draw, GET_VALUE=drawID 
 
  ; Set the timer value of the draw widget. 
  WIDGET_CONTROL, draw, TIMER=0.0 
 
  ; Set the droplist to display the proper selection index. 
  WIDGET_CONTROL, dlist, SET_DROPLIST_SELECT=sel_index 
 
  ; Store the widget ID of the draw widget, the widget ID of  
  ; the label widget, the droplist selection index, and the  
  ; initial color table index in an anonymous structure, and  
  ; set the user value of the top-level base widget to this  
  ; structure. 
  stash = { drawID:drawID, labelID:label, $ 
            sel_index:sel_index, ctable:0} 
  WIDGET_CONTROL, base, SET_UVALUE=stash 
 
  ; Register the widget with the XMANAGER. 
  XMANAGER, 'draw_widget_example', base, /NO_BLOCK 
 
  ; Set some display device parameters. 
  DEVICE, RETAIN=2, DECOMPOSED=0 
 
END 

The intent of this example is to demonstrate the use of draw widgets, menus, and timer events with a minimum of other complicating issues. However, it is easy to imagine applications wherein a graphics window containing a plot or some other information is updated periodically by a timer. The method used here can be easily applied to more realistic situations.

Button, Motion, and Keyboard Events

To go beyond merely displaying an image in a draw widget and allow the user to interact in some way with the displayed image, you must configure the draw widget to generate either button, motion, or keyboard events:

The following example uses motion events to update the values of several label widgets as the mouse cursor moves over an image in a draw widget. This and several other features are discussed in the section following the code.

Note
This example is included in the file draw_widget_data.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
   draw_widget_data
at the IDL command prompt. See Running the Example Code if IDL does not run the program as expected. You may need to enter DEVICE, DECOMPOSED=1 at the IDL command prompt before running this example.

; Event-handling procedure. 
PRO draw_widget_data_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 event is generated in the draw widget, update the 
  ; label values with the current cursor position and the value 
  ; of the data point under the cursor. Note that since we have 
  ; passed a pointer to the image array rather than the array 
  ; itself, we must dereference the pointer in the 'image' field 
  ; of the stash structure before getting the subscripted value. 
 
  IF (TAG_NAMES(ev, /STRUCTURE_NAME) eq 'WIDGET_DRAW') THEN BEGIN 
    WIDGET_CONTROL, stash.label1, $ 
      SET_VALUE='X position: ' + STRING(ev.X) 
    WIDGET_CONTROL, stash.label2, $ 
      SET_VALUE='Y position: ' + STRING(ev.Y) 
    WIDGET_CONTROL, stash.label3, $ 
      SET_VALUE='Hex Value: ' + $ 
      STRING((*stash.imagePtr)[ev.X, ev.Y], FORMAT='(Z12)') 
  ENDIF 
 
  ; If the event is generated in a button, destroy the widget 
  ; hierarchy. We know we can use this simple test because there 
  ; is only one button in the application. 
 
  IF (TAG_NAMES(ev, /STRUCTURE_NAME) eq 'WIDGET_BUTTON') THEN BEGIN 
    WIDGET_CONTROL, ev.TOP, /DESTROY 
  ENDIF 
 
END 
 
; Widget creation routine. 
PRO draw_widget_data 
 
  ; Define a monochrome image array for use in the application. 
  READ_PNG, FILEPATH('mineral.png', $ 
    SUBDIR=['examples', 'data']), image 
 
  ; Place the image array in a pointer heap variable, so we can 
  ; pass the pointer to the event routine rather than passing the 
  ; entire image array. 
  imagePtr=PTR_NEW(image, /NO_COPY) 
 
  ; Retrieve the size information from the image array. 
  im_size=SIZE(*imagePtr) 
 
  ; Create a base widget to hold the application. 
  base = WIDGET_BASE(/COLUMN) 
 
  ; Create a draw widget based on the size of the image, and 
  ; set the MOTION_EVENTS keyword so that events are generated 
  ; as the cursor moves across the image. Setting the BUTTON_EVENTS 
  ; keyword rather than MOTION_EVENTS would require the user to 
  ; click on the image before an event is generated. 
  draw = WIDGET_DRAW(base, XSIZE=im_size[1], YSIZE=im_size[2], $ 
    /MOTION_EVENTS) 
 
  ; Create 'Done' button. 
  button = WIDGET_BUTTON(base, VALUE='Done') 
 
  ; Create label widgets to hold the cursor position and 
  ; Hexadecimal value of the pixel under the cursor. 
  label1 = WIDGET_LABEL(base, XSIZE=im_size[1]*.9, $ 
    VALUE='X position:') 
  label2 = WIDGET_LABEL(base, XSIZE=im_size[1]*.9, $ 
    VALUE='Y position:') 
  label3 = WIDGET_LABEL(base, XSIZE=im_size[1]*.9, $ 
    VALUE='Hex Value:') 
 
  ; Realize the widget hierarchy. 
  WIDGET_CONTROL, base, /REALIZE 
 
  ; Retrieve the widget ID of the draw widget. Note that the widget 
  ; hierarchy must be realized before you can retrieve this value. 
  WIDGET_CONTROL, draw, GET_VALUE=drawID 
 
  ; Create an anonymous array to hold the image data and widget IDs 
  ; of the label widgets. 
  stash = { imagePtr:imagePtr, label1:label1, label2:label2, $ 
            label3:label3 } 
 
  ; Set the user value of the top-level base widget equal to the 
  ; 'stash' array. 
  WIDGET_CONTROL, base, SET_UVALUE=stash 
 
  ; Make the draw widget the current IDL drawable area. 
  WSET, drawID 
 
  ; Draw the image into the draw widget. 
  TVSCL, *imagePtr 
 
  ; Call XMANAGER to manage the widgets. 
  XMANAGER, 'draw_widget_data', base, /NO_BLOCK 
 
END 

The following things about this example are worth noting:


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