Tips_gui   >   Menus   >   Menus On-The-Fly

Menus On-The-Fly

When I wrote my first Omnis Studio application each menu was built using the menu class editor. Each menu line was added to a menu class and code was put behind the menu item to carry out the requested action.

Later I learned how to build menus on-the-fly and discovered the following advantages to them:

  1. You avoid spending time manually adding menu items to menu classes.
  2. Your menus are flexible and easier to maintain.

The following example may help to explain how using menus on-the-fly makes application development easier.

You have a window menu called Reports and a user asks you to add a new report called Back Ordered Items. With menus on-the-fly, you simply go to the $constructMenus method, add one line of code, then go to the observer of the menu events and add a $BackOrderedItems method, write the code to print the report, and you're done! You never need to touch the menu class.

If you are supporting a multi-language application, menus on-the-fly are a great benefit because the menu text is assigned at runtime.

If you have already read the topic Context Menu On-The-Fly you will find this section is similar with just a few differences.

All the methods needed to create menus on-the-fly are listed in this section.

Press the Run Demo button in the StudioTips Browser to open a demo window.

Menus on-the-fly are a great way to write flexible and easy to maintain applications!

$constructMenus - Window Method

In the window class a special method, $constructMenus, is called by $construct when the window instance is constructed. The $constructMenus method is responsible for adding menus to the window and adding menu lines to the menus.

The sample code below is in the $constructMenus method of the demo window.

; Make sure $hasmenus is set to true.
Do $cinst.$hasmenus.$assign(kTrue)

; Add the demo menu to the $menus group of the window instance.
Do $cinst.$menus.$add('mMenusOnTheFlyDemo') Returns rMenu

; Set the menu title.
Do rMenu.$title.$assign('Reports')

; Attach this window instance as an "events observer" to be notified of any menu events.
; The second parameter is the 'dispatcher' call back method name.
Do rMenu.$attachEventsObserver($cinst,'$doReportsMenuCmnd')

; Add some menu lines to the menu.
Do rMenu.$addMenuLine('Report1','Report 1') Returns rLine
Do rMenu.$addMenuLine() ;; Empty line.
Do rMenu.$addMenuLine('Report2','Report 2',kTrue) Returns rLine
Do rMenu.$addMenuLine('NoRecipientMethod','No Recipient Method',kTrue) Returns rLine

Quit method kTrue

$doMenuCmnd - Window Method

The $doMenuCmnd window class method is the dispatcher for all messages received from the menus to which the window class has attached itself as an events observer. You can use the same dispatcher method for multiple menus.

The $doMenuCmnd method prefixes a $ character the selected menu line's $name and checks for a recipient class method using $cando. If the method is available $doMenuCmnd forwards the message to the method.

The sample code below is in the $doMenuCmnd method of the demo window.

; This method forwards to reports menu message to a public method matching the name of the menu line selected.

; Calculate the public method name based on the selected menu line name.
Calculate MethodName as con('$',pfrMenuLine().$name)

; Test to make sure the method name exists.
If $cinst.[MethodName].$cando
   
   OK message [sys(85)] (Icon) {The event message has been received by the dispatcher method [$cmethod().$name]////Parameter 1 is a reference to the menu line '[pfrMenuLine().$name]'////This message is about to be forwarded to the [MethodName] window class method.}
   
   Do $cinst.[MethodName](pfrMenuLine) Returns FlagOK
   
Else
   
   OK message [sys(85)] (Icon) {Error!////The window class method '[MethodName]' could not be found.////Unable to dispatch the request.}
   Calculate FlagOK as kFalse
   
End If

Quit method FlagOK

$attachEventsObserver - Menu Method

The demo menu only allows a single events observer per menu class instance. You could allow for multiple observers by storing the observer references in one row ivar, and the call back method names in another row variable. See the topic Observer Design Pattern for more information.

; $attachEventsObserver(pfrObserver,pCallBackMethodName)

; Store a reference to the observer instance.
Set reference irObserver to pfrObserver.$ref

; Store the 'Dispatcher' method. (Optional)
Calculate iCallBackMethodName as pCallBackMethodName

Quit method kTrue

$addMenuLine - Menu Method

The menu instance receives $addMenuLine messages with the parameters pMenuName and pMenuText. The $addMenuLine method adds a menu line to the menu instance and returns a reference to the added menu line.

Sample code is listed below.

; $addMenuLine(pName,pText,pbElipsis)

; Append ... to the pText if pbElipsis is set to true. Default is false.
Calculate Text as con(pText,pick(pbElipsis=1,'','...'))

; Add a menu line object to the menu instance.
; $add(cText,bEnabled,bChecked)
Do $cinst.$objs.$add(Text,kTrue,kFalse) Returns rMenuLine

; Assign the name to the menu item.
Do rMenuLine.$name.$assign(pName)

; Return a reference to the added menu item.
Quit method rMenuLine

$control - Menu Method

When the user clicks on a menu item, a $event message is sent to the menu item. Since the menu item was added on-the-fly there is no $event message so the message automatically is passed to the $control method of the menu instance.

The mReports menu sends a $printReport message to the registered observers.

The menu's $control method uses $cobj().$name to figure out which menu item was selected by the user. The menu line name is used prefixed with a $ character and used as the first parameter sent to the registered observers, the second menu line $text is used as the second parameter sent to the registered observers.

Sample code is shown below.

On evClick

; Test to make sure the callback message can be sent to the observer.
If irObserver.[iCallBackMethodName].$cando
   
   ; Send a message to the observer.
   Do irObserver.[iCallBackMethodName]($cobj)
   
Else
   
   ; Unable to send the message. Report an error.
   OK message [sys(85)] (Icon) {The call back method, [iCallBackMethodName], is not found in the menu events observer of [irObserver().$name].}
   
End If

Quit method kTrue

Sequence of Events

The following is a summary of event for the Menus-On-The-Fly demo.

  1. The window class is instantiated. The $construct method calls $constructMenus.
  2. $constructMenus adds and instance of the mMenusOnTheFlyDemo menu class as a window menu to the window's $menus group, return a reference to the menu instance to the local variable rMenu.
  3. The $constructMenus method sets the $title property of the menu instance to Reports.
  4. $constructMenus sends an $addEventsObserver($cinst,'$doMenuCmnd') message to the menu instance, adding itself as the observer and $printReports as the dispatcher method.
  5. The menu instance stores the window instance as an observer of menu events, and $doMenuCmnd as the call back method.
  6. $constructMenus sends an $addMenuLine('Report1','Report 1') message to the menu instance.
  7. The menu instance adds the menu line Report 1, and sets the $name property to Report1.
  8. The user selects the menu line Report 1.
  9. Omnis Studio sends an $event message to the menu line.
  10. The message is automatically passed to the $control method of the menu instance.
  11. The $control sends a $doMenuCmnd message to the registered observer. A reference to the selected menu line is passed as the first parameter.
  12. The window instance receives the $doMenuCmnd message.
  13. The $doMenuCmnd method forwards the call to $Report1.