X
22919 Rate this article:
No rating

Resizable Widgets

Anonym

When developing widgets, it can often be tricky to position them correctly and make them the right size. Even for an experienced programmer, it takes quite a bit of trial-and-error. Code can quickly become messy and difficult to read when sizing code is mixed in with widget creation; this is especially true when developing a cross-platform widget, which may need different sizes depending on the platform.

Here is an example of widget code that does not contain any size definitions:

  base = WIDGET_BASE(/COLUMN, TITLE='Please make some selections', $
    /BASE_ALIGN_LEFT, /TLB_SIZE_EVENTS)
  features_label = WIDGET_LABEL(base, VALUE='Check all that apply:')
  features_base = WIDGET_BASE(base, /ROW, /NONEXCLUSIVE, UNAME='features_base')
  glasses = WIDGET_BUTTON(features_base, VALUE='Wears glasses', UNAME='glasses')
  moustache = WIDGET_BUTTON(features_base, VALUE='Moustache', UNAME='moustache')
  over6ft = WIDGET_BUTTON(features_base, VALUE='Taller than 6ft', UNAME='over6ft')

  haircolor_label = WIDGET_LABEL(base, VALUE='Select Hair Color:')
  haircolor_base = WIDGET_BASE(base, /ROW, /EXCLUSIVE, UNAME='haircolor_base')
  black = WIDGET_BUTTON(haircolor_base, VALUE='Black', UNAME='black')
  brown = WIDGET_BUTTON(haircolor_base, VALUE='Brown', UNAME='brown')
  blond = WIDGET_BUTTON(haircolor_base, VALUE='Blond', UNAME='blond')
  red = WIDGET_BUTTON(haircolor_base, VALUE='Red', UNAME='red')

  descrip_label = WIDGET_LABEL(base, VALUE='Describe yourself:')
  descrip = WIDGET_TEXT(base, /EDITABLE, UNAME='descrip')

  bottom_base = WIDGET_BASE(base, /ROW, UNAME='bottom_base')
  help_base = WIDGET_BASE(bottom_base, /ROW, UNAME='help_base')
  help = WIDGET_BUTTON(help_base, VALUE='Help', UNAME='help')
  space = WIDGET_BASE(bottom_base, UNAME='space')
  commit_base = WIDGET_BASE(bottom_base, /ROW, UNAME='commit_base', /GRID_LAYOUT)
  ok = WIDGET_BUTTON(commit_base, VALUE='OK', UNAME='ok')
  cancel = WIDGET_BUTTON(commit_base, VALUE='Cancel', UNAME='cancel')

This is what the widget looks like after calling WIDGET_CONTROL, base, /REALIZE on the widget (on Windows 7):


This doesn't look bad. However, the OK and Cancel buttons are not in their usual spot, the bottom-right corner, and the text box is rather small.

One trick to creating a widget that has the desired size and layout is to write a separate routine instead of modifying the code above. Once the above code is run, simply call the new routine with the desired size before realizing the base.

Why do this in a separate routine? This routine will not only set the original size, but it can also be used to resize the widget and all of its components when the user drags the edges or corners. Allowing the user to resize the widget will provide an enhanced user-experience. In this example, the user may enter text that is longer than the width of the text box, and the ability to widen it would allow the user to view all of the text at once rather than using the arrow keys to navigate through it.

Here is an example of a routine that can be used to adjust the size of this example widget:


PRO example_widget_adjust_size, base, new_size
  COMPILE_OPT IDL2

  ; Do not allow the widget to be made any smaller than the original size.
  WIDGET_CONTROL, base, GET_UVALUE=orig_size
  IF new_size[0] LT orig_size[0] THEN new_size[0] = orig_size[0]
  IF new_size[1] LT orig_size[1] THEN new_size[1] = orig_size[1]

  ; Determine the padding, which needs to be subtracted.
  base_geom = WIDGET_INFO(base, /GEOMETRY)
  xpad = base_geom.xpad
  ypad = base_geom.ypad

  ; There is padding on both sides of the textbox, so subtract twice the xpad.
  descrip_width = new_size[0] - 2*xpad
  descrip = WIDGET_INFO(base, FIND_BY_UNAME='descrip')
  WIDGET_CONTROL, descrip, SCR_XSIZE=descrip_width

  ; Determine the size of the Help, OK and Cancel buttons. There are
  ; three bases within the bottom base, so subtract 4 times xpad.

  commit_base = WIDGET_INFO(base, FIND_BY_UNAME='commit_base')
  commit_geom = WIDGET_INFO(commit_base, /GEOMETRY)
  help_base = WIDGET_INFO(base, FIND_BY_UNAME='help_base')
  help_geom = WIDGET_INFO(help_base, /GEOMETRY)
  space_width = new_size[0] - commit_geom.xsize - help_geom.xsize - 4*xpad
  space = WIDGET_INFO(base, FIND_BY_UNAME='space')
  WIDGET_CONTROL, space, XSIZE=space_width

END

The main routine now needs to define a size and pass it, along with the base, into this routine. There are two options for choosing the original size: A fixed original size can be chosen, based on what the developer thinks is suitable, or the original size can be calculated by calling WIDGET_INFO with /GEOMETRY on each of the component items and totaling the x and y sizes. For example purposes, a fixed size will be used. When using a fixed original size, it may be a good idea to make the size slightly larger on motif widgets than on Windows because motif widgets use bigger text. 

Here is the code needed to set the original size, which should be added right above the REALIZE call:

  IF !VERSION.OS_FAMILY EQ 'Windows' THEN BEGIN
    orig_size = [300, 180]
  ENDIF ELSE BEGIN
    orig_size = [350, 200]
  ENDELSE

  ; Set the original size in the UVALE to keep track of it.
  WIDGET_CONTROL, base, SET_UVALUE=orig_size
  
  example_widget_adjust_size, base, orig_size

If the developer decides to change the original size, this is the only place that will need to be updated. Here is the new look:

Allowing the user to resize the widget now falls into place. The widget's event handler can catch the size event, which uses the WIDGET_BASE structure, and pass the new size into the resize routine.

  IF TAG_NAMES(event, /STRUCTURE_NAME) EQ 'WIDGET_BASE' THEN BEGIN
    example_widget_adjust_size, event.top, [event.x, event.y]
  ENDIF

Now the user can expand the widget, and the text box and buttons automatically adjust:

In this example, the labels, checkboxes, and radio buttons aren't changed when the widget is resized, but this technique can be applied to any widget, no matter how big or small, in order to provide a resizable widget.