Tag Archives: ContextMenu

Context Menus in OpenInsight 10 – Part III

To finish off this short series on context menus we’re going to take a look a couple of new control methods, and also some new functionality in the ContextMenu stored procedure.

The ATTACHMENU method

successFlag = exec_Method( ctrlEntID, "ATTACHMENU", menuID )

As mentioned in the previous post, context menus are not usually created until they are needed, after which they are cached and made ready for subsequent use.  If the context menu supports accelerator keys (like the one used with the group selection control in the Form Designer) this is a problem, as there is nothing for the system to scan when looking for a keystroke handler.

Instead of waiting for the user to right click on a control the menu may be created without being displayed using the ATTACHMENU method.  This task is normally performed in the CREATE event for specific controls that need it.

Parameters

  • MenuID – This is the fully qualified repository ID of a context menu to attach.  It defaults to the contents of the control’s CONTEXTMENU property.

Returns

  • TRUE$ if the menu is attached successfully, or FALSE$ otherwise.

Even though the menu is not displayed it still progresses through the standard INITCONTEXTMENU and CONTEXTMENU events as described in Part II.  This is when the aforementioned AttachOnly parameter in the CONTEXTMENU event will be set to TRUE$, and this is why you shouldn’t modify it in your event handler.

The SHOWMENU method

successFlag = exec_Method( ctrlEntID, "SHOWMENU", xPos, yPos )

This method displays the control’s context menu at the specified coordinates. No attempt is made by the system to provide default coordinates, so you must decide where you want the menu to appear.

Parameters

  • XPos – Horizontal screen position to display the menu at.
  • YPos – Vertical screen position to display the menu at

Returns

  • TRUE$ if the menu is displayed successfully, or FALSE$ otherwise.

 

The ContextMenu stored procedure

This program has been part of OpenInsight for a while, but has been updated with some new methods to help with managing context menus in v10.  The available methods are:

  • ADDQUICKEVENTS
  • CREATEMENU
  • GETVALUE
  • SETVALUE

ContextMenu ADDQUICKEVENTS method

If you dynamically add menu items during the CONTEXTMENU event you will also need to add some way of responding the MENU events raised when a user selects them.  As usual you have two choices – an event script or a QuickEvent.  For the former you can pass a normal script qualifier ID directly in the item’s menu structure, but for latter this is not so: QuickEvents are held within the cached menu structure and therefore need special treatment.

This is the purpose of the ADDQUICKEVENTS method – it takes a list of menu item IDs, along with their QuickEvent specifications, and adds them to the internal structure.

successFlag = contextMenu( ctrlEntID,        |
                           "ADDQUICKEVENTS", |
                           menuIDs,          |
                           eventHandlers )

 Parameters

  • MenuIDs – This is an @vm-delimited list of menu IDs to set the QuickEvent handlers for.  Note these are not fully qualified – you don’t need to pass the name of the control and the “.CONTEXTMENU.” prefix.
  • EventHandlers – This is an @vm-delimited list of QuickEvent handler specifications for each item passed in MenuIDs.  Each handler is an @svm-delimited array with the following structure:
<0,0,1>  Event Type
<0,0,2>  Message
<0,0,3>  Target
<0,0,4>  Parameters (@tm-delimited)
<0,0,5>  Return Target
<0,0,6>  Return Message
<0,0,7>  Return Type
<0,0,8>  Return Parameters
<0,0,9>  Return Flags (@tm-delimited)
<0,0,10> Final Type
<0,0,11> Final Message
<0,0,12> Final Target
<0,0,13> Final Parameters (@tm-delimited)
<0,0,14> Final Flags

(This is a standard QuickEvent structure and is documented more fully in the NPHANDLER_EQUATES insert record)

Returns

  • TRUE$ if the events are added successfully, or FALSE$ otherwise.

Example

// This is a simple example of adding an item in the CONTEXTMENU
// event and setting a QuickEvent handler for it.
//
// We are inserting an item just after a separator called "TEST_SEP"
// and we will add a quick event for it to call the window's commuter
// module when it gets selected.

declare function contextMenu, rti_Convert
$insert oiWin_Equates
$insert npHandler_Equates
$insert logical

sepID     = ctrlEntID : ".CONTEXTMENU.TEST_SEP"
insertPos = 0
xCount = fieldCount( menuStruct, @vm )
for x = 5 to xCount ; // ignore the header fields
   if ( menuStruct<0,x>[1,1] == "@" ) else
      if ( menuStruct<0,x,MENUPOS_NAME$> == sepID ) then
         insertPos = x + 1
         x = xCount; // break
      end
   end
next

if insertPos then
   itemID = ctrlEntID : ".CONTEXTMENU.TEST_ITEM"
   menuItem = ""
   menuItem<0,0,MENUPOS_TYPE$> = "ITEM"
   menuItem<0,0,MENUPOS_NAME$> = itemID
   menuItem<0,0,MENUPOS_TEXT$> = "Test Item"

   // Add the item to the menu structure
   menuStruct = insert( menuStruct, 0, insertPos, 0, |
                        menuItem )

   // And give it a quick event
   qeID      = "TEST_ITEM"
   qeHandler = ""
   qeHandler<0,0,NP_MSGTYPE$> = "R"
   qeHandler<0,0,NP_MSG$>     = "EXECUTE"
   qeHandler<0,0,NP_TARGET$>  = @appID<1> : "*STPROCEXE**@COMMUTER"
   qeHandler<0,0,NP_ARGS$>    = rti_Convert( "@SELF,@EVENT", ",", @tm )

   call contextMenu( ctrlEntID, "ADDQUICKEVENTS", |
                     qeID,                        |
                     qeHandler )
end

ContextMenu CREATEMENU method

successFlag = CreateMenu( ctrlEntID, "CREATEMENU", menuStruct, |
                          "", "", "", altMenuID )

This method is the same as it was in previous versions of OpenInsight.  It attempts to load a context menu entity with the same name as the parent control, optionally overriding it with a passed structure.

Parameters

  • ctrlEntID – Fully qualified name of the control to attach the menu to.
  • menuStruct – (optional) A dynamic array containing a menu structure that overrides the stored one.  This structure can be in either the old v9 format (as documented in the v9 online help), or the new v10 format as discussed in Part II.
  • altMenuID – (optional) Specifies the name of the menu to load, overriding the default behaviour that assumes we are creating a menu with the same name as the parent control.

Returns

  • TRUE$ if the menu was created and attached successfully, or FALSE$ otherwise.  Errors are returned via the Set_Status stored procedure.

When you use this method the menu structure is parsed and cached by the control using the INITCONTEXTMENU event; it is not displayed until the user right-clicks on it (in a similar fashion to the ATTACHMENU method)

ContextMenu GETVALUE method

This is a simple helper function for inspecting values in a menu item as discussed in Part II of this series.

itemValue = contextMenu( itemID, "GETVALUE", menuStruct, itemValueIdx )

Parameters

  • ItemID – Fully qualified name of the item to query.
  • MenuStruct – Menu structure to parse.
  • ItemValueIdx – Index of the item value to return (see OIWIN_EQUATES).

Returns

  • The requested value.

ContextMenu SETVALUE method

This is a simple helper function for updating values in a menu item as discussed in Part II of this series.

origValue = contextMenu( itemID, "SETVALUE", menuStruct, itemValueIdx, |
                         newValue )

Parameters

  • ItemID – Fully qualified name of the item to query.
  • MenuStruct – Menu structure to parse.
  • ItemValueIdx – Index of the item value to return (see OIWIN_EQUATES).
  • NewValue – New value to set.

Returns

  • The original value that was replaced by the new value.

 

That concludes this small trilogy on context menus – hopefully you will find them much easier to use in OpenInsight 10 and be able to make more use of them in your own applications.

(Disclaimer: This article is based on preliminary information and may be subject to change in the final release version of OpenInsight 10).

 

 

 

Context Menus in OpenInsight 10 – Part II

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:

  1. The INITCONTEXTMENU event
  2. The CONTEXTMENU event
  3. 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:
 //
 // <controlName> ".CONTEXTMENU." <itemName>
 //
 // 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).

Context Menus in OpenInsight 10 – Part I

Using context menus in previous versions of OpenInsight has always been something of a chore: they were not well documented and they were subject to several limitations:

  • They could only be attached to a control programmatically, rather than via the Form Designer.
  • The context menu designer tool would name a menu based on the control it was supposed to be attached to – there was no real concept of sharing a menu between controls.
  • They were limited to a single level – no sub-menu nesting was allowed.
  • They were difficult to modify at runtime.

These issues made them quite onerous to use, which is unfortunate as context menus are an important part of modern UI design, having been in widespread use across the OS since Windows 95.

With version 10 we went back to the drawing board and completely redesigned them to make them first class UI citizens and solve the problems outlined above.

The Context Menu Designer

One of the new tools in the IDE is the Context Menu Designer, which allows you to define the structure of the menu along with any quick events (Event scripts are not supported for context menus).

Context Menu Designer (WIP)

Context Menu Designer (WIP)

Menus saved by the designer are given simple names in the same format as any other repository entity – they are no longer based on the name of a specific control.  They can then be attached in the Form Designer by using an object’s CONTEXTMENU property.

(Note that like normal WINDOW menus they also support nested structures as they share the same code-base.)

The CONTEXTMENU property

Forms and controls now support an explicit CONTEXTMENU property which is the repository ID of a saved context menu.

ContextMenu Property

ContextMenu Property

At runtime, when a user right-clicks on a control, the Presentation Server looks at the CONTEXTMENU property to find the name of an attached menu, and, if found, displays it.

When the user selects an item from context menu a MENU event is raised and sent to the quick event target defined in the designer.

This is a much simpler and streamlined process.

Of course, that’s not the end of the story as there are times when the context menu will need to be adjusted before it is displayed at runtime, depending on factors like the state of it’s parent control and so on.  We’ll take a look at how to do that in the next post.

(Disclaimer: This article is based on preliminary information and may be subject to change in the final release version of OpenInsight 10).