PowerBuilder 6 Unleashed

Previous chapterNext chapterContents


- 12 -

Menus and the Menu Painter


A menu provides a list of choices or actions that the user can choose from to initiate an action. In this chapter you'll learn about the Menu painter and the benefits and problems of using menus in an application. A menu can be used in both multiple-document interface (MDI) and single-document interface (SDI) applications.

Menu Basics

Several concepts, warnings, and terms need to be introduced before you can fully understand the description of the actual Menu painter.

Menu Types

There are three types of menus. The first type is a drop-down menu (see Figure 12.1); this is a menu on a menu bar.

FIGURE 12.1. A drop-down menu.

Drop-down menus are represented by a menu title in the menu bar of a window, which can be accessed either by clicking the title with the mouse or by pressing Alt and the underlined character in the title.

The second type is a pop-up menu (see Figure 12.2), which appears in relation to an object and is also known as a contextual menu.

FIGURE 12.2. A pop-up menu.

The pop-up menu usually appears at the mouse pointer position and is invoked when the user performs an action, which is usually clicking the right mouse button. The menu contains context-sensitive options relating to the object that it was invoked on and provides an efficient means to access methods associated with an object. The menu should be kept short, and there should not be multiple cascading levels.

The third type, the cascading menu (see Figure 12.3), can appear on either of the two preceding types of menus.

FIGURE 12.3. A cascading menu.

Cascading menus are indicated by a right-pointing arrow on the parent menu item and are accessed by the user selecting this menu item. A cascading menu is used when you group similar actions together under a heading (for example, the Align Objects menu in the Window painter). Cascading menus enable you to simplify your top-level menus and hide a multitude of options in the submenus. You should try to keep the level of cascades to no more than two so that the user can see all the options simply by selecting a top-level menu item; otherwise, menus become difficult to navigate.

Menu Items

The menu items of a menu usually describe an action or command that is executed when selected, but they can also include open window names or changeable options.

You might have heard the terms accelerator and shortcut key, and many novice developers confuse the two.

An accelerator key is signified by the underline that appears beneath a letter in a menu item, or even some controls. You select the item by pressing the Alt key (which activates the menu bar) and the character that is underlined. These can be accessed only if the item is visible.

A shortcut key is always accessible when the window is in focus, no matter where the associated menu item resides in the menu structure. The shortcut key for a menu item appears to the right of the menu item. For example, Ctrl+P is usually used for printing. Shortcut keys directly access the menu item and are used for common actions.


NOTE: The particular keystrokes for an action are determined by the operating system that the application is being written for, and you should examine the design guide for that system. You can also examine existing commercial applications to see what hotkeys they use.

If the menu item causes a dialog box to be opened, the convention is to use an ellipsis (...) following the item text. This provides a visual cue that the action described will not immediately happen, and the user has the chance to either modify the behavior or cancel the operation.

As mentioned previously, some menu items indicate settable options for the application, such as the visibility of a ruler in a word processor. There is no standard for indicating that a menu item is an option as opposed to an action, unless it is currently selected, in which case a checkmark appears to the left of the menu item text.

Most options are logically grouped under a particular menu title. You can provide further subdivisions by using separation lines. These are lines that appear horizontally in the menu.

Menu Conventions and Guidelines

When you add a menu to a window, you must remember that you have just lost some of your screen real estate, and when you add a toolbar (or two), you have lost even more. You must bear this in mind when you are creating your application windows and allow sufficient space for menus and toolbars to be accommodated. If you do not, controls will start to disappear from the bottom of the window.

Try to limit the number of items you place in a cascading menu so you don't overwhelm a user with a multitude of options.

As mentioned earlier, you should try to keep the number of cascading menus to only one level, or at most two.

You should disable (gray out) a menu item, or even a menu title, if the user should not have access to it. For example, you could disable the Save menu item until a change has actually been made.

The descriptions of menu titles and items should be kept short and descriptive. Try for just a couple words--four being a maximum--while still fully describing the item. Remember to include the ellipsis (...) if the option opens a dialog box.

Developers of Windows-based systems should purchase The Windows Interface--An Application Design Guide, a book published by Microsoft Press, which you can use to reference the standard names, positions, and construction of menus. Try to follow these guidelines as much as possible. If you cannot get this book, you should explore other commercial Windows applications, such as Microsoft Word or Excel, and examine their menu structures. This also applies to other development environments; you should work with an accepted design guide or adopt another widely used application as your template.

If you are going to use a menu item that toggles between one state and another, you should use a checkmark next to the item and also set the associated toolbar button to the appropriate up or down state. A checkmark and a button in the down state signify that the option is in effect.


TIP: You should avoid changing the text of menu items at runtime because it can be very confusing to an end user.

Make sure each menu item can be reached by the keyboard by using either accelerator or shortcut keys. Some people, especially in data entry applications, hate to move their hands away from the keyboard to grab a mouse just to access a menu. You should make any keypresses unique and consistent within the application, and you should use the accepted standards of the operating system being used.

Use the MDI frame toolbar to display the most common menu items for quick user access.

PowerBuilder toolbars now conform with those that Microsoft has introduced with Office97. The toolbars now have the following properties:

The Menu Painter

Open the Menu painter by clicking the menu button in the PowerBar or PowerPanel. This opens the Select Menu dialog box (see Figure 12.4), which enables you to open an existing menu, create a new one, or inherit from an existing menu object.

FIGURE 12.4. The Select Menu dialog box.

After you close the Select Menu dialog box, the actual Menu painter workspace is accessible (see Figure 12.5).

FIGURE 12.5. The Menu painter workspace.

The topmost edit field is where you enter menu titles; after you have typed the text into the field, you can create a new title to the immediate right. If you enter a significant number of menu titles, or if you are running at a low resolution, you will have to use the horizontal scrollbar above the menu title line to scroll backward and forward.

As you enter the text for each menu title, it is named by PowerBuilder; you can see the assigned name in the Menu Item Name box on the right side of the window. If you come back to the menu item and alter the text, the object's name also changes. This is obviously undesirable when the menu is in use by an application; you need to turn on the Lock Name option (the checkbox below the Menu Item Name field) to retain the original name.


NOTE: PowerBuilder turns on the Lock Name option any time you return to an already entered menu item. All new menu items are entered with the Lock Name option off. If you turn the option off, this state exists only for the duration of your edit on the menu item and is reset as soon as you change focus to another menu item.

The menu items for the drop-down part of the menu are entered in the scrolling area on the left of the window. Each line is split into two fields; the first is for the menu item text and the second is for the assigned shortcut key. As with the menu titles, after you have entered a menu item line, a new line is made available below it for you to continue the menu (see Figure 12.6).

FIGURE 12.6. A menu title and menu item.

You can change the order of menu titles and menu items by using the yellow hand button in the PainterBar. Then when you click a field, the mouse pointer turns into a big white hand and you can drag the item to the location in the menu layout where you want it to appear.


NOTE: You cannot drag a menu item into the menu title area or vice versa. You also cannot directly move a menu item in a descendant menu; you'll read more on that in the "Menu Inheritance" section later in this chapter. Attempting to drag a menu item past the top of the menu results in different behavior in versions 5.0 and later; the menu item is moved to the bottom of the menu rather than to the top, as it was in previous versions.

You can insert menu titles and menu items before the current area by using the Insert Item button on the PainterBar. You also can remove items by using the Delete Item button.

As you might have noticed in Figure 12.6, you assign the accelerator for a menu item the same way you do for window controls: by using the ampersand character (&). The shortcut key for a menu item is assigned on the Shortcut tab. You can assign the key and whether it is associated with the Ctrl, Alt, and/or Shift keys. These options are not available for menu titles.

To create separator lines in PowerBuilder menus, you enter a single minus sign (-) as the item's text (see Figure 12.7).

FIGURE 12.7. The separator item has the hand pointer pointing to it.

As you can see in Figure 12.7, the name PowerBuilder constructs is m_-. Because this name clashes every time you create a new separator line, you should number separator lines in increments of 10 so that you can insert new separator lines as required.


WARNING: One of the options that many developers turn off when they have been using PowerBuilder for a while is Dashes in Identifiers. This enables you to use the dash (-) in variable and object names and is quite a confusing option if you actually do name objects with it. Inside the Menu painter, PowerBuilder quite happily generates names with dashes in them, and even enables you to do the same. However, when you try to save the object, you get an error detailing some Forward Declarations. This is because the dash option has been turned off and the menu contains dashes. The same error appears when you're migrating menus between versions of PowerBuilder. A useful naming scheme is to number each separator with numbers, the first being the menu title index, and the second some unique number within that index. For example, in the example above the separator would be named m_11.

The Enabled and Visible attributes for each menu title and menu item, in addition to the Checked attribute for menu items, are accessible on the Style tab (see Figure 12.8). The other options in this area are dealt with in the "Menu Inheritance" section in this chapter. The Type option is used when developing cross-platform applications and informs PowerBuilder to make the necessary changes for Exit and About menu items for the operating system in which the menu is running.

FIGURE 12.8. The Style tab.

If you want to turn a menu item into a submenu title with its own cascading menu, you use the Down a Cascading Level button in the PainterBar. This places you either in a new area for defining menu items or in the menu items you have already defined.


WARNING: If you delete a submenu title, you are not warned that there are menu items existing in the cascading menu, and everything is removed.

It is not very obvious that a cascading menu exists under a menu item, because the only indicator is a small black triangle on the far right of the menu item area, which can be easily overlooked by novices. When you go down a level, the text area directly above the menu items changes to show the path taken to get to the current level (see Figure 12.9).

FIGURE 12.9. A cascaded menu.

You can traverse between levels of a cascading menu by using the Down and Up Cascading Level buttons in the PainterBar or by double-clicking the little black triangle (which only takes you down a level).

You can assign a MicroHelp value for each piece of the menu. PowerBuilder uses this value to automatically display in an MDI frame's MicroHelp area (if available) as the user traverses the menu. Enter the value in the MDI MicroHelp edit field; you are permitted to enter quite long descriptions. This saves you from having to write scripts to carry out the same functionality.

Each menu item can have a corresponding toolbar icon that is displayed when you create an MDI application. To access these properties select the Toolbar tab (see Figure 12.10).

FIGURE 12.10. The Toolbar tab.

In the Toolbar tab you can specify the text displayed when the Show Text option is on or with PowerTips. You can also specify the positioning of the button on the toolbar and drop-down toolbar properties.

The Text box enables you to specify the text that displays on the toolbar button and also the text that appears in the PowerTip. Button text is displayed when the ToolbarText attribute of the application object is TRUE, and PowerTips are displayed only when this attribute is FALSE. You specify the two pieces of text with a comma-separated string, such as Button Text, PowerTip Text. If you specify only one piece of text, it is used for both the button and the PowerTip.


NOTE: You should limit the actual button text to no more than eight or nine characters. The PowerTip text, however, can be quite extensive.

To separate a toolbar button from the preceding button, you need to enter an integer value into the Space Before box. This is done to logically group related buttons together. The usual value is 1, and the default (no spacing) is 0. This attribute is ignored when the toolbar is set to display button text.

If, for some reason, you need to change the order of the buttons on the toolbar, you can use the Order box to set an ordering value. This defaults to 0, and the buttons are displayed in menu order.


NOTE: Remember that PowerBuilder defaults menu item orders to 0, so you might have to set all the Order values for menu items to get the desired result. Otherwise, all of the 0 Order value menu items appear first, and then the menu items with Order values greater than 0. Number each item in increments (5, 10) to give you more flexibility in changing the order or adding new options.

PowerBuilder also has the ability to provide drop-down toolbar items (see Figure 12.11). This allows you to make your toolbars shorter so that they can be more easily used and are easier to dock (see the "Toolbars and PowerTips" section). You can provide drop-down toolbar items for menu titles such that all their subordinate menu item toolbar entries appear underneath the parent's drop-down. You can specify the number of columns that the drop-down should use when displaying the toolbar pictures by entering a value in the Column field.

FIGURE 12.11. Drop-down toolbar items.


NOTE: The Bar Index option allows you to open multiple toolbars for the one menu. Each toolbar is independent of the other. You can control which toolbar a menu item appears in by setting this value.


NOTE: You must specify the drop-down toolbar items within a level of menu inheritance. You cannot extend a drop-down toolbar from within an inherited menu. Any that you specify within the child menu become their own drop-down toolbars.

In the Pictures tab you can specify the pictures displayed on the toolbar and when the button is pressed down.

You can either enter the filename and path of the graphics file (in BMP, RLE, or WMF file formats) to be used for the button and down pictures, or use the appropriate button to bring up a file dialog box. The stock pictures that come with PowerBuilder are also accessible in this tab. The Picture attribute is used in the button's normal state, and the Down Picture is used when the button is clicked. If you want the Down Picture to remain when the user releases the mouse button, click the Display Down checkbox. When the button is in the down state, you must use code to alter the ToolbarItemDown attribute of the menu item to set it back to the normal picture. The value FALSE resets the button. You use this functionality with menu items that can be checked and unchecked.


NOTE: The picture file should be 16 pixels wide by 15 pixels high. Otherwise, PowerBuilder compresses or expands the picture, and you get some ugly results.

When you have finished constructing your menu, or even during construction, you can see what the finished menu will look like by selecting Preview from the Design menu or by pressing Ctrl+Shift+P. This opens the design preview window and enables you to traverse the menu structure. No PowerScript that you have written will be executed.


NOTE: Any drop-down toolbars that you have specified will not be displayed, and all pictures appear contiguously across the toolbar.

The only step left when you have finished the menu and saved it is to attach it to a window--unless you are dynamically using the menu for creating pop-up menus.

Menu-Specific PowerScript

Several functions, attributes, and events specific to menus are covered in this section.

Opening a Pop-Up Menu

Now that you can create a menu, two things can be done with the object. The menu can be attached to a window, in which case it becomes a drop-down menu and is created and destroyed by PowerBuilder. The menu can also be used as a pop-up menu for which you have control and responsibility. Pop-up menus can also be opened for menu items that exist in the window's current menu.

To create a menu at a certain location, you use the PopMenu() function. The syntax is

MenuItem.PopMenu( XLocation, Ylocation)

The MenuItem argument is a fully qualified name of the desired menu title for the menu and is created at the coordinates (in PowerBuilder units) indicated by the Xlocation and Ylocation arguments (from the left and top of the window, respectively). Be careful, because these coordinates are relative to the currently active window. Remember that MDI sheets are not active--only the frame is.

If the menu is not already open and attached to a window, you must declare and instantiate a variable of the correct menu type before calling the PopMenu() function.


NOTE: If for some reason the menu title you are going to use for a pop-up is not visible, you must make it visible before it can be displayed as a pop-up.

To illustrate the points made earlier, let's explore some examples.

The first example opens a pop-up menu of a menu title that already exists as part of a window. The two functions that return the current coordinates of the mouse pointer are used to open the pop-up menu at the mouse's position:

m_sheet.m_file.PopMenu( PointerX(), PointerY())

The second example opens the same menu that is not currently attached to any of the open windows:

m_sheet mPopUp
mPopUp = Create m_sheet
mPopUp.m_file.PopMenu( PointerX(), PointerY())

However, if you are opening the pop-up menu in an MDI sheet, you need to prefix both PointerX() and PointerY() with the name of the MDI frame window:

mPopUp.m_file.PopMenu( w_frame.PointerX(), w_frame.PointerY())

When the user has made a selection, the menu action is carried out and the pop-up menu is destroyed by PowerBuilder.


NOTE: Remember that both PointerX() and PointerY() return the coordinates from the upper left of the object that you prefix the functions with, and not from the window corner.

Menu Attributes

As mentioned in the description of menu items, you can provide items that can be checked and unchecked. This is an attribute of a menu item; it can be accessed either directly by using the dot notation (.Checked) or by using the Check() and UnCheck() functions. You cannot check a menu title.

PowerBuilder does not automatically handle turning the checkmark on and off. You must place the following line of code in the Clicked event:

this.Checked = Not this.Checked

Like other controls, a menu item (and even a menu title) can be disabled and enabled at runtime. As before, PowerBuilder provides two ways to alter the state: using the dot notation (.Enabled) and using the Enable() and Disable() functions.

The ParentWindow Pronoun

Often you need to refer to the window that owns the menu in which you are coding. This special relationship can be written using the ParentWindow pronoun. For example, to code the exit menu item, the code would be

Close( ParentWindow)

The ParentWindow pronoun, however, points only to an object of type window, and you cannot make specific references to controls or other developer-defined attributes of the menu's parent window.

Menu Events

Menu items have only two events: Clicked and Selected.

The Clicked event occurs when the user clicks a menu item and releases the mouse button.

The Selected event occurs when the user is browsing through the menu and is on the current menu item but has yet to trigger the Clicked event. This event is very rarely used but is provided if you want to code some specific functionality into your application.

By placing code in the Clicked event of a menu title, you can dynamically enable and disable the menu items in the cascading menu every time a user selects it. You can provide dynamic security based on application parameters or transaction object settings, or more usually a cascading menu that is sensitive to the current state of the window it is associated with. For example, you want to enable an MDI frame menu Save option only if the current sheet has been modified. The sheet maintains an instance variable i_bModified for this purpose. The code in the Clicked event of the File menu title would then be

w_sheet wInstance
wInstance = ParentWindow.GetActiveSheet()
// Disable it for the cases 1) no sheet and 2) no modifications
this.m_file.m_save.Enabled = FALSE
If IsValid( wInstance) Then
   // Note that these are split into two tests, because PowerBuilder will
   // evaluate both regardless of the success or failure of the first
   // expression. This means if there was no active sheet
   If wInstance.i_bModified Then
      this.m_file.m_save.Enabled = TRUE
   End If
End If

Accessing Menu Items

Accessing a particular menu item can require coding an extensive dot notation chain, depending on from where you are calling. The format is

Window.MenuName.MenuTitle.MenuItem{.MenuItem etc.}

The chain can be shortened depending on from where you are accessing the menu. For example, a menu function needs to specify only MenuTitle and MenuItem. You can also make use of the Object Browser to paste the calling chain for menus.

Menu Functions and Structures

Menus are one of the four objects (along with applications, windows, and user objects) that can have local functions and structures declared for them. These are created and maintained using the same painters as the other objects, and are covered in Chapter 6, "The PowerScript Environment."

Menu Inheritance

As with other PowerBuilder objects, menus can also be inherited. You specify this in the Select Menu dialog box before you enter the Menu painter workspace.

With a descendant menu you can append items to the end of a cascading menu or modify existing menu items. You cannot insert new items between ancestor items or remove ancestor items. As with inherited windows, you can make only the unnecessary menu items invisible.

There is, however, a method for inserting new menu items between ancestor menu items in a limited fashion. Within the ancestor menu, you must set the Shift Over/Down attribute for the menu titles you want to move right and for menu items you want to move down. In the descendant menu, any appended menu titles or menu items are placed before the moved ancestor titles and items at runtime. You also can check this in the Preview window; so the order of the menu items is Ancestor, Descendant, Moved Ancestors.

If you have made changes to an ancestor menu item within the descendant and then decide you actually want to retain the original settings, you can use the Reset Attributes menu option in the File menu. You cannot reset the attributes for the whole menu but only on an item-by-item basis.

Changing the visibility of menu items in inherited and normal menus has performance problems in all the current releases of PowerBuilder (this may be fixed in the next release). This is because every time a menu item is hidden, it is actually destroyed, and the whole menu is re-created and drawn on the screen. Creating a menu at runtime is a very expensive operation because each menu item is an individual object within the Windows system. If you start hiding items, you are going to cause a significant amount of rebuilding, and, depending on the machine's speed, you might or might not notice the impact.


NOTE: Microsoft appears to have spent some significant effort increasing the speed of opening and modifying menus within the Windows 95 operating system. We carried out a simple test using a two-level inherited menu with seven menu titles, three cascaded menus, 51 menu items, and a total menu object size of over 70KB. Each of the menu items contained code, which was taken from a production system. The menu appeared almost twice as fast as it did in PowerBuilder 4.0, and modifications to the menu did not cause a visible decrease in performance.

Menus and MDIs

In PowerBuilder, when you create an MDI frame you have to associate a menu with it. This is supposed to lead you into creating a menu that will open sheets and be capable of closing the frame. The sheets in an MDI application do not have to have menus associated with them. If a sheet has its own menu, it displays in the menu bar of the frame window when the sheet is active. The frame's menu, if there is one, is used when a sheet does not have its own menu.

PowerBuilder uses two internal attributes of a window to track which menu is associated with it. These attributes are MenuID and MenuName and can be only indirectly affected by using the ChangeMenu() function.

The ChangeMenu() function enables you to programmatically change the menu associated with a window:

Window.ChangeMenu( Menu {, SheetPosition})

The SheetPosition argument is used when the window is an MDI frame. It indicates the menu title position to which you append the currently open window list. The default is 1, and all opened sheets appear at the bottom of the first menu title's drop-down menu.


NOTE: If a sheet is currently open in the MDI frame with its own menu, the new menu is not visible until the sheet is closed or the user activates a sheet without a menu.

Whenever you create or open an object at runtime, PowerBuilder creates a global variable of that type and points it at the instance you just created. However, when you open multiple instances of a window with a menu, PowerBuilder creates a global variable of that menu type and an instance of the menu for each window instance. You might not immediately see a problem with this, but the global variable points to the last instance that was created. This means that anywhere you specifically code that menu's object name, you are potentially accessing the wrong menu. For example, imagine that you have an MDI frame that has three open sheets (A, B, and C), each sheet is an instance of the same window, and all have menus (m_menu). If you have the following piece of code in the Clicked event of a menu item, it affects the menu pointed to by window C until you close sheet C:

m_menu.m_save.Enabled = FALSE

You receive a Null Object Reference error message the next time you try to access the menu, as the menu instance was released from memory. There are two solutions to this problem.

The first solution forces you to use good coding practices and use only pronoun references within your menu scripts. This means using the Parent and ParentWindow keywords throughout your code, which makes the script work on the current instance rather than the last one created.

The second solution requires you to make use of the window's MenuId property to reference each window's own menu instance. MenuId is of type Menu, so you need to cast it to the correct menu object class before making explicit menu item calls or changes. You do this by casting the MenuId into another variable:

m_mfg_order mMyMenu
mMyMenu = this.MenuID
mMyMenu.m_save.Enabled = FALSE

This example assumes you know that the window's menu is of a certain class, in this case m_mfg_order. You can of course use the ClassName() function to find the value, and then use a case statement to create the correct class type for the variable. Or you can use the Dynamic keyword and call the appropriate menu functions straight off of the MenuId property.

The following list includes some general information and guidelines on using menus within an MDI application:

Toolbars and PowerTips

Unique to MDI frames and their sheets is the capability to provide a toolbar based on selected menu items. You read about the rules for specifying a toolbar earlier in the "The Menu Painter" section. In this section you'll discover how to use toolbars and some of the restrictions and problems associated with toolbars.

First, let it be quite clear that toolbars can be used only in MDI frames and MDI sheets. If you open a sheet with a menu and toolbar outside the MDI frame, the toolbar does not show. Each button on a toolbar is directly associated with a menu item, and clicking the button simply triggers the Clicked event for that menu item.

You have control over these toolbars only through attributes (see Table 12.1) on the application and on the frame window.

Table 12.1. Toolbar attributes of an application object.

Attribute Data Type Description
ToolbarFrameTitle String The title text for a floating FrameBar
ToolbarPopMenuText String The text on the pop-up menu for toolbars
ToolbarSheetTitle String The title text for a floating SheetBar
ToolbarText Boolean Whether the menu item text shows on the button
ToolbarTips Boolean Whether PowerTips show when the ToolbarText is not active
ToolbarUserControl Boolean Whether the toolbar pop-up menu can be used

When a user right-clicks the toolbar, PowerBuilder provides a default pop-up menu that provides options to manipulate the toolbar. In fact, it is the same menu you use inside PowerBuilder on the PowerBar and PainterBar. The ToolbarPopMenuText attribute enables you to alter the text on the menu, but not the functionality. This attribute is really used only in the construction of multilingual applications. The first two items on the pop-up menu display the titles set for the ToolbarFrameTitle and ToolbarSheetTitle attributes.

You set the ToolbarPopMenuText attribute by using a comma-separated list constructed as follows:

&Left, &Top, &Right, &Bottom, &Floating, &Show Text

These are the default values for the pop-up menu. For example, the French equivalent, with appropriate accelerators (&), would be

 "A'&Gauche, En &Haut, A'&Droite, Au &Fond, F&lottant, Montrer Le &Texte"

The toolbar properties for window objects are shown in Table 12.2.

Table 12.2. Toolbar properties of MDI frame and sheet window objects.

Attribute Data Type Description
ToolbarAlignment ToolbarAlignment Where the toolbar displays
ToolbarHeight Integer Height of a floating toolbar
ToolbarVisible Boolean Whether the toolbar displays
ToolbarWidth Integer Width of a floating toolbar
ToolbarX Integer X coordinate of a floating toolbar
ToolbarY Integer Y coordinate of a floating toolbar


NOTE: The following two properties for window objects are available in version 4.0 of PowerBuilder and are not available in later versions:

ToolbarItemsPerRow Integer Maximum items per row of a floating toolbar

ToolbarTitle String Title of a floating toolbar

An MDI frame window also has one event that is associated with toolbars: ToolbarMoved. This event is triggered in an MDI frame window when the user moves the FrameBar or SheetBar. The Message.WordParm and Message.LongParm attributes contain information on what was moved and to where it was moved:

Message.WordParm Value What It Means
0 The FrameBar has been moved
1 The SheetBar has been moved
Message.LongParm Value What It Means
0 Moved to the left
1 Moved to the top
2 Moved to the right
3 Moved to the bottom
4 Set to be a floating toolbar


NOTE: A toolbar control window that demonstrates the use of the preceding attributes is constructed in Chapter 10, "Windows and the Window Painter."

One of the nonstandard effects displayed by the menu toolbars that PowerBuilder 4.0 provides occurs when you disable a menu item. This action also causes the associated toolbar button to be disabled, but the appearance of the button remains unchanged. PowerBuilder does not provide you with a means to specify a disabled bitmap for the button, and you must code such functionality into your application. This means replacing the ToolBarItemName attribute value with a bitmap that shows the option grayed out.


NOTE: Starting with PowerBuilder 5.0, the toolbar picture is grayed out for you when the menu item is disabled. This conforms with the Microsoft Windows 95 interface standards.

Hiding a menu item does not cause the toolbar button to become disabled or to disappear. Again, you must code specifically for this occurrence. However, if you disable a menu title, it disables, but does not remove, the toolbar buttons for all of the menu items under it.

Both of these kinds of processing are best encapsulated into either the menu itself or a non-visual user object built for managing menus.

One of the sometimes annoying toolbar effects in PowerBuilder applications is the double toolbar. When the frame and the sheet both have toolbars, the frame toolbar displays above the sheet toolbar. Both toolbars are active, and options can be selected from either.

A simple way around this is to not construct a toolbar for the sheet menus and have the options you want to make available at the sheet level part of the frame's menu, but disabled. Obviously, this is not always practical. With each type of sheet there is usually a different menu, and therefore you can use this method only if the number of types of sheets is small.

Another more complex method requires you to turn off screen redraws while you hide the frame toolbar when a sheet opens. You then have to track when the last sheet is closed to re-enable the frame toolbar. This is best achieved by using a controlling function on the MDI frame window. Within this function it can update a window instance variable as you open and close sheets. When this counter is decremented back to 0, the function makes the frame toolbar visible again.

Starting with version 5.0 of PowerBuilder, your user has the ability to dock these menus together onto one line, thus reclaiming some of the screen space that had been lost (see Figure 12.12).

FIGURE 12.12. Two menu toolbars docked.

In the Window painter you have access to some of the attributes for the initial position of the window's toolbar. This is accessed via the Window property sheet, under the ToolBar tab (see Figure 12.13).

FIGURE 12.13. The window toolbar properties.

The X, Y, Width, and Height options only have an effect when the toolbar alignment is set to floating.

Controlling Toolbars

PowerBuilder 5.0 introduced some new functions that allow developers to control and query the state of a toolbar for a window. Using these functions you can also make a toolbar dock onto the frame window's toolbar.

The GetToolbar() function allows you to query a window for basic toolbar information. If the window does not have a toolbar, the function returns -1. The function syntax is

wWindow.GetToolbar( nIndex, bVisible {, aAlignment {, szTitle}})

There are two versions of the GetToolbarPos() function that allow you to extract further information about the toolbar:

wWindow.GetToolbarPos( nIndex, nRow, nOffset)
wWindow.GetToolbarPos( nIndex, nX, nY, nWidth, nHeight)

The first version returns information about the row the toolbar appears on and the offset from the row's left origin. The second returns the size and position information for a floating toolbar.

The SetToolbar() function allows you to set basic toolbar values and has the following syntax:

wWindow.SetToolbar( nIndex, bVisible {, aAlignment {, szTitle}})

There are two versions of the SetToolbarPos() function that allow you to set further information for the toolbar:

wWindow.SetToolbarPos( nIndex, nRow, nOffset)
wWindow.SetToolbarPos( nIndex, nX, nY, nWidth, nHeight)

The first version allows you to set the row the toolbar will appear on and the offset from the row's left origin. The second sets the size and position information for floating toolbars.

Saving and Restoring Toolbar Settings

Using the toolbar functions just described, you can construct generic save and restore functions for preserving the state of a window's toolbars. This information can be written to a number of different places: an INI file, a database table, or the system Registry.

The code shown in Listing 12.1 uses an INI file, but it should require minor changes to store the data elsewhere. The reason you might still want to use an INI file over the Registry is the same as the reason that PowerBuilder still uses a PB.INI: cross-platform compatibility. Macintosh and UNIX platforms do not have a corresponding Registry structure, but the PowerBuilder INI access functions work in either case.

So that you can store (Listing 12.1) and restore (Listing 12.2) multiple windows' toolbars, make a new section for each toolbar using each window's class name. If the section does not already exist, the SetProfileString() function will create it for you; the only requirement is that the INI file must already exist.

Listing 12.1. SaveToolbar( ReadOnly Window a_wWindow).

Integer nRow, nOffset, nX, nY, nWidth, nHeight
Boolean bVisible
ToolbarAlignment aAlignment
String szTitle, szAlignment, szSection
// If the toolbar does not exist, this will return -1
If a_wWindow.GetToolbar( 1, bVisible, aAlignment, szTitle) = 1 Then
   // The window's class is the section we will save the toolbar's state under
   szSection = a_wWindow.ClassName()
   // Convert the boolean to a string representation
   If bVisible Then
      SetProfileString( g_App.i_szINIFile, szSection, "Visible", "TRUE")
   Else
      SetProfileString( g_App.i_szINIFile, szSection, "Visible", "FALSE")
   End If
   // Convert the toolbars alignment to a string representation
   Choose Case aAlignment
      Case AlignAtLeft!
         szAlignment = "Left"
      Case AlignAtTop!
         szAlignment = "Top"
      Case AlignAtRight!
         szAlignment = "Right"
      Case AlignAtBottom!
         szAlignment = "Bottom"
      Case Floating!
         szAlignment = "Floating"
   End Choose
   SetProfileString( g_App.i_szINIFile, szSection, "Alignment", szAlignment)
   // Extract the row and offset values and save them
   a_wWindow.GetToolbarPos( 1, nRow, nOffset)
   SetProfileString( g_App.i_szINIFile, szSection, "Row", String( nRow))
   SetProfileString( g_App.i_szINIFile, szSection, "Offset", String( nOffset))
   // Extract the size and position values and save them
   a_wWindow.GetToolbarPos( 1, nX, nY, nWidth, nHeight)
   SetProfileString( g_App.i_szINIFile, szSection, "X", String( nX))
   SetProfileString( g_App.i_szINIFile, szSection, "Y", String( nY))
   SetProfileString( g_App.i_szINIFile, szSection, "Width", String( nWidth))
   SetProfileString( g_App.i_szINIFile, szSection, "Height", String( nHeight))
End If

Notice that the function argument was declared using the new ReadOnly parameter passing mechanism. This allows the function to be called using the this PowerBuilder pronoun. For example, to save the state of a window's toolbar within the CloseQuery event, the code would be

g_App.SaveToolbar( this)


NOTE: In this case you have implemented the toolbar save and restore functions in a base class user object. The calls to both this and the following function can be placed in a base class window object. The information for each window's toolbar is stored under a class name and so will not clash with another.

Listing 12.2. The RestoreToolbar(ReadOnly Window a_wWindow) function.

Integer nRow, nOffset, nX, nY, nWidth, nHeight
Boolean bVisible = FALSE
ToolbarAlignment aAlignment
String szTitle, szAlignment, szVisible, szSection
// If the bar does not exist, this will return -1
If a_wWindow.GetToolbar( 1, bVisible, aAlignment, szTitle) = 1 Then
   // The window's class is the section that we saved the toolbar's state under
   szSection = a_wWindow.ClassName()
   // Try and retrieve the toolbars visibility
   szVisible = ProfileString( g_App.i_szINIFile, szSection, "Visible", "")
   // If we failed to find the toolbars visible state then do NOT overwrite
   // the current settings of the toolbar.
   If szVisible <> "" Then
      szAlignment = ProfileString( g_App.i_szINIFile, szSection, "Alignment", ¬"Top")
      szTitle = ProfileString( g_App.i_szINIFile, szSection, "Title", "")
      If Upper( szVisible) = "TRUE" Then
         bVisible = TRUE
      End If
      // Convert the string back into an alignment value
      Choose Case Lower( szAlignment)
         Case "left"
            aAlignment = AlignAtLeft!
         Case "top"
            aAlignment = AlignAtTop!
         Case "right"
            aAlignment = AlignAtRight!
         Case "bottom"
            aAlignment = AlignAtBottom!
         Case "floating"
            aAlignment = Floating!
      End Choose
      // Set the base settings for the toolbar
      a_wWindow.SetToolbar( 1, bVisible, aAlignment, szTitle)
      // Obtain the other settings for the toolbar ...
      nRow = ProfileInt( g_App.i_szINIFile, szSection, "Row", 1)
      nOffset = ProfileInt( g_App.i_szINIFile, szSection, "Offset", 0)
      nX = ProfileInt( g_App.i_szINIFile, szSection, "X", 0)
      nY = ProfileInt( g_App.i_szINIFile, szSection, "Y", 0)
      nWidth = ProfileInt( g_App.i_szINIFile, szSection, "Width", 0)
      nHeight = ProfileInt( g_App.i_szINIFile, szSection, "Height", 0)
      // ... and then set them for the toolbar
      a_wWindow.SetToolbarPos( 1, nRow, nOffset, FALSE)
      a_wWindow.SetToolbarPos( 1, nX, nY, nWidth, nHeight)
   End If
End If

Tricks with Menus

Using Powersoft functions and attributes, there are a number of little tricks to using menus. Here are some examples to illustrate these functions and attributes.

Implementing an Edit Menu

In Windows 95 applications the operating system already provides a full pop-up edit menu for editable controls. For developers using other operating systems, these features might not be available, and a brief description of the code required is given next.

The code listed for each of the edit actions could be incorporated into a base-level menu ancestor or implemented as global or nonvisual object functions.

The standard Edit menu consists of Undo, Copy, Cut, Paste, and Clear. Only the code for the Undo option is shown in detail because the other options only require a function name change. This edit functionality can be applied only to DataWindows, drop-down list boxes, edit masks, multiline edits, single-line edits, and OLE 2.0 controls.

Undo

An Undo menu option should cancel the last edit that was made to an editable control; use the PowerBuilder Undo() and CanUndo() functions.

The Undo() function cannot be used with drop-down list boxes or OLE 2.0 controls. To see whether the last action can be undone, use the CanUndo() function:

GraphicObject goObject
DataWindow dwUndo
EditMask emUndo
MultiLineEdit mleUndo
SingleLineEdit sleUndo
goObject = GetFocus()        // Saves us calling the f() multiple times
If Not IsNull( goObject) Then
   Choose Case TypeOf( goObject)
      Case DataWindow!
         dwUndo = goObject
         If dwUndo.CanUndo() Then
            dwUndo.Undo()
         End If
      Case EditMask!
         emUndo = goObject
         If emUndo.CanUndo() Then
            emUndo.Undo()
         End If
      Case MultiLineEdit!
         mleUndo = goObject
         If mleUndo.CanUndo() Then
            mleUndo.Undo()
         End If
      Case SingleLineEdit!
         sleUndo = goObject
         If sleUndo.CanUndo() Then
            sleUndo.Undo()
         End If
   End Choose
End If

You have to set a variable of the correct object type to the current object because the GraphicObject object does not have either Undo() or CanUndo() as object functions.

Copy

The Copy menu option nondestructively (that is, leaves the highlighted text alone) duplicates the value in the current control into the Windows Clipboard. Only the part that is highlighted is copied. For this you use the PowerBuilder Copy() function.

If the control is a drop-down list box, the AllowEdit attribute must be set to TRUE; otherwise, the control is effectively a list box. Here's an example:

Case DropDownListBox!
   ddlbCopy = goObject
   If ddlbCopy.AllowEdit = TRUE Then
      ddlbCopy.Copy()
   End If

For a DataWindow control, the text value is copied from the edit box, not from the column.

If for some reason you need to trap when nothing was copied or trap what happened with an OLE 2.0 control, you can examine the return value of Copy(). For the edit controls, the number of characters copied is returned: If the control was empty, the value is 0, and on an error it is -1. If it was an OLE 2.0 control, the return value is 0 for a success, -1 if the control is empty, -2 if the copy fails, and -9 for all other errors.

Cut

The Cut menu option destructively (that is, removes the highlighted text) moves the value in the current control into the Windows Clipboard. Only the part that is highlighted is moved. For this you use the PowerBuilder Cut() function.

The same restrictions for the Copy() function apply to Cut().

The return value for Cut() is identical to that of Copy(). The return values, if it was an OLE 2.0 control, are the same except that -2 means the cut failed. Cutting an OLE object breaks any connection between it and the source file or storage.

Paste

The Paste menu option inserts into the current text or overwrites the highlighted section, or with OLE controls, completely replaces the object. For this you use the PowerBuilder Paste() function.

For DataWindow controls the text is pasted into the edit field, not the column. If the value does not match the data type of the column, the whole value is truncated so that an empty string is inserted. For all controls, Paste() copies only as many characters as will fit in the control; the rest are truncated. When an OLE object is pasted into an OLE 2.0 control, the data is only embedded, not linked.

The return value for Paste() is the number of characters that were pasted. If the Clipboard does not contain a textual value, the function does nothing and returns 0. With OLE 2.0 controls, the function returns 0 on success, -1 if the Clipboard contents are not embeddable, and -9 on all other errors.

Clear

The Clear menu option deletes the selected text or OLE 2.0 control and does not place it in the Clipboard. For this you use the PowerBuilder Clear() function.

Clearing an OLE 2.0 control's object deletes all references to it, does not save any changes that were made, and breaks any connections.

The return value for Clear() is 0 on success and -1 on an error. For OLE 2.0 controls, 0 also indicates success, but -9 indicates that an error occurred.

You can place a controlling script in the Clicked event for the Edit menu title that enables and disables the appropriate menu items, depending on the object that has current focus or whether there is anything to paste. You might expand this menu to include the capability to use the PasteSpecial() and PasteLink() functions.

Maintaining the Edit Menu

It is also helpful for the end user if you enable and disable the edit menu options as appropriate (for example, if no sheet window is currently open, or when the current control on a sheet is an edit control):

GraphicObject goObject
DataWindow dwUndo
EditMask emUndo
MultiLineEdit mleUndo
SingleLineEdit sleUndo
Window wInstance
Integer nSelectedLength
m_undo.Enabled = FALSE
 m_copy.Enabled = FALSE
 m_cut.Enabled = FALSE
 m_paste.Enabled = FALSE
 m_clear.Enabled = FALSE
wInstance = ParentWindow.GetActiveSheet()
If Not IsValid( wInstance) Then
   Return
End If
goObject = GetFocus()        // Saves us calling the f() multiple times
If IsNull( goObject) Then
   Return
Else
   Choose Case TypeOf( goObject)
      Case DataWindow!
         dwUndo = goObject
         nSelectedLength = dwUndo.SelectedLength()
      Case EditMask!
         emUndo = goObject
         nSelectedLength = emUndo.SelectedLength()
      Case MultiLineEdit!
         mleUndo = goObject
         nSelectedLength = mleUndo.SelectedLength()
      Case SingleLineEdit!
         sleUndo = goObject
         nSelectedLength = sleUndo.SelectedLength()
   End Choose
End If
If nSelectedLength > 0 Then
   m_copy.Enabled = TRUE
   m_cut.Enabled = TRUE
   m_clear.Enabled = TRUE
End If
If ( Len( Clipboard()) > 0) Then
    m_paste.Enabled = TRUE
End If

Accessing the Open Sheet Menu

Maybe you are going to track the open sheets using the dynamic window list menu, or perhaps you need access to other window titles. Whichever it is, the following piece of code can be used to traverse a cascaded menu.

These API functions will be used and should be declared either globally or locally:

FUNCTION uInt GetMenu( uInt hWnd) LIBRARY "user32.dll"
FUNCTION uInt GetMenuItemID( uInt hWnd, int nPosition) LIBRARY "user32.dll"
FUNCTION uInt GetMenuItemCount( uInt hWnd) LIBRARY "user32.dll"
FUNCTION uInt GetSubMenu( uInt hWnd, int nPosition) LIBRARY "user32.dll"
FUNCTION uInt GetMenuStringA( uInt hWnd, uInt nItem, REF string szItem, &
                              int nMax, uint nByCommand) LIBRARY "user32.dll"


NOTE: Developers using Windows 3.x need to replace the user32.dll filename with user.exe and change the name of the GetMenuStringA() function to GetMenuString().

The code that makes use of these functions would probably be placed in a menu function. Listing 12.3 is an example of a menu function used to traverse an open sheet menu.

Listing 12.3. Code for traversing the open sheet menu.

uInt hMainMenu, hMenuTitle, hMenuItem
Integer nLength, nMaxSize = 32, nNoOfMenuItems, nItem, nItemPosition
String  szBuffer
szBuffer = Space( nMaxSize)
// Get main menu handle of a window passed as an argument
hMainMenu = GetMenu( Handle( ParentWindow))
// GetSubMenu()'s second argument is the position of the menu
// title that you want the handle. Within this menu we maintain
// a menu instance variable that is used in all OpenSheet() calls.
// However, PowerBuilder is based from 1 onwards, Windows is 0 based.
hMenuTitle = GetSubMenu( hMainMenu, i_nWindowList - 1)
// Get a count of the menu item for the menu title
nNoOfMenuItems = GetMenuItemCount( hMenuTitle)
// We can now loop through the menu and explore each item
Do While nItem < nNoOfMenuItems
   // Get the handle of a menu item
   hMenuItem = GetMenuItemID( hMenuTitle, nItem)
   // The menu item text is returned into the string szBuffer
   // The last argument is MF_BYCOMMAND (0) so that we can use hMenuItem
   nLength = GetMenuStringA( hMainMenu, hMenuItem, szBuffer, nMaxSize, 0)
   nItem ++
Loop

The value of nLength is the length of the text that was copied into szBuffer. For menu items that are line separators, nLength is 0, and szBuffer contains an empty string.


NOTE: Remember that the text value contains an ellipsis (...) and the accelerator indicator (&). It also contains the number of the sheet (that is, if the menu item is 1 Stock Sheet, the value returned is &1 Stock Sheet).

Similar in concept to the previous example is the capability to scan through a menu to find a particular menu item. You can do this easily and without having to resort to API functions by using the MenuID attribute of a window and its associated array of menu items.

For example, the code shown in Listing 12.4 is built into a window-level function that takes a menu title and item, locates that item, and disables it.

Listing 12.4. Traversing the MenuID attribute.

Integer nMenuTitle, nTotalTitles, nMenuItem, nTotalItems
// Get a count of the top level menu titles
nTotalTitles = UpperBound( this.MenuID.Item)
For nMenuTitle = 1 To nTotalTitles
   // Locate the required menu title
   If this.MenuID.Item[ nMenuTitle].Text = a_szTitle THEN
      nTotalItems = UpperBound( this.MenuID.Item[ nMenuTitle].Item)
      For nMenuItem = 1 To nTotalItems
         // Locate menu item
         If this.MenuID.Item[ nMenuTitle].Item[ nMenuItem].Text = a_szItem Then
            Disable( this.MenuID.Item[ nMenuTitle].Item[ nMenuItem])
            nMenuItem = nTotalItems
            Exit
         End If
      Next
      Exit
   End If
Next

You can use this technique when you do not know the name of the menu with which the window is associated. It also prevents the need to hard code menu names.

Menus and OLE

With OLE 2.0 in-place activation, the OLE server's menu becomes the active menu to enable you to work on the object within its own context. The server menu can be merged into the current PowerBuilder application's menu by making use of the MergeOption attribute for each menu title. This attribute can be found in the Menu Merge Option drop-down list in the Style tab page of the Menu painter:

Enumerated Data Type Description
Exclude Do not include it in the OLE server menu.
Merge Add this menu title and cascading menu into the OLE server menu, appearing after the first menu title of the server.
File This menu title is leftmost on the menu bar. The server's File menu is not used.
Edit The server's Edit menu displays in place of this Edit menu title.
Window The menu that lists the open window. The server's Window menu is not used.
Help The server's Help menu displays instead of this Help menu title.

The default value for the MergeOption attribute is Exclude.

The In-Place settings cause the menu bar to display the PowerBuilder application's File and Window menus and the server's Edit and Help menus. Any menus that you label as Merge are added into the other server menu titles. For more information on OLE, see Chapter 39, "OLE 2.0 and DDE."

Summary

In this chapter you have explored how to physically create menus and logically create better menus. You have examined a number of guidelines that will lead to the construction of better menus. You have also taken a detailed look at the use of menus in an MDI application and some of the tricks and traps of using toolbars.


Previous chapterNext chapterContents

© Copyright, Macmillan Computer Publishing. All rights reserved.