11783 Rate this article:
No rating

Aligning widgets


(Note: Today, since I’m giving a webinar, we have another guest post by Jim Pendleton, an Advisory Consultant in the Exelis VIS Professional Services Group. I indented his code for him. –MP) Working with IDL’s widget geometry can be challenging at times. There are many platform- and font-specific issues that often need to be taken into account when writing a UI that will present well across all IDL’s target devices. Additionally, your end user will often have their desktop customized in such a way that you never dreamt possible or desirable. The upshot is that you can waste a lot of energy tweaking an interface to be exactly the way you want it on your box via bulletin board bases and specific pixel offsets, but it’ll render in nightmarish fashion on another box. Some customers are willing to pay a lot of money for pretty and they should be encouraged to continue doing so. Others would rather have their development budget directed toward having their problem solved. One approach for an 80% solution is to align widgets based on their calculated geometries at runtime. Consider the following example which displays 5 rows of widgets within the context of a column base. The rows will have differing numbers of items, one row of 2 items, one row of 3 items, and three rows of 4 items.

 pro ExampleBase, ALIGN = ALIGN compile_opt idl2 TLB = widget_base(/COLUMN, TITLE = $ (['Poorly', 'Properly'])[keyword_set(ALIGN)] + ' Aligned Base') ; Row 0 = 2 columns row0 = widget_base(TLB, /ROW) label = widget_label(row0, VALUE = 'Parameter') label = widget_label(row0, VALUE = 'Value') ; Row 1 = 3 columns row1 = widget_base(TLB, /ROW) label = widget_label(row1, VALUE = 'Select a file') t1 = widget_text(row1, VALUE = 'a/filename/here') b1 = widget_button(row1, /BITMAP, $ VALUE = filepath('open.bmp', SUBDIR=['resource', 'bitmaps']), $ /ALIGN_LEFT) ; Row 2 = 4 columns row2 = widget_base(TLB, /ROW) label = widget_label(row2, VALUE = 'Select one from among many') t = widget_combobox(row2, $ VALUE = ['primary option', 'a much longer option here']) blank = widget_label(row2, VALUE = '') label = widget_label(row2, VALUE = 'Additional expository text') ; Row 3 = 4 columns row3 = widget_base(TLB, /ROW) label = widget_label(row3, VALUE = '') label = widget_label(row3, VALUE = '') label = widget_label(row3, VALUE = '') label = widget_label(row3, VALUE = 'broken into two lines.') ; Row 4 = 4 columns row4 = widget_base(TLB, /ROW) label = widget_label(row4, VALUE = 'Status = READY', /SUNKEN_FRAME) blank = widget_label(row4, VALUE = '') blank = widget_label(row4, VALUE = '') button = widget_button(row4, VALUE = 'Cancel') if (keyword_set(ALIGN)) then begin AlignColumns, TLB endif widget_control, TLB, /REALIZE end 

Notice that this code does not attempt to tweak any of the widget sizes to specific widths and heights. By and large, doing so would be total waste of your time unless you have total control over the final display environment and/or a large budget. By itself it produces the following UI, which is quite awful if I do say so myself. IDL> examplebase Jim Pendleton: a poorly aligned widget base Our goal is to align the output into a grid. The GRID_LAYOUT keyword to WIDGET_BASE is not applicable in this case because the items with which we’re working are embedded in other bases. Additionally, we don’t want all the items to be the same width. Feel free to give GRID_LAYOUT a try as an independent exercise. Consider the following utility routine which takes as input the widget ID of a column base and assumes it is populated by row bases whose items we want to align. The base ID does not need to be that of a top-level base. For each row (the X direction), we want to find the width of each item. We’ll call this a column entry. Over the columns (the Y direction), we want to find the maximum width in each. Finally, we’ll make a second pass back over our rows and adjust each item to be the maximum width for that column.

 pro AlignColumns, ColumnBase compile_opt idl2 ; The outer loop iterates over the rows in the column base. columnmaxwidths = [] child = widget_info(ColumnBase, /CHILD) while (widget_info(child, /VALID_ID)) do begin ; The inner loop iterates over the widgets in each row. ; The number of widgets in this base is interpreted ; as the number of columns. columncount = 0L grandchild = widget_info(child, /CHILD) while (widget_info(grandchild, /VALID_ID)) do begin ; What is the screen width in pixels of this widget? width = (widget_info(grandchild, /GEOMETRY)).scr_xsize ; If this is a new column, record its width, otherwise ; take the maximum of this width and the previous ; recorded maximum width. if (n_elements(columnmaxwidths) lt columncount + 1) then begin columnmaxwidths = [columnmaxwidths, width] endif else begin columnmaxwidths[columncount] >= width endelse columncount++ grandchild = widget_info(grandchild, /SIBLING) endwhile child = widget_info(child, /SIBLING) endwhile ; Pass 2: Set the screen width of each widget equal ; to the maximum width found per column child = widget_info(ColumnBase, /CHILD) while (widget_info(child, /VALID_ID)) do begin columncount = 0L grandchild = widget_info(child, /CHILD) while (widget_info(grandchild, /VALID_ID)) do begin widget_control, grandchild, SCR_XSIZE = columnmaxwidths[columncount] columncount++ grandchild = widget_info(grandchild, /SIBLING) endwhile child = widget_info(child, /SIBLING) endwhile end

The “magic” here is encapsulated in the following:

  • WIDGET_INFO can be used to return a GEOMETRY structure of a widget
  • The geometry’s SCR_XSIZE returns the screen size, in pixels, of a widget
  • The SCR_XSIZE keyword to WIDGET_CONTROL can be used to explicitly set the width of a widget

We don’t add any new functionality to our original user interface, but we greatly increase its ease of comprehension. IDL> examplebase, /align Jim Pendleton: a properly aligned widget base Feel free to modify the code for more general cases. Note that:

  • This example only looks one level deep for child widgets. You might consider recursion.
  • It doesn’t account for widget heights, which may also be important to you.
  • It doesn’t take into account other geometry items such as spacing and padding.
  • You may also see different results if the utility is called post-realization rather than pre-realization. (That is, WIDGET_CONTROL, /REALIZE is called before AlignColumns rather than after.)
  • Consider a mechanism to indicate to the utility routine that a widget should occupy more than one column, such as the “status” label in the bottom-most row.

(Thanks, Jim! –MP)