This document covers the following topics:
As from Natural v4.1.1, it is possible to create context menus for use within Natural applications. The context menus can be completely static (i.e., the menu contents are known in advance and can be built via the dialog editor) or wholly or partially dynamic (i.e., the menu contents and/or state depend on the runtime context and are not completely known at design time).
A context menu is very similar in concept to a submenu. Therefore, the same menu editor is used for editing a context menu as is used for editing a dialog's menu bar. Menu items can be added to context menus, and events associated with them, in exactly the same way as for menu-bar submenus. There are no functional differences to the menu bar editor, except that the OLE combo box (which is applicable only to top-level menu-bar submenus) will always be disabled. It should be noted, however, that any accelerators defined for context menu items will be globally available as long as that menu item exists. Furthermore, the accelerator will trigger the menu item for which it is defined even if the context menu is not being displayed or if the focus is on a control using a different context menu or no context menu at all.
The context menu editor may be invoked via either a new menu item, CTRL+ALT+X by default), or toolbar icon. However, because the context menu editor can only edit one context menu editor at a time, the context-menu editor is not invoked directly. Instead, the Dialog Context Menus window is shown, where operations on the context menu as a whole are made, and from which the menu editor for a given (selected) context menu can be invoked.
on the menu, or via its associated accelerator (Internally, in order to distinguish between submenus and context menus,
context menus have a new type, CONTEXT MENU
. Otherwise, the
generated code in both cases is identical. Here is some sample code
illustrating the statements used to build up a simple context menu containing
two menu items:
/* CREATE CONTEXT MENU ITSELF: PROCESS GUI ACTION ADD WITH PARAMETERS HANDLE-VARIABLE = #CONTEXT-MENU-1 TYPE = CONTEXTMENU PARENT = #DLG$WINDOW END-PARAMETERS GIVING *ERROR /* ADD FIRST MENU ITEM: PROCESS GUI ACTION ADD WITH PARAMETERS HANDLE-VARIABLE = #MITEM-1 TYPE = MENUITEM DIL-TEXT = 'Invokes the first item' PARENT = #CONTEXT-MENU-1 STRING = 'Item 1' END-PARAMETERS GIVING *ERROR /* ADD SECOND MENU ITEM: PROCESS GUI ACTION ADD WITH PARAMETERS HANDLE-VARIABLE = #MITEM-2 TYPE = MENUITEM DIL-TEXT = 'Invokes the second item' PARENT = #CONTEXT-MENU-1 STRING = 'Item 2' END-PARAMETERS GIVING *ERROR
Note that if context menus or context-menu items are created dynamically in user-written code, the context menu or menu items will not be visible to the dialog editor. For example, the dynamically created menu item will not be visible in the context menu list box, and the dynamically created menu items will not be visible in the context menu editor.
After creating a context menu, the context menu needs to be associated with a Natural object. Context menus are supported for almost all controls types capable of receiving the keyboard focus and for the dialog window itself. The full list includes ActiveX controls, bitmaps, canvasses, edit areas and input fields, list boxes, push buttons, radio buttons, scroll bars, selection boxes, table controls, toggle buttons, standard and MDI child windows, and MDI frame windows.
For all object types supporting context menus, the corresponding attribute dialogs in the dialog editor include a read-only combo box listing all context menus created by the dialog editor, together with an empty entry. The selection of the empty entry implies that no context menu is to be used for this object, and is the default.
Internally, the association is achieved by a new attribute,
CONTEXT
MENU
, which should be set to the handle of a context
menu. This attribute can be assigned at or after object creation time, and
defaults to NULL-HANDLE
if not specified, indicating the absence
of a context menu. For context menus created by the
dialog editor, the context
menu is specified at control creation time as illustrated below:
PROCESS GUI ACTION ADD WITH PARAMETERS HANDLE-VARIABLE = #LB-1 TYPE = LISTBOX RECTANGLE-X = 585 RECTANGLE-Y = 293 RECTANGLE-W = 142 RECTANGLE-H = 209 MULTI-SELECTION = TRUE SORTED = FALSE PARENT = #DLG$WINDOW CONTEXT-MENU = #CONTEXT-MENU-1 SUPPRESS-FILL-EVENT = SUPPRESSED END-PARAMETERS GIVING *ERROR
The same syntax can also be used for controls created in user-written
event code. In other cases, where the control was created by the
dialog editor but the
context menu was not, the context menu attribute must be assigned to the
control after its creation, e.g., in the dialog's
AFTER-OPEN
event:
/* CONTEXT MENU SPECIFIED AFTER CREATION: #LB-2.CONTEXT-MENU := #CONTEXT-MENU-2
Note that a context menu is not destroyed when an object using it is
destroyed. Instead, it is destroyed when its parent object (typically, the
dialog for which the context menu was defined) is destroyed. Similarly, the
assignment of a new menu handle to the CONTEXT
MENU
attribute where one is already assigned does
not result in the previous context menu being destroyed. Thus, using the above
examples, neither of the following statements results in
CONTEXT-MENU-1
being destroyed:
PROCESS GUI ACTION DELETE WITH #LB-1 /* #CONTEXT-MENU-1 LIVES ON #LB-1.CONTEXT-MENU := #CONTEXT-MENU-2 /* SAME HERE
The context menu invocation process in Natural is as follows:
If the context menu is accessed via the mouse (i.e., secondary mouse button click), the target control is initially assumed to be the control immediately under the mouse cursor. Otherwise, if the context menu is accessed via the keyboard (i.e., either via the context menu key, if any, or via the key combination Shift+F10), the target control is initially assumed to be the control that currently has the keyboard focus.
The control's click position is set, relative to the target control's client area. If the context menu is accessed via the keyboard, the click position is set to (0, 0).
A CONTEXT-MENU
event is raised for the target control, if not suppressed via the
SUPPRESS-CONTEXT-MENU-EVENT
attribute.
The target control's CONTEXT-MENU
attribute is queried. Depending on its value and the type of the target
control, the following action is taken:
If the attribute is set to NULL-HANDLE
and the target
control is a dialog, the context menu invocation process is aborted, without
any context menu having been displayed.
If the attribute is set to NULL-HANDLE
and the target
control is a dialog element, the target control is assumed to be the dialog
element's PARENT
,
and the context menu invocation process repeats starting with step 2 above.
If the attribute is set to the handle of a context menu, this context menu is taken as being the context menu that needs to be displayed (i.e., the target context menu), and processing continues with step 5 below.
A BEFORE-OPEN
event is raised for the target context menu, if not
suppressed.
The target context menu's ENABLED
attribute is queried. If it is set to FALSE
, the context menu is
not displayed.
Otherwise, a
COMMAND-STATUS
event is raised for the target dialog, if not
suppressed. The
target dialog is the dialog containing the target control, if it is a dialog
element, or the target control itself, if it is a dialog.
The context menu is displayed at the click position set in step 2 above.
The actual navigation within the context menu and the triggering of the events associated with the menu items is done by Windows and Natural with no intervention from the application.
Note that the above process continues up through the control hierarchy, starting with the initial target control, until if finds a dialog or dialog element with a context menu (if any), and then uses that context menu.
The purpose of the
CONTEXT-MENU
event is to allow application to select the appropriate context menu
(by modifying the target control's CONTEXT-MENU
attribute) from a number of possible candidates according to the context. For
an example of using multiple context menus, see
Working with List View
Controls.
Similarly, the context menu's
BEFORE-OPEN
event gives the application the chance to modify the context menu
according to the current program state. For example, menu items could be added
or deleted, or particular menu items grayed or checked. Here is some sample
code for the BEFORE-OPEN
event:
/* DELETE FIRST MENU ITEM: PROCESS GUI ACTION DELETE WITH #MITEM-1 /* CHECK SECOND MENU ITEM: #MITEM-2.CHECKED := CHECKED /* DISABLE THIRD MENU ITEM: #MITEM-3.ENABLED := FALSE /* INSERT NEW MENU ITEM BEFORE #MITEM-3: PROCESS GUI ACTION ADD WITH PARAMETERS HANDLE-VARIABLE = #MITEM-4 TYPE = MENUITEM DIL-TEXT = 'Invokes the first item' PARENT = #CONTEXT-MENU-1 STRING = 'Item 3' SUCCESSOR = #MITEM-3 END-PARAMETERS GIVING *ERROR
For context menus not created by the
dialog editor, the handling
of the BEFORE-OPEN
event must be done in the
DEFAULT
event for the dialog. Note also that if a control or dialog is
disabled, no context menu is displayed, and the
BEFORE-OPEN
event is also not triggered. The same applies if the context menu
itself is disabled. For example:
#CONTEXT-MENU-1.ENABLED := FALSE /* DISABLE CONTEXT MENU DISPLAY
Note that it is possible to disable the context menu in this way during
the BEFORE-OPEN
event, allowing selective disabling of the context menu depending on
the mouse cursor position within the control. For example, it might be desired
to only display a context menu if the mouse cursor is over a selected list-box
item. Determining whether this is the case is possible via the use of two
PROCESS GUI ACTION
calls:
INQ-CLICKPOSITION
has been extended to controls other than bitmaps and canvasses to return the
(X, Y) position of the right mouse button click within the control. In
addition, these parameters are now optional, and a new optional parameter has
been introduced that is set to TRUE
if the context menu was
accessed via the mouse, or FALSE
if it was accessed by the
keyboard. In the latter case, the click position is set to (0, 0). All this
information is updated immediately prior to the sending of the
BEFORE-OPEN
event.
INQ-ITEM-BY-POSITION
.
This allows translation of the relative co-ordinate returned by
INQ-CLICKPOSITION
applied to a list box to the corresponding
item.
As an example of the use of these two new actions, consider the
situation where we want to detect whether the cursor was over a selected
list-box item when the right mouse button was pressed in order to determine
whether to display a context menu or not. This can be achieved by the following
code in the BEFORE-OPEN
event of the associated context menu:
PROCESS GUI ACTION INQ-CLICKPOSITION WITH #LB-1 #X-OFFSET #Y-OFFSET PROCESS GUI ACTION INQ-ITEM-BY-POSITION WITH #LB-1 #X-OFFSET #Y-OFFSET #LBITEM #MENU = *CONTROL IF #LBITEM = NULL-HANDLE /* NO ITEM UNDER (MOUSE) CURSOR */ #MENU.ENABLED := FALSE ELSE IF #LBITEM.SELECTED = FALSE /* ITEM UNDER CURSOR DESELECTED */ #MENU.ENABLED := FALSE ELSE /* ITEM UNDER CURSOR IS SELECTED */ #MENU.ENABLED := TRUE END-IF END-IF
In some cases, it may be desired to automatically select the item under
the mouse cursor if it is not already selected, clearing any existing
selection. For list boxes, it is possible to achieve this by using the new
AUTOSELECT
attribute, either directly or via the new Autoselect check
box in the List Box
Attributes window in the dialog editor. If this attribute is
set to TRUE
, Natural will automatically update the selection
before sending the BEFORE-OPEN
event, if the context menu was invoked over an unselected list-box
item.
For table controls, any change in the selection must be done via the
application itself in the
BEFORE-OPEN
event. To make this possible, another new PROCESS GUI
ACTION
has been introduced for table controls:
TABLE-INQUIRE-CELL
.
This returns the cell's row and column number (starting from 1) for a relative
(X, Y) position within the table. This position can (and would typically be)
the position returned by a previous call to PROCESS GUI ACTION
INQ-CLICKPOSITION
.
The COMMAND-STATUS
event is an alternative location for the application to perform any
updating such as graying and checking of commands (i.e., menu items, tool bar
items and signals). If you are already using this event, you do not need to
perform these actions in the
BEFORE-OPEN
event.
In addition to the automatic context menu invocation process described
above, it is also possible to invoke a particular context menu manually at a
specific position via the SHOW-CONTEXT-MENU
action.
This is primarily intended for (but not restricted to) use with ActiveX controls where the automatic mechanism is not always applicable. This is because some ActiveX controls, depending on their internal implementation, do not raise the message used by Natural to trigger the context menu display. In such cases, if the ActiveX control raises an event when the secondary mouse button is pressed, the context menu can be manually displayed within the event handler for that event via this action.
For example, assuming we wish to display the context menu
#CTXMENU-1
for the Microsoft Rich Textbox ActiveX Control,
#OCX-1
, we could use the following code in the control's MouseDown
event handler:
IF #OCX-1.<<PARAMETER-Button>> = 2 #X := #OCX-1.<<PARAMETER-x>> + 2 #Y := #OCX-1.<<PARAMETER-y>> + 2 PROCESS GUI ACTION SHOW-CONTEXT-MENU WITH #CTXMENU-1 #OCX-1 #X #Y GIVING *ERROR END-IF
where the following local data definitions are assumed:
01 #X (I4) 01 #Y (I4)
Note that the above code first checks whether the secondary mouse button was pressed, then invokes a context menu manually, based on the position passed by the control. The position is, however, first corrected slightly to account for the fact that the position supplied by the control is relative to the ActiveX control (which has a 2-pixel sunken border), whereas the position used to display the context menu is assumed to be relative to the ActiveX control's container window (which has no border).
Note that some ActiveX controls may return coordinates in units other
than pixels, such as twips (twentieths of a point). The following example shows
how to convert co-ordinates (#X
, #Y
) from twips to
pixels:
#CONTROL := *CONTROL /* Convert x-coordinate MULTIPLY #X BY #CONTROL.DPI DIVIDE #X BY 1440 /* Convert y-coordinate MULTIPLY #Y BY #CONTROL.DPI DIVIDE #Y BY 1440
where #CONTROL
is defined as HANDLE OF GUI
,
and #X
and #Y
are assumed to be of format I4.
The value 1440 is the number of twips per logical inch, whereas the DPI attribute applied to a dialog element returns the number of pixels per logical inch.
It is of course possible to associate the same context menu with more than one object (i.e., control or dialog). For example:
#LB-1.CONTEXT-MENU := #CTXMENU-1 #LB-2.CONTEXT-MENU := #CTXMENU-1
In such a scenario, we need to be able to determine for which control
the context menu was invoked. We cannot use
*CONTROL
in the BEFORE-OPEN
event, because this will contain the handle of the context menu
itself. Instead, it is necessary to inquire which control has the focus, since
Natural automatically places the focus on the control for which the context
menu is being invoked. Here is some sample
BEFORE-OPEN
event code illustrating the use of this technique:
PROCESS GUI ACTION GET-FOCUS WITH #CONTROL DECIDE ON FIRST VALUE OF #CONTROL VALUE #LB-1 #MITEM-17.ENABLED := FALSE VALUE #LB-2 #MITEM-17.CHECKED := CHECKED NONE IGNORE END-DECIDE
However, a better approach, which works in all cases, is to query the
context menu's CONTROL
attribute instead:
#CONTROL := *CONTROL DECIDE ON FIRST VALUE OF #CONTROL.CONTROL ... END-DECIDE