|
|
|
|||||
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
| |
Efficient
Programming with PV-WAVE® Widgets
The core purpose of this document is to enlighten the software developer as to a more efficient ways of making use of PV-WAVE Widgets so that their applications may grow to be more robust and usable. This paper is based on the development experiences of engineers using PV-WAVE Widgets. Introduction Improper development of applications with PV-WAVE Widgets can lead to the generation of unwanted errors like: "Temporary variable limit exceeded." This error is most commonly seen in PV-WAVE Widget intensive applications which do not take advantage of "re-usable" widgets. Re-usable widgets are merely a different programming practice in PV-WAVE Widgets and not a new feature set in PV-WAVE. The following sections describe why this practice is necessary, how to implement this philosophy and the expected results when implemented. Misconceptions of PV-WAVE Widget Development Practices Destroying Widgets
Multiple Instances May be Useless What is a Re-Usable Widget?
Create Once, Use Many
Benefits: Low Memory Overhead
Benefits: Quick Updates to Dialogs
Basic overview of a re-usable dialog
Creation Component If the dialog has not been created the dialog will create the instance of the dialog, save the dialog's shell widget_id and all widget_ids in the dialog that display information, usually in a common block, and then update the dialog with the proper values. Notice there should be no setting of widget values in the creation routine. It is best to let the update component handle this task.
Update Component
OK/Apply/Cancel Component Supporting routines in PV-WAVE There are several routines in PV-WAVE that are crucial to creating re-usable widgets.
WoGenericDialog & WwGenericDialog with /NoDestroy
WwSetValues /Size, /Position, /Hide, /Show Examples in PV-WAVE
Navigator: Tools & Template Menus Step 1: Define the menus in navmenus.pro to have one or no items, as seen in the code section below:
nav_menu = {, $ . . .
NAME: ['Tools', 'Tools'], $
MENUBUTTON:'', $
MENU:{,CALLBACK:'NavToolCB',$
NAME: 'sel_var',$
BUTTON: '', $
SEPARATOR: 1 $
}, $
NAME: ['Defaults','Defaults'], $
MENUBUTTON:'', $
MENU:{,CALLBACK:'NavToolDefaultsCB',$
NAME: 'Defaults_1',$
BUTTON: '' $
}, $ . . .
Step 2: Create the menu via WoMenuBar in the navigator.pro and set the methods to be used to update the menus in the TM layer;
; Definition of the menu structure to be used with WoMenuBar
; ---------------------------------------------------------
@navmenus
; 1/ Specify the NAV menu bar.
; --------------------------------------------------------------------
bar = WoMenuBar(layout, unique_name, nav_menu, $
/Graphics, /Top, /Left, /Right)
; Set up the necessary attributes for the Tool menu
; -------------------------------------------------
tmp = TmSetAttribute(unique_name, 'MENUBAR', 'TOOL_MENU', 2)
tmp = TmSetAttribute(unique_name, 'MENUBAR', 'TOOL_COUNT', 0)
TmSetGraelMethod, unique_name, 'MENUBAR', $
'NAV_TOOL_UPDATE', 'NavToolList'
; Set up the necessary attributes for the Defaults menu
; ------------------------------------------------------
tmp = TmSetAttribute(tool_name, 'MENUBAR', 'TEMPLATE_MENU', 3)
tmp = TmSetAttribute(tool_name, 'MENUBAR', 'TEMPLATE_COUNT', 0)
TmSetGraelMethod, tool_name, 'MENUBAR', $
'NAV_TEMPLATE_UPDATE', 'NavToolDefaultsList'
Step 3: Code the method used to update the menu. This example is for the Template menu, found in navigator.pro:
PRO NavToolDefaultsList, tool_name, grael_name
; Forward declaration of TM routines
; -------------------------------------
DECLARE FUNC, TmGetAttribute
DECLARE FUNC, TmSetAttribute
DECLARE FUNC, TmEnumerateToolNames
; Get the list of menu IDs from the tool manager and subscript with
; the stored window menu index. This will give us the ID of the
; menu to add/remove variable names from.
; -----------------------------------------------------------------
menu_ids = TmGetAttribute(tool_name, grael_name, 'MENU_IDS', Default='')
IF SIZE(menu_ids, /Type) EQ 7 THEN RETURN
menu = TmGetAttribute(tool_name, grael_name, 'TEMPLATE_MENU', $
Default=0)
IF menu EQ 0 THEN RETURN
main_id = menu_ids(menu)
list_count = TmGetAttribute(tool_name, grael_name, 'TEMPLATE_COUNT', $
Default=0)
; Now replace/delete items in the current variable list according to
; what needs to be done.
; ------------------------------------------------------------------
items = TmGetAttribute(tool_name, 'CONFIG', 'TOOL_NAMES')
tool_count = N_ELEMENTS(items)
IF list_count GT tool_count THEN $ ;More menus than tools
last = tool_count $
ELSE $ ;More tools than menus
last = list_count
; Update to the minimum of the current number of items in the list
; or the entire list length.
; ----------------------------------------------------------------
last = MAX([last, 1])
FOR i = 1, last DO BEGIN
label_txt = TmGetAttribute(tool_name, STRUPCASE(items(i-1)), $
'BUTTON_LABEL', Default=items(i-1))
IF STRTRIM(label_txt, 2) EQ '' THEN $
label_txt = 'NO TOOLS' $
ELSE $
label_txt = label_txt + '...'
status = WwMenuItem(main_id, i, label_txt, 'NavToolDefaultsCB', /Update)
; Add the resources
; -----------------
child = WwGetValue(main_id, /Children)
mnemon = TmGetAttribute(tool_name, STRUPCASE(items(i-1)), 'MNEMONIC')
mnemon = BYTE(STRTRIM(mnemon, 2))
tmp = WtSet(child(i-1), {,mnemonic:mnemon(0)})
ENDFOR
; If any items are left, add them to the end of the menu
; ------------------------------------------------------
IF tool_count GT list_count THEN BEGIN
child = WwGetValue(main_id, /Children)
IF (STRMATCH(!Version.os, 'Windows')) AND $
(list_count EQ 0) THEN $
special = 1 $
ELSE $
special = 0
FOR i = list_count+1+special, tool_count DO BEGIN
label_txt = TmGetAttribute(tool_name, STRUPCASE(items(i-1)), $
'BUTTON_LABEL')
IF STRTRIM(label_txt, 2) EQ '' THEN $
label_txt = items(i-1)
label_txt = label_txt + '...'
IF (i LE N_ELEMENTS(child)) AND $
(NOT STRMATCH(!Version.os, 'Windows')) THEN BEGIN
status = WwMenuItem(main_id, i, label_txt, 'NavToolDefaultsCB', $
/Update)
status = WwSetValue(child(i-1),/Show)
ENDIF ELSE BEGIN
status = WwMenuItem(main_id, i, label_txt, 'NavToolDefaultsCB',/Add)
ENDELSE
; Add the resources
; -----------------
child = WwGetValue(main_id, /Children)
mnemon = TmGetAttribute(tool_name, STRUPCASE(items(i-1)), 'MNEMONIC')
mnemon = BYTE(STRTRIM(mnemon,2))
tmp = WtSet(child(i-1), {,mnemonic:mnemon(0)})
ENDFOR
; If we have too many items in the list, delete the remaining items.
; ------------------------------------------------------------------
ENDIF ELSE BEGIN
IF list_count GT tool_count THEN BEGIN
child = WwGetValue(main_id, /Children)
FOR i = tool_count+1, list_count DO BEGIN
IF STRMATCH(!Version.os, 'Windows') THEN BEGIN
;status = WwMenuItem(main_id, i-1, /Delete)
status = WwMenuItem(main_id, i, /Delete)
ENDIF ELSE BEGIN
status = WwSetValue(child(i-1), /Hide)
ENDELSE
ENDFOR
ENDIF
ENDELSE
; Update the variable list count
; ------------------------------
status = TmSetAttribute(tool_name, grael_name, 'TEMPLATE_COUNT', tool_count)
RETURN
END
You will notice in this example there is a call to WtSet to set some of the resources for the menu items (i.e. mnemonic, etc.) The general calling of WtSet is not a supported cross-platform widget utility, but in this case these specific resources are available on all platforms. You will also notice that the WwMenuItem utility is used to update the menu items with their new strings, WwSetValue could have been used safely instead. Navigator: Selected VDA Tool Dialog The following example shows the steps which were taken to make the "Selected VDA Tool" dialog re-usable in the PV-WAVE: Navigator. Step 1: Creation Component: Create routine which will create the initial instance of the dialog in navigators.pro with the desired widget (i.e. text areas, layouts, option menus, etc.), as seen in the code section below:
PRO NavToolDialog, tool_name, grael_name, user_data
; Forward declaration for TM routines
; -----------------------------------
DECLARE FUNC, TmGetTop
DECLARE FUNC, TmGetAttribute
DECLARE FUNC, TmSetAttribute
DECLARE FUNC, TmGetMessage
DECLARE FUNC, WoBuildResourceFilename
; Set the wait cursor
; -------------------
WoSetCursor, tool_name, /Wait
wid = TmGetAttribute(tool_name, 'DIALOGS', 'VTOOL_WID', Default=0 ; NOTE: This is the check to see if the dialog has been created yet. If it has already been created then just call the update routine. Notice that the Navigator is using the TM layer as its common block to store the widget_ids.
; If the dialog has already been created then update and show it
; --------------------------------------------------------------
IF wid NE 0 THEN BEGIN
NavToolDialogUpdate, tool_name, grael_name
; Show the updated dialog
; -----------------------
tmp = WwSetValue(wid, /Show)
; Return the cursor to normal
; ---------------------------
WoSetCursor, tool_name, /System
RETURN
ENDIF
; NOTE: This section actually creates the dialog.
; Uses WoGenericDialog to set up a dialog box w/ [Ok/Apply/Cancel/Help]
; ---------------------------------------------------------------------
parent = TmGetTop(tool_name)
buttons = lonarr(4)
title = TmGetMessage('NAV_VToolDialogTitle')
helpfile= TmGetAttribute(tool_name, 'TM_HELP', 'HELP_FILE')
topic = TmGetMessage('NAV_VToolDialogHelpTopic')
dialog = WoGenericDialog(parent, layout, $
'NavToolDialogBtnCB', $
Dialog_name='NavToolDialog', $
/Board, $
/OK, /Apply, /Reset, /Cancel, $
/NoDestroy, /Block, $
Help=[topic, helpfile], $
Title=title, Buttons=buttons)
tmp = TmSetAttribute(tool_name, 'DIALOGS', 'VTOOL_WID', dialog)
; Set up Dialog part of the generic dialog
; ------------------------------------------
tool_name_wid = WwText(layout, 'TmNoOpCB', $
TEXT='', /LABEL, $
Position=[100, 5])
buttn_name_wid = WwText(layout, 'TmNoOpCB', $
COLS=25, LABEL='', $
TEXT='', $
LAYOUT_NAME='tool_name_layout', $
NAME=['buttn_name_wid','btn_label'], $
Position=[10,40])
left_text_widgets=LONARR(3)
left_text = WoLabeledText(layout, $
['accel_key', 'accel_text', 'mnemonic'], $
'TmNoOpCB', cols=11, $
TEXT_WIDGETS=left_textwidgets, $
LAYOUT_NAME='left_text_group', $
Position=[10,80])
right_text_widgets=LONARR(2)
right_text = WoLabeledText(layout, $
['xpos', 'ypos'], $
'TmNoOpCB', cols=5, $
TEXT_WIDGETS=right_textwidgets, $
LAYOUT_NAME='right_text_group', $
Position=[270, 80])
; Button Icon text
;
b_text = WwText(layout, 'TmNoOpCB', $
/LABEL, $
Position=[140,210])
; button (Push to change icon)
;
button = WwButtonBox(layout, $
[''], $
'NavToolDialogIconButtonCB', $
Name='icon_button', $
Buttons=icon_button, $
Position=[10,240])
; Icon area
;
icon_name = TmGetAttribute(tool_name, grael_name, 'ICON_NAME')
b_icon = WwText(layout, 'TmNoOpCB', $
/LABEL, $
Pixmap=icon_name, $
Position=[140,230])
; Save the widget ids for future updating
; ---------------------------------------
wid = [tool_name_wid, buttn_name_wid, left_textwidgets, $
right_textwidgets, b_text, icon_button(0), b_icon]
status = {, wid:wid, buttons:[buttons,button]}
tmp = TmSetAttribute(tool_name, 'DIALOGS', 'VTOOL_STATUS', status)
; For each one of the buttons in the bottom part of the generic dialog
; set the user data to be the wids of the widgets to query.
; --------------------------------------------------------------
cb_data = {, tool_name:tool_name, $
grael_name:grael_name, $
icon_file:icon_name}
FOR i=0, N_ELEMENTS(buttons)-1 DO BEGIN
status = WwSetValue(buttons(i), Userdata=cb_data)
ENDFOR
status = WwSetValue(icon_button(0), Userdata=cb_data)
; NOTE: The dialog calls the update routine for modularity.
; Update the dialog
; -----------------
NavToolDialogUpdate, tool_name, grael_name
; Show to the world the face of the dialog
; ----------------------------------------
status = WwSetValue(dialog, /Show)
; Set the system cursor
; ---------------------
WoSetCursor, tool_name, /System
END
Step 2: Update Component: Create the routine which will update the dialog in navigators.pro with the desired values, as seen in the code section below:
PRO NavToolDialogUpdate, tool_name, grael_name
; Forward declaration for TM routines
; -----------------------------------
DECLARE FUNC, TmGetAttribute
DECLARE FUNC, TmGetMessage
status = TmGetAttribute(tool_name, 'DIALOGS', 'VTOOL_STATUS')
IF SIZE(status, /Type) NE 8 THEN $
RETURN
; Get the values from TM
; ----------------------
b_label = TmGetAttribute(tool_name, grael_name, 'BUTTON_LABEL')
accel = TmGetAttribute(tool_name, grael_name, 'ACCELERATOR')
accel_t = TmGetAttribute(tool_name, grael_name, 'ACCELERATOR_TEXT')
mnemon = TmGetAttribute(tool_name, grael_name, 'MNEMONIC')
pos = TmGetAttribute(tool_name, grael_name, 'POSITION')
icon_name = TmGetAttribute(tool_name, grael_name, 'ICON_NAME')
; Set the values into the widgets
; -------------------------------
msg = TmGetMessage('NAV_VTool_truename')
tmp = WwSetValue(status.wid(0), msg+grael_name)
tmp = WwSetValue(status.wid(1), b_label)
tmp = WwSetValue(status.wid(2), accel)
tmp = WwSetValue(status.wid(3), accel_t)
tmp = WwSetValue(status.wid(4), mnemon)
tmp = WwSetValue(status.wid(5), STRTRIM(STRING(pos(0)),2))
IF STRTRIM(pos(0)) NE '' THEN $
tmp = WwSetValue(status.wid(6), STRTRIM(STRING(pos(1)),2))
msg1 = TmGetMessage('NAV_VTool_blabel')
tmp = WwSetValue(status.wid(7), msg1)
IF STRTRIM(icon_name) NE '' THEN $
tmp = WwSetValue(status.wid(9), icon_name)
; Update the Userdata for all buttons
; -----------------------------------
buttons = status.buttons
cb_data = WwGetValue(buttons(0), /USERDATA)
cb_data.grael_name = grael_name
cb_data.icon_file = icon_name
FOR I =0, N_ELEMENTS(buttons)-1 DO $
tmp = WwSetValue(buttons(I), USERDATA=cb_data)
END
Step 3: OK/Apply/Cancel Component: Create the routine which will handle the OK/Apply/Cancel callbacks of the dialog in navigators.pro, as seen in the code section below. Part 1: This is the routine registered with the actual OK/Apply/Cancel callback buttons. A common function called NavToolDialogOkApply was created simply for modularity.
FUNCTION NavToolDialogBtnCB, wid, which
; Forward declaration for TM routines
; -----------------------------------
DECLARE FUNC, NavToolDialogOkApply
data = WwGetValue (wid, /USERDATA)
tool_name = data.tool_name
grael_name = data.grael_name
CASE which OF
1: BEGIN ; OK
result = NavToolDialogOkApply(tool_name, grael_name)
TmExecuteMethod, tool_name, 'TM_CONFIG'
RETURN, result
END
2: BEGIN ; Apply
result = NavToolDialogOkApply(tool_name, grael_name)
TmExecuteMethod, tool_name, 'TM_CONFIG'
RETURN, 0
END
3: BEGIN ; Reset
NavToolDialogUpdate, tool_name, grael_name
RETURN, 0
END
4: BEGIN ; Cancel
RETURN, 0
END
5: BEGIN ; Help
RETURN, 0
END
ENDCASE
END
Part 2: This is the routine which does the actual work of retrieving the users input from the dialog and saving it in the TM layer.
FUNCTION NavToolDialogOkApply, tool_name, grael_name
; Forward declaration for TM routines
; -----------------------------------
DECLARE FUNC, TmGetTop
DECLARE FUNC, TmGetMessage
DECLARE FUNC, TmGetAttribute
DECLARE FUNC, TmSetAttribute
;
; Get the values of the dialog widgets.
;
status = TmGetAttribute(tool_name, 'DIALOGS', 'VTOOL_STATUS')
IF SIZE(status, /Type) NE 8 THEN $
RETURN, 0
; Get the values from the widgets
; -------------------------------
b_label = WwGetValue(status.wid(1))
accel = WwGetValue(status.wid(2))
accel_t = WwGetValue(status.wid(3))
mnemon = WwGetValue(status.wid(4))
pos1 = NINT( FLOAT(WwGetValue(status.wid(5))) )
pos2 = NINT( FLOAT(WwGetValue(status.wid(6))) )
icon_file = TmGetAttribute(tool_name, grael_name, 'NEW_ICON_NAME', $
Default='-1')
icon_name = STRTRIM(icon_file, 2)
; Set the values in TM
; --------------------
tmp = TmSetAttribute(tool_name, grael_name, 'BUTTON_LABEL', b_label)
tmp = TmSetAttribute(tool_name, grael_name, 'ACCELERATOR', accel)
tmp = TmSetAttribute(tool_name, grael_name, 'ACCELERATOR_TEXT', accel_t)
tmp = TmSetAttribute(tool_name, grael_name, 'MNEMONIC', mnemon)
tmp = TmSetAttribute(tool_name, grael_name, 'POSITION', [pos1, pos2])
; If the icon was not changed
; ---------------------------
IF icon_name EQ '-1' THEN $
RETURN, 0
IF (icon_name NE '') AND (WoCheckFile(icon_name, /Read) EQ 1) THEN BEGIN
tmp = TmSetAttribute(tool_name, grael_name, 'ICON_NAME', icon_name)
ENDIF ELSE BEGIN
title = TmGetMessage('graphics_messages.ads', $
'MSG_ReadTitle')+tool_name
msg = TmGetMessage('graphics_messages.ads', $
'MSG_BadRead')
msg = [msg, icon_name]
status = WwAlert(TmGetTop(tool_name), msg, $
[TmGetMessage('ok_label')], $
Title=title, /Beep, $
/NoSystemMenu)
ENDELSE
RETURN, 0
END
Conclusion Re-usable widgets have proven very effective in large PV-WAVE Widget intensive programs. Not only does this implementation extend the life of your PV-WAVE program, it also helps enforce good modular programming practices. |
|
|
|
| © Copyright 2010 Visual Numerics, Inc. All Rights Reserved |