Efficient Programming with PV-WAVE® Widgets

Updated by Brian Miller, December 2001 for PV-WAVE v7.51


This paper details the recommended programming practices within PV-WAVE Widgets to reduce the occurrence of errors generated due to the lack of virtual memory. The intended audience is all user's of PV-WAVE Widgets concerned with memory management and the use of temporary variables in 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
It is a common misconception among most PV-WAVE developers that widgets can be created and destroyed freely. The truth is that the internals of PV-WAVE do not free 100% of the allocated memory when a widget is destroyed, due to the fragmentation of memory at the system level. While this particular concept is not new by any means its impact and widespread use in a large scale application can be devastating. Application dialogs are the most likely culprits of improper memory usage and memory trashing. These such dialogs will be the main focus of discussion, due to their wide-spread use and having the greatest benefit after the re-usable dialog concept is in place.

Multiple Instances May be Useless
As is common in so many widget intensive applications, dialogs are created upon request. This can lead to multiple instances of the same dialog, leading to problems in synchronizing the callbacks to the dialogs. In most applications one instance of a dialog is enough, and thus should be enforced. In order to enforce this single instance the creation part of the code must be called only once and the internals of the dialog kept in memory for future use. This saving of internals appears at first to create an overhead in PV-WAVE, but will pay off in the long run by no longer fragmenting memory as well as allowing for a speedy dialog display and refresh whenever the dialog should be requested again by the user. A dialog or widget which can be created and then updated without being destroyed is called: re-usable.

What is a Re-Usable Widget?

Create Once, Use Many
A re-usable widget is a widget which is created once during the life of the application and it's features/attributes are modified due to user requests. Some of the attributes which might be modified are: menu items in a menubar, hiding or showing a dialog, hiding and showing layouts. Once again dialogs are the biggest area of impact due to their complexity and size. A dialog should be created once, either when it is first requested by the user or at the beginning of the program startup. The dialog can then be updated with new information as it becomes available and hidden when the 'Dismiss' or 'Cancel' button is pressed. The next time the dialog is requested by the user it is updated with the new information and shown on the screen.

Benefits: Low Memory Overhead
As a result of implementing re-usable widgets the fragmentation of memory, due to their creation and destruction, will be greatly reduced. Some applications have been known to decrease their memory fragmentation by up to 65%. This results in a longer life of the application. NOTE: It is still possible to run out of virtual memory, but this technique will greatly reduce this likely hood, if not at least postpone it.

Benefits: Quick Updates to Dialogs
Another added benefit of implementing re-usable dialogs is their increased time to display. Once a dialog has been created and a request is received to display, it is merely an issue of updating the widgets inside the dialog and displaying the dialog. This is much faster than creating the dialog from scratch each time.

Basic overview of a re-usable dialog
There are several components to making a dialog re-usable, each of which should be in their own code section or modules. This modularity is the key to making the dialog re-usable.

Creation Component
The creation component/routine should create the initial instance of the dialog. First this piece of code should check to see if the dialog has been created yet. This is best done through a common block. If the dialog has been created then it can simply be updated and redisplayed.

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
The update component/routine should only update the dialog with values. Values are most commonly retrieved from a common block and pushed to the widget via WwSetValue calls, whose widget_ids are also retrieved from a common block.

OK/Apply/Cancel Component
The OK/Apply/Cancel component/routine is responsible for pulling information from the dialog, the exact opposite from the update component. This component is most commonly broken into two routines, the first registers the callback with the buttons and the second routine performs the actual extraction of the users input from the dialog. Values are most commonly retrieved from the widget via WwGetValue calls. These values can then be placed in a common block for later use when the dialog is updated.

Supporting routines in PV-WAVE

There are several routines in PV-WAVE that are crucial to creating re-usable widgets.

WoGenericDialog & WwGenericDialog with /NoDestroy
First of all there must be a means for creating a dialog that can be re-used. The WwGenericDialog and the WoGenericDialog both have a /NoDestroy keyword. This keyword changes the default behavior of a dialog box when it is removed from the display via the "Cancel" button or the window systems "Close" menu item. The /NoDestroy keyword forces the dialog to be hidden versus being completely destroyed as the default behavior enables. A user may create their own versions of these types of functions that create their style of dialog, but be sure to allow for the hiding of the dialog instead of its destruction. All other widgets can be created and used as a re-usable widget without any change. After creating a re-usable dialog, the WwSetValue command with the /Display keyword is still required to display the initial instance. All other requests to display the dialog will perform a /Show on the dialog.

WwSetValues /Size, /Position, /Hide, /Show
For all other non-dialog widgets, the WwSetValue function can be used to make the widget re-usable. For instance, a button can be created in a main window or dialog and be updated with a call to WwSetValue. Sometimes it is more efficient to create several layouts, next to each other and each with a different set of widgets, in a dialog and only show the one that is needed while hiding the others. This gives the dialog a dynamic appearance and saves on creating a dialog for each layout type. The /Hide keyword can be used to hide any PV-WAVE Widget and the /Show keyword can be used to re-display the widget, in a call to WwSetValue. NOTE: Hiding and showing menu items is NOT allowed on the Windows platforms, hence a re-usable menu is not available for Windows, the menu items must be deleted and recreated as needed.

Examples in PV-WAVE

Navigator: Tools & Template Menus
The following example shows the steps that were taken to make the "Tools" and "Template" menus re-usable in the PV-WAVE: Navigator.

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.

 
Company Products & Services Solutions Success Stories Support Downloads Email this page
© Copyright 2010 Visual Numerics, Inc. All Rights Reserved Legal Privacy Sitemap