Context Menus in OpenInsight 10 - Part II
Published 22 AUG 2017 at 12:22:53PM
In our last post we looked at the CONTEXTMENU property and the way in which a context menu can be linked to a form or control at design time. This time we'll take a look at how to alter those menus at runtime before they are displayed, which is sometimes necessary depending on the state of the parent control and/or its environment. When a context menu is about to be displayed the system goes through three distinct phases:
- The INITCONTEXTMENU event
- The CONTEXTMENU event
- A call to the TRACKPOPUPMENU method.
Generally speaking a context menu is not created until needed, after which it is cached for subsequent access.
The INITCONTEXTMENU event
This event is fired by the Presentation Server in response to a right click (actually a WM_CONTEXTMENU message from Windows) and is responsible for the following tasks:
- Calling the Yield() stored procedure to clear any pending events
- Calling an INITCONTEXTMENU quick event, if defined.
- Reading the context menu definition from the repository (if it's not cached)
- Converting the structure into v10 format if needed
- Compiling it into an "executable" format
- Caching it
- Firing the subsequent CONTEXTMENU event
The intent of INITCONTEXTMENU is as a tool for the Presentation Server to kick off the context menu process, so as such it is a system tool - it is not really intended that developers have to interact with this event, although there's nothing to stop you should you wish to do so.
The CONTEXTMENU event
This is the point where the context menu is about to be displayed, and offers you a chance to modify it. The CONTEXTMENU event is passed five parameters:
- MenuID - the identifer of the context menu to display
- MenuStructure - a dynamic array containing the executable structure of the menu - this is the same format as used for standard OpenInsight Window menus.
- XPos - the horizontal position of the cursor, in screen coordinates, at the time of the mouse click.
- YPos - the vertical position of the cursor, in screen coordinates, at the time of the mouse click.
- AttachOnly flag - if this flag is TRUE$ then the menu will only be "stored" ready to be displayed. This is a more advanced feature for use with context menus that have their own accelerator keys, because the menu needs to be created to trap the keystokes, even if it has not been displayed yet. We'll cover this in a later post, but you should leave this parameter unmodified.
You can intercept this event from a script, or from a QuickEvent.
- If you prefer to use a script then you must call the Forward_Event stored procedure to display the menu and return FALSE$ from your script (otherwise you will see the menu twice).
- If you use a QuickEvent you need to return TRUE$ from your event handler so that the menu is executed. You can also use the Set_EventStatus stored procedure to stop the menu from being displayed and return information as to why it was cancelled.
Moving the menu
If you adjust the XPos or YPos parameters you can alter the position at which the menu is displayed. These coordinates are normally the point at which the right mouse-button was clicked, but if the context menu was triggered by the keyboard the Presentation Server attempts to pick a suitable location - for many controls this would be left-aligned underneath the parent object, or underneath the current cell for an EditTable control and so on.
Modifying the menu structure
The format of the context menu structure has changed in v10 to use the same format as normal window menus, so they now support nested sub-menus. The layout of this structure is described the OIWIN_EQUATES insert record, and if you're familiar with this structure you'll notice it's been expanded to include more image information:
equ MENUPOS_TYPE$ to 1 equ MENUPOS_END$ to 2 equ MENUPOS_NAME$ to 3 equ MENUPOS_TEXT$ to 4 equ MENUPOS_GREY$ to 5 equ MENUPOS_CHECK$ to 6 equ MENUPOS_HIDDEN$ to 7 equ MENUPOS_ACCEL$ to 8 equ MENUPOS_HELP_TEXT$ to 9 equ MENUPOS_HANDLER$ to 10 equ MENUPOS_STYLE$ to 11 equ MENUPOS_BITMAP$ to 12 equ MENUPOS_COLORKEY$ to 13 equ MENUPOS_IMAGELISTINDEX$ to 14 equ MENUPOS_IMAGEAUTOSCALE$ to 15 equ MENUPOS_IMAGEFRAMEINDEX$ to 16 equ MENUPOS_IMAGEOFFSET$ to 17 equ MENUPOS_IMAGEORIGIN$ to 18 equ MENUPOS_IMAGETRANSLUCENCY$ to 19 equ MENUPOS_MISC$ to 20 equ MENUPOS_RESERVED_1$ to 21 equ MENUPOS_RESERVED_2$ to 22
As you have access to the raw structure you may modify it in any way you please, but we have included a pair of helper methods in the ContextMenu() stored procedure (GETVALUE and SETVALUE) to deal with setting simple values like so:
// Set hide a menu item call contextMenu( itemID, "SETVALUE", menuStruct, MENUPOS_HIDDEN$, TRUE$ ) // Disable a menu item call contextMenu( itemID, "SETVALUE", menuStruct, MENUPOS_GREY$, TRUE$ )
…. and so on. These functions themselves are very simple and just iterate over the structure until they find the passed ID. You can do this yourself quite easily, but these make your code look a little neater. Of course if you have a lot of modifications to make then parsing the structure yourself will be faster, so here's a bare bones example to get you started:
// MenuID - ID of the item to modify. Has the format: // // ".CONTEXTMENU." // // e.g. // // CUSTOMERS.EDL_FORENAME.CONTEXTMENU.PASTE xCount = fieldCount( menuStruct, @vm ) for x = 5 to xCount if ( menuStruct<0,x>[1,1] == "@" ) then null ; // ImageList header field - ignore end else if ( menuStruct<0,x,MENUPOS_NAME$> == menuID ) then // Found it - disable it menuStruct<0,x,MENUPOS_GREY$> = TRUE$ x = xCount; // break; end end next
Inserting and removing items is a little more involved, due to the need to preserve the end flags in the correct location, but it is quite possible with a bit of care and attention. We won't be covering that here however, so for the present this is left as an exercise for the reader.
Calling the TRACKMENUPOPUP method
Once you've finished with your modifications you can simply let the CONTEXTMENU event complete which calls the TRACKPOPUPMENU method to actually display the menu. TRACKPOPUPMENU is a new method that displays a context menu at the specified coordinates. In the unlikely event that you need to call it yourself here are the details:
bSuccess = exec_Method( ctrlEntID, "TRACKPOPUPMENU", | menuStruct, | xPos, | yPos, | uFlags )
As you can see most of the parameters are the same as passed for the CONTEXTMENU event - the only difference being the "uFlags" argument. The TRACKPOPUPMENU method is actually a thin wrapper around the Windows API TrackPopupMenu() function and the "uFlags" argument in the method maps onto the "uFlags" argument in the Windows function. You can check the Microsoft documentation for more details on that if you wish. In the next post we'll wrap up this short series on context menus and take a look at the ContextMenu stored procedure. (Disclaimer: This article is based on preliminary information and may be subject to change in the final release version of OpenInsight 10).