Author Archives: Captain C

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).

Assertions in Basic+

For those of you who have done any Java or C/C++ programming in the past, assertions may be a familiar programming construct. For those who have not, an assertion is simply a way of embedding tests in your programs to check that a condition is true: if the condition evaluates to false then the program stops to display a message informing you of the failure, and presents a set of choices for dealing with it.

Assertions are basically “sanity checks” that you can employ anywhere in your programs to ensure that the state of your data is as you expect it.  You should use normal error-handling code for errors you expect; you should use assertions for errors that should never occur.

The $assert() statement

In order to support assertions Basic+ a new statement, “$assert” has been introduced into Basic+. This takes a simple comparison expression as an argument like so:

$assert( a > 3 )

The statement above checks that the value of the variable “a” is greater than 3.  If so then the program continues as normal, otherwise an assertion is raised and the assert message displayed.

When passing expressions to the $assert statement ensure you keep them simple.  The $assert is effectively turned into an “if () else” statement when it is compiled, so the resulting runtime code from the example above looks like this:

if ( a > 3 ) else <show assert message>

Therefore you can only pass what you would legally be able to pass to the normal “if” statement.

The $assert_here statement

This statement is an extension of the normal $assert statement and is used to simply assert without testing for an expression.  This is commonly used in an “end else” clause when you enter an unexpected code branch.

It has two forms:

  • $assert_here
  • $assert_here( <text> )

The latter form takes a text string (you don’t have to quote it) like so:

if bLen( winID ) then
   // All good
end else
   // We can't get here amiright?
   $assert_here( winID is null - how did this happen? )
end

Failed assertions

If the assertion fails you are presented with a message that looks like this:

Assertion message

Assertion message

It tells you the program name, the line number and the expression that caused the failure and gives you three options for processing:

  • Abort – This stops all program execution and returns the system to an idle state.  Just as if you had hit the debugger and then immediately closed it.
  • Debug – This loads the debugger at the point of the assertion.
  • Ignore – This continues the program execution as normal.

There is also a checkbox that allows you to turn off all assertions for the rest of the session.

Note that if you are running a program from the system monitor the assertion dialog looks slightly different as we rely on the Windows MessageBox function to display the error instead:

Assertion MessageBox

Assertion MessageBox

As you can see, the “Debug” button is replaced by a “Retry” button, but they both perform the same function.

Currently assertion messages will not display outside of event context, due to the fact that the message needs a UI to display, and if called from something like a web-server this would be problematic.  In this case assertions are ignored.

Disabling assertions

Assertions present a tidy way to deal with some problems without crashing straight into the debugger, but even so you may not not want your customers to see them in your released code.  In this case you ensure they are never executed by using one of the following methods:

Disabling assertions with the SetDebugger() function

The SetDebugger function can be used to programmatically turn assertions on or off at runtime using its “ASSERT” method.  This can be used when your application is started, and then turned on later for diagnostic purposes if you wish.

$insert logical

// Turn off assertions at runtime
call SetDebugger( "ASSERT", FALSE$ )

// Turn assertions back on
call SetDebugger( "ASSERT", TRUE$ )

Disabling assertions with the compiler

You can also disable assertions when compiling, in which case the checks are never included in the object code.  To do this simply use the NOASSERT token with the #define statement like so:

#define NOASSERT

You can add this to individual programs as you wish,  or to an IDE build configuration, or to the compiler IFDEF list itself if you call it manually.

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

BLen is the new GetByteSize

As one observant commenter noticed in our last post there’s a new Basic+ function called “BLen” in the version 10 compiler.  This is simply a synonym for the standard GetByteSize function, and was added to:

  1. Save me some typing effort (very important)
  2. Fit in with some of the other binary functions like BRemove and BCol2.

Of course, you may be wondering why GetByteSize/BLen is being used so much that I got tired of typing it?  It’s simply that as we progress through the v10 codebase we’re updating the code to be  “UTF8-safe” – i.e. we’re aiming to ensure that we don’t lose any performance when running in UTF8 mode, and a common Basic+ programming pattern for detecting a non-null variable is this:

   If Len( someVar ) Then
      // Variable is not null
   End

Variables in Basic+ are length-encoded, i.e. they cache the number of bytes that they occupy in memory.  When running in ANSI mode the Len statement simply returns this number (because 1-byte always equals 1 character) so if it’s zero you know you don’t have any data. However, because UTF8 is a multi-byte character-encoding format, the Len statement in UTF8-mode has to scan the contents of the entire variable to count the number of characters – it can’t use the cached byte-count.  This means that a simple check with Len could trigger this counting process when all you really want to know is if the variable contains data, and this could impact performance when dealing with large strings or arrays.

So, the best option is to use GetByteSize rather than Len, which always returns the cached byte-count regardless of ANSI or UTF8-mode, but as I don’t like typing very much you can now use BLen instead.

If you’re interested in writing UTF8-safe code and you’re not familiar with the Basic+ binary functions, you can find more details on them in a series of posts I wrote a few years ago on the Sprezzatura blog.  You may also want to check out the Internationalization section in the OI Coding Standards document too for some more UTF8-mode hints and tips.

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

 

 

 

 

 

The FILEPREVIEW control

One of the new controls added to version 10 is the FILEPREVIEW control, which taps into the Windows Shell interface to expose the same  functionality provided by the Windows Explorer when previewing the contents of files, as per the example below:

explorer_preview

Windows Explorer PowerPoint preview

Using the FILEPREVIEW control is quite easy – simply set the FILENAME property with the name and path of the file you want to preview, and if the OS has handlers installed for that file type then the control will render them.

Here’s an example of previewing a PowerPoint file:

  fileName = get_Property( @window : ".EDL_FILENAME", "TEXT" )
  if bLen( fileName ) then
     call set_Property_Only( @window : ".FPV_VIEWER", "FILENAME", |
                             fileName )
  end
filepreview

FILEPREVIEW control example

The devil of course is in the details – your OS must have the correct handler DLLs installed for you to view the preview of the file, and this may rely on third party software being installed as well.  For example, to preview Word and Excel documents you must have Office installed, to preview PDF files you must have something like Adobe Reader installed and so on.  You can test for this at runtime by using the PREVIEWHANDLER method, which returns a GUID identifying the preview handler DLL if it is installed.  You simply pass the extension you wish to look up and check for a returned GUID like so:

  handlerGUID = exec_Method( previewCtrl, "PREVIEWHANDLER", "pdf" )
  if bLen( handlerGUID ) then
    // We have a PDF preview viewer on the workstation ...
    call set_Property_Only( previewCtrl, "FILENAME", "c:\temp\test.pdf" )
  end 

The control also supports an ACTIVE property which returns TRUE$ if the control currently has a file loaded for previewing.

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

 

 

The AUTODROPDOWN property

One of the areas in which dropdown comboboxes have always been lacking is the ability to fully control when the list is populated in a visually pleasing manner.  Under most circumstances the contents are loaded well before the dropdown button is clicked, and this is fine, but in situations where the items are context -sensitive it is necessary to load them just before the list is shown, and in previous versions this can look a little messy.

You can see an example of this in the OI9 System Editor++: there is a very handy pair of dropdowns at the top if the form that allow you to navigate your source code using inserts and labels, but when you use them you see the list contents loaded just after it has been dropped; this looks somewhat jarring and usually causes issues with the vertical scrollbar too.

Of course, the reason for this is that OpenInsight events are normally fired in an asynchronous manner, and the DROPDOWN event won’t run until after the list has been shown.  While you do have the ability to fire events in a synchronous fashion, this can introduce other complications and is something that should be avoided whenever possible.

So, in order to fix this, OpenInsight 10 provides a new property called AUTODROPDOWN that allows the list to be loaded during the DROPDOWN event and then displayed precisely when needed.

AUTODROPDOWN is a boolean property and is set to TRUE$ by default.

  • When set to TRUE$ the combobox behaves in exactly the same way as previous versions.
  • When set to FALSE$, the list is not shown until you explicitly set the DROPDOWN property to TRUE$. This gives you ability to load the list contents and then show them when ready.

Here’s an example of loading a list of STPROC repository items in a DROPDOWN event when AUTODROPDOWN is FALSE$

  // Get a list of items
  reposIDs = get_Repos_Entities( "", "STPROC", "", FALSE$, |
                                 TRUE$, FALSE$, "" )
  
  // Load them into the combobox LIST property
  call set_Property_Only( ctrlEntID, "LIST", reposIDs )

  // Now show the list
  call set_Property_Only( ctrlEntID, "DROPDOWN", TRUE$ )

Comboboxes as options buttons?

Another area where AUTODROPDOWN may help is for systems that use comboboxes as a combined edit and options control, where clicking the dropdown button loads a dialog box or popup instead of dropping a list.  Again, in previous versions this tends to look a little crude because the combobox shows a very shallow list even though it is empty, but with AUTODROPDOWN set to FALSE$ the list is never shown, and consequently looks much better.

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

Listboxes and ToolTips

Following on from our previous post on the new TOOLTIP property, this time we’re going to look at the new tooltip functionality added to Listbox controls, namely tracking and in-place item tooltips.

Tracking tooltips are small popup windows that appear when a user hovers over a partially obscured item in the control: they display the full item text string instead, and thereby avoid the need for horizontal scrolling, which is always a preferable user experience.

listbox_tracking_tooltips

Offset tracking tooltip

In-place tooltips operate in a similar manner, but rather than appearing at an offset to the cursor position they actually appear over the item itself, hence the term “in-place”:

listbox_inplace_tooltips

In-place tracking tooltip

This behaviour is controlled by the new SHOWITEMTOOLTIPS property which can be set to one of the following values:

  "0" - Disabled : Item tooltips are not displayed
  "1" - Offset   : Item tooltips are displayed at an offset to the cursor
  "2" - In-place : Item tooltips are displayed over the item itself.

When set to “1” (Offset) or “2” (In-place) the SHOWITEMTOOLTIPS property overrides the normal TOOLTIP property, so a “normal” tooltip will not be displayed.

Displaying alternative item text

Sometimes it is desirable to display a different text string in the tooltip rather than the item text itself.  In this case you can set the new SHOWVALUESASTOOLTIPS property to specify that the tooltip should display the contents of the item’s VALUE property instead.

SHOWVALUESASTOOLTIPS is a simple boolean property of TRUE$ or FALSE$.

listbox_tracking_value_tooltips

Displaying the Value property as a tooltip

Note in this case the tooltip is always displayed at an offset, and is triggered regardess of whether or not the item is clipped.

 

Combobox and TreeListbox controls

Both new properties described here also apply to simple Combobox and TreeListbox controls.

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