Tag Archives: Events

A focus on the FOCUS property

There are two methods for setting the input focus in OpenInsight, and there is a subtle implementation difference that can impact your applications if you’re not careful how you apply them.

The first method is to use an object’s own FOCUS property and set it to TRUE$ like so:

Call Set_Property_Only( @Window : ".EDL_SURNAME", "FOCUS", TRUE$ )

This moves the focus to the specified object, but the event queue will be flushed both before and after the focus has been set, thereby preventing any events raised as a result of the focus being moved from being processed.  This method was originally designed for use with validation routines so the focus could be reset to an invalid control “safely”.

The second method is to use the SYSTEM object’s FOCUS property:

Call Set_Property_Only( "SYSTEM", "FOCUS", @Window : ".EDL_SURNAME" )

This moves the focus, but any events triggered as a result of moving the focus (like LOSTFOCUS and GOTFOCUS events) will be processed.

That all sounds straightforward enough, but using the first method can lead to unexpected results if you are relying on an event already in the queue that you subsequently need – not a common situation but one we encountered recently while converting an old form to v10.  In our case we had a menu failing to show when the focus was on a specific control, and it turned out that the LOSTFOCUS event for the control was setting it’s FOCUS property to TRUE$.  This had the effect of killing a pending MENUDROPDOWN event (new in v10) that created the menu to display, hence no menu.

In this case the solution is to use the SYSTEM BLOCKEVENTS property to turn off events being triggered while the focus is moved and then restore event processing afterwards:

Call Set_Property_Only( "SYSTEM", "BLOCKEVENTS", TRUE$ )
Call Set_Property_Only( "SYSTEM", "FOCUS", @Window : ".EDL_SURNAME" )
Call Set_Property_Only( "SYSTEM", "BLOCKEVENTS", FALSE$ )

This prevented any LOSTFOCUS and GOTFOCUS events from being raised, but the MENUDROPDOWN event was still in the queue.

Using the SYSTEM FOCUS and BLOCKEVENTS properties in this way is a far “safer” alternative when moving the focus because you have full control over how events are handled, and I would always advocate using this method over using a control’s own FOCUS property.

 

Tracking the SAVEWARN property

As veteran OpenInsight programmers know, the system uses a simple boolean flag (exposed as the SAVEWARN property) to determine if the contents of a data-bound form have changed.  This flag can be updated in several ways, the most common being:

  • From the LOSTFOCUS event of a control.
  • From the POSCHANGED, INSERTROW and DELETEROW events of an EDITTABLE control.
  • From setting a control’s DEFPROP property.
  • From the CLOSE event of a form when the control with focus is inspected to see if it has changed.

It is checked during the CLEAR and CLOSE events to see if it has been set and an “Unsaved Changes” warning issued to the user if so.  Most of the time this system works quite well, but (as anyone who has spent several years working with OI systems knows) sometimes it gets triggered when you least expect it, and you’re left with no clue as to why.

To help with this situation the next version of OpenInsight introduces SAVEWARN tracking, so you can see which parts of the system update the SAVEWARN property and when they actually do it. In previous versions the system updated the SAVEWARN flag directly (it’s a simple variable in the “window common area”) but this has been changed to use the Set_Property function so it can be monitored effectively from a single point.

To track SAVEWARN you have two choices:

  • Use the SYSMSG event
  • Use the System Monitor

 

Using the SYSMSG event to track SAVEWARN

Every time SAVEWARN is set a standard SYSMSG event is raised with a SAVEWARNINFO code; the system itself does nothing with this message, but it’s there for you to use if you wish.  This option is probably more suited for run-time tracing as it’s something you could add to your applications easily if you needed to.

The PS_EQUATES insert record defines the SAVEWARNINFO message number that you can intercept:

equ SYSMSG_SAVEWARNINFO$ to 21  ; // Save warn has been changed - null msg

The Auxiliary parameter passed to the  SYSMSG event contains information that describes why the SAVEWARN property was changed.

 

Using the System Monitor to track SAVEWARN

The SetDebugger() function has been updated to support a new method called “SAVEWARN” that enables SAVEWARN tracking so that changes are displayed in the System Monitor.  This option is probably more suited to development use rather than run-time.

From the System Monitor execute:

setdebugger savewarn 1

to turn on tracing, and:

setdebugger savewarn 0

to turn it off.

E.g:

SAVEWARN tracing in the System Monitor

SAVEWARN tracing in the System Monitor

Setting the SAVEWARN property

If you wish to set SAVEWARN yourself you may use the “index” parameter to pass a description for the change, so this can be picked up in any tracing scenario like so:

Call Set_Property_Only( @Window, "SAVEWARN", TRUE$, "From My Stuff" )

This description is then passed in the Auxiliary parameter of the  SYSMSG event as noted above.

 

Hopefully you will find this facility useful if you ever suffer from problems with SAVEWARN in the future.

EditTables – The new “CELL” events

The OpenInsight EditTable has always supported a set of cell-related common events that are fired when a user interacts with the control:

  • CHAR
  • CHANGED
  • CLICK
  • DBLCLK
  • OPTIONS

In order to process these events properly however, it is necessary to know which cell they relate to, and this can ostensibly be found by using the NOTIFYPOS property, which is set to the position of the cell that raised the event.

In theory this approach works well, but in practice it can exhibit problems:  Events in OpenInsight are nearly always raised in an asynchronous fashion, which means that if two of those events where executed in quick succession for different cells, then NOTIFYPOS could be set to the position of the second cell, before the Basic+ event handler could process the event for the first cell, thereby leading to incorrect results.

In order to handle this better the EditTable now supports a series of corresponding “CELL” events:

  • CELLCHAR
  • CELLCHANGED
  • CELLCLICK
  • CELLDBLCLK
  • CELLOPTIONS

The only difference here is that these events pass the indexes of the cell that raised them as arguments to the event handler, thereby preserving their origin accurately.

E.g. The signature for the old CHANGED event looks like this:

Function Changed( CtrlEntID, CtrlClassID, NewData )

Whilst the signature for the CELLCHANGED event looks like this:

Function CellChanged( CtrlEntID, CtrlClassID, ColNum, RowNum, NewData )

Note that when a “CELL” event is defined the EditTable will non longer raise the ordinary event to prevent the notification from being processed twice.

(One useful example of the benefits of having the “CELL” events is that you can now use the new CELLCHANGED event for cell validation, rather than the usual POSCHANGED event, due to the fact that you know precisely where the change originated from. You also know that there actually was a change, rather than having to compare the cell’s current contents to it’s GOTFOCUSVALUE to discover this).

 

 

 

EditTables – Deleting and Inserting Rows

Support for deleting and inserting rows in the EditTable control has always been somewhat basic, exposing only minimum functionality that allows you to control how a user inserts or deletes a row in the grid.  We’ve enhanced this for version 10 by providing some fine-grain control over the row insertion and deletion process that we’ll describe below.

Imposing limits

Firstly, a couple of new properties have been implemented that allow you to set limits on the number of rows a user can add or remove from the EditTable via the keyboard: These are the MAXROWLIMIT and MINROWLIMIT properties.

MAXROWLIMIT property

When MAXROWLIMIT is set the user cannot use the Insert key to insert more rows than the number specified by this property.  The default value is “0”, which means there is no maximum limit.   Note that this property does not apply to programmatic INSERT method operations or data set by the LIST or ARRAY properties.

MINROWLIMIT property

When MINROWLIMIT is set the user cannot use the Delete key to delete EditTable rows once the minimum limit has been reached. The default value is “0”, which means there is no minimum row limit.   Note that this property does not apply to programmatic DELETE method operations or data set by the LIST or ARRAY properties.

Blocking the “Insert” and “Delete” keys

Previous versions of the EditTable allowed you to set a “Protected” property at design time that stopped all row insert and delete operations by the user.  Unfortunately, this was not actually exposed run-time (unless you adjusted a bit-flag the control’s STYLE property), so in version 10 we’ve expanded the old “Protected” property into two new properties called ALLOWROWDELETE and ALLOWROWINSERT.  These can used at both run-time and design-time.

ALLOWROWDELETE property

When set to False the user cannot use the Delete key to delete rows within an edit table.  The default value of this property is True.

ALLOWROWINSERT property

When set to False the user cannot use the Insert key to insert new rows within an edit table.  The default value of this property is True.

Managing the “Insert” and “Delete” keys

Finally, we made some changes to the way the actual inserts and deletes take place to give you an opportunity to intercept them for even more control.  In previous versions of OpenInsight the EditTable notified you of an insert or delete operation after it had taken place, via the INSERTROW and DELETEROW events respectively.  This means that if you wanted to ‘prevent’ the modification based on some run-time criteria you had to effectively undo it, which usually included some sort of unpleasant visual effect as the insert or delete was rolled back (it also resulted in a loss of formatting information or other cell-specific data that you would have to reapply).

In version 10 we’ve added a new Boolean property called ROWEVENTMODE. When set to False (the default) row insertion and deletion via the keyboard behaves in exactly the same way as previous versions of OpenInsight.  When set to True however, the INSERTROW or DELETEROW event happens before the actual operation takes place, and at this point you can do the following:

  1. If you’re using an EventScript you can return FALSE$ and the operation will be cancelled.
  2. If you’re using a code called from a QuickEvent you can set the EventStatus flag in your code to prevent the operation from proceeding further, just as you would currently do in a WINDOW CLOSE event to cancel it.

 

E.g. Stopping a row being deleted in a DELETEROW EventScript handler.

// Assumes "RowEventMode" is "True" 
//
// Check to see if there is anything in column 2 of the deleted data,
// and if so stop the delete via the event return value. 

$insert logical
if bLen( rowData<2> ) then
   // Stop the delete
   retVal = FALSE$
end else
   retVal = TRUE$
end

return retVal

 

E.g. Stopping a row being deleted in a DELETEROW QuickEvent handler

// Assumes "RowEventMode" is "True" 
//
// Check to see if there is anything in column 2 of the deleted data,
// and if so stop the delete via the Event Status 

$insert logical 
if bLen( rowData<2> ) then
   // Stop the delete
   call set_EventStatus( TRUE$ )
end

return

Of course, once you’ve allowed the Insert or Delete operation to take place you may also want to do some post-processing, and for this we’ve provided two new events: INSERTEDROW and DELETEDROW.  These have the same signature as their INSERTROW and DELETEROW counterparts and are executed after the row modification has taken place (Note that these are only fired if the ROWEVENTMODE is True).

It is also worth noting that, as with previous versions of OpenInsight, using the DELETE and INSERT methods to programatically modify the EditTable contents will not trigger the INSERTROW or DELETEROW events (or any subsequent INSERTEDROW and DELETEDROW events either).

The EDITSTATECHANGED event

One of the requirements we needed when developing the new IDE was the ability to detect when the state of a control changed in such a fashion that might affect the operations that could be performed on it.

A classic example of this is highlighting text in an edit control so that it can be cut or copied, or perhaps replaced with a paste operation: At this point an item like a Cut or a Paste button might need enabling so the UI is in sync with the state of the control.

To enable this functionality several controls now support a new event called EDITSTATECHANGED, which is fired when the “edit state” is changed.  The edit state is defined as one of the following operations:

  • Undo
  • Redo
  • Cut
  • Copy
  • Paste
  • Select All

So, if a user takes an action in the control that enables or disables one of these options you can respond to it via the EDITSTATECHANGED event.

The EDITSTATECHANGED event passes a single parameter called “EditState“, which is a dynamic array of Boolean flags with the following structure:

<1> CanUndo      : TRUE$ if the control allows an UNDO operation
<2> CanRedo      : TRUE$ if the control allows a REDO operation
<3> CanCut       : TRUE$ if the control allows a CUT operation
<4> CanCopy      : TRUE$ if the control allows a COPY operation
<5> CanPaste     : TRUE$ if the control allows a PASTE operation
<6> CanSelectAll : TRUE$ if the control allows a SELECTALL 
                 : operation

(You may notice that these flags closely follow the items in a standard “Edit” menu).

Here’s a simple example to set the state of some Cut/Copy/Paste buttons:

   objxArray =        @window : ".BTN_CUT"
   propArray =        "ENABLED"
   dataArray =        editState<0,3>

   objxArray := @rm : @window : ".BTN_COPY"
   propArray := @rm : "ENABLED"
   dataArray := @rm : editState<0,4>
   
   objxArray := @rm : @window : ".BTN_PASTE"
   propArray := @rm : "ENABLED"
   dataArray := @rm : editState<0,5>

   call Set_Property_Only( objxArray, propArray, dataArray )

The following controls support the EDITSTATECHANGED event:

  • COMBOBOX
  • EDITLINE
  • EDITBOX
  • EDITTABLE
  • LISTBOX
  • PROPERTYGRID

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

(EDIT: 8th May 2018 – Variable name changed from NewEditState to EditState to match released version)

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

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

Sorting in EditTables

In previous versions of OpenInsight, EditTable sorting has been implemented by the SORTEDCOL property, which simply performs a Quicksort on a single column and then only on the visible text contained in each cell.  This latter trait is particularly sub-optimal because it pays no attention to the actual type of data represented in the cell itself; for example, if you wish to sort on a column containing dates, you usually have to write code to convert the data to a numeric format first, and then pass the results onto the V119 sort function, all of which gets tedious very quickly.

In OpenInsight 10 the SORTEDCOL property has been deprecated and has been replaced by the new SORT method detailed below:

The SORT method

This method allows you to perform a multi-column sort on an EditTable, along with the ability to convert the data to an appropriate format before the sort takes place.

Call Exec_Method( CtrlEntID, "SORT", SortCriteria, SortOptions )

The method takes two parameters.  The first, SortCriteria, which is a dynamic array structured as follows:

<0,1> @svm'd list of column numbers to sort by
<0,2> @svm'd list of sorting directions/justifications for each column 
      passed in field <0,1>.  Available values are:

         0 - Descending Left
         1 - Ascending Left
         2 - Descending Right
         3 - Ascending Right

<0,3> @svm'd list of ICONV patterns used to convert the column data to its
      internal format before the sort takes place.

The default ICONV pattern used for sorting a column is taken from it’s VALID property.  This means that you can flag a column as a date (e.g. “DE”) in the Form Designer, and have it sort properly in a numeric fashion without any extra coding needed.

The second parameter, SortOptions, is a dynamic array structured as follows:

<1> If TRUE$ then perform a trim operation before the sort takes place, or
    FALSE$ to prevent the trim. If this field is null then the SORTTRIM 
    property is used to decide if a trim operation takes place.

(A trim operation is the removal of “blank” rows from the EditTable control.  A description of trim functionality will appear in a future post).

Using the SORT method triggers a new event called SORTED:

The SORTED event

This event takes the same parameters as passed to the SORT method described above.  This event is fired before any sorting takes place, thereby giving you the chance to modify the criteria or options, or even prevent it by using the Set_EventStatus() function.  All event script and QuickEvent handlers are processed before sorting.

The COLHEADERSORTINGMODE property

This is another new property for EditTable controls and can be set to one of the following values:

  • 0 (Disabled – this is the default value)
  • 1 (Sort on single-click)
  • 2 (Sort double-click)

When set to to 1 or 2, clicking or double-clicking on a column header will automatically sort the contents of the control by that column, in a similar manner to Popup entity sorting.  The SORTED event is still raised in the manner described above however, so you may still intercept and modify the process if you wish.

The SORTTRIM property

When set to TRUE a sort operation automatically performs a trim operation before sorting.  This property can be overridden by passing a flag in the SORT method SortOptions parameter described above.

We hope these improvements make sorting in EditTables a little less onerous.

 

[EDIT: 27 Sep 15, Updated for SortOptions argument and SORTTRIM property]

[EDIT: 17 Nov 15, Updated for COLHEADERSORTINGMODE property]

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

 

 

 

 

 

The SCALED event

As covered in our recent posts on scaling and High-DPI, OpenInsight now has the capability to dynamically alter the scale of a form at runtime, taking care of layout, fonts and images.  However, there may be circumstances where this is not sufficient – perhaps you need to tweak the layout yourself, or perhaps you need to display a specific image rather than rely on a DPI Image List.  In this case you will need to know when the scaling operation has taken place, and you can handle this in the new SCALED event:

SCALED event

This WINDOW event is triggered when the SCALEFACTOR property is changed or when the form is moved to another monitor with a different DPI.

bForward = SCALED( ctrlEntID, ctrlClassID, origDpiX, origDpiY, origScaleFactor, |
                                           newDpiX, newDpiY, newScaleFactor )

The event is passed the following event-specific arguments:

  1. The original X DPI value
  2. The original Y DPI value
  3. The original SCALEFACTOR value
  4. The new X DPI value
  5. The new Y DPI value
  6. The new SCALEFACTOR value

The system performs no default processing for this event.

 

Handling layout for scaled forms

Of course, this leads us to one of the main issues with handling scaling: how do you get and set layout properties like SIZE for a scaled form? What units are used?

There are basically two choices available:

  1. Use Device Independent Pixels (DIPs): With this method all coordinates are treated as though the form is scaled at 96 DPI with a scale factor of 1.  The system is then responsible for mapping them to actual pixels at runtime.
  2. Use Pixels (PX): With this method the coordinates passed are treated as actual screen pixels regardless of the DPI or scale factor.

Using DIPs may seem easiest at first, especially in terms of backwards compatibility with existing code, but it does have some drawbacks:

  • Positioning can be imprecise due to integer rounding, and you may sometimes find a case where you need complete accuracy.
  • Some properties and events cannot use DIPs at all (mainly those that relate to screen coordinates), thereby leading to the need for some type of dual coordinate system, resulting in added complexity and possible confusion.

So, to keep things simple, OpenInsight operates in Pixel mode by default, which means it keeps a single and accurate coordinate system.  Remember, scaling is an “opt-in” system, meaning that none of your existing forms will scale unless you specify otherwise (via the DPISCALING and SCALEFACTOR properties), so you can review your code before enabling it and ensure that you don’t encounter any problems.

However, even though the default coordinate system is Pixels we don’t want to remove the choice of using DIPs if you prefer, so forms now support a new SCALEUNITS property that allows properties like SIZE to operate in either DIP or Pixel mode.

SCALEUNITS property

This is a WINDOW property that defines the units used when accessing layout properties like SIZE, CLIENTSIZE, TRACKINGSIZE and so on.  Note that it also affects events like BUTTONDOWN and methods like TEXTRECT too.

It accepts the following values:

  • “0” – Scaling units are Pixels
  • “1” – Scaling units are DIPs

Example: Scale a form and examine it’s SIZE using different SCALEUNITS

* // SCALEUNITS property equates - (from PS_WINDOW_EQUATES)
 equ PS_SCU_PIXELS$ to 0
 equ PS_SCU_DIPS$   to 1

* // Assume we are currently running with Pixel units
call set_Property_Only( @window, "SIZE", 10 : @fm: 10 : @fm : 400 : @fm : 300 )

* // Now scale the window to twice its normal size ( actual XY remains constant
* // for a form when setting SCALEFACTOR - only the width and height change)
call set_Property_Only( @window, "SCALEFACTOR", 2 )

* // SIZE returns 10x10x800x600 
pxSize = get_Property( @window, "SIZE" )

* // Now set the scaling units to DIPS
call set_Property_Only( @window, "SCALEUNITS", PS_SCU_DIPS$ )

* // SIZE returns 5x5x400x300 
dipSize = get_Property( @window, "SIZE" )

* // Note that the X and Y returned in the DIPs SIZE above have also been scaled. 
* // The form hasn't moved, but the units of measurement have changed, so the 
* // location is reported relative to a _theoretical_ scaled desktop size.

At first glance it may seem that the SCALEUNITS property should be a SYSTEM property rather than a WINDOW one, but bear in mind that OpenInsight applications may inherit from one another, and executing a form designed for one set of units while running in another application with a different “global” setting would undoubtedly cause problems.  Of course there’s nothing to stop you setting the SCALEUNITS to DIPs in a promoted CREATE event for your own applications but that’s another story…

 

Scaling helper methods

There are six new WINDOW methods you can use to help with manual scaling – they convert between Pixels and DIPs based on the form’s current DPI and SCALEFACTOR (They are not affected by the SCALEUNITS property):

  • SCALEFONT
  • SCALESIZE
  • SCALEVALUE

The “SCALE” methods perform a DIPs to Pixel conversion.

  • UNSCALEFONT
  • UNSCALESIZE
  • UNSCALEVALUE

The “UNSCALE” methods perform a Pixel to DIPs conversion.

(You only really need the SCALEVALUE and UNSCALEVALUE methods, but the other four have been added to make things a little more convenient for you).

SCALEFONT method

This method takes an unscaled FONT property and scales it relative to the current scale factor of the form.

scaledFont = exec_Method( @window, "SCALEFONT", origFont )

SCALESIZE method

This method takes an unscaled SIZE property and scales it relative to the current scale factor of the form.

scaledSize = exec_Method( @window, "SCALESIZE", origSize )

SCALEVALUE method

This method takes an unscaled value and scales it relative to the current scale factor of the form.

scaledVal = exec_Method( @window, "SCALEVALUE", origVal )

UNSCALEFONT method

This method takes a scaled FONT property and unscales it relative to the current scale factor of the form.

unscaledFont = exec_Method( @window, "UNSCALEFONT", scaledFont )

UNSCALESIZE method

This method takes a scaled SIZE property and unscales it relative to the current scale factor of the form.

unscaledSize = exec_Method( @window, "UNSCALESIZE", scaledSize )

UNSCALEVALUE method

This method takes a scaled value and unscales it relative to the current scale factor of the form.

unscaledVal = exec_Method( @window, "UNSCALEVALUE", scaledVal )

Example: Moving a control using DIP coordinates on a form with Pixel SCALEUNITS

* // Example - Move a control using DIP coordinates. We get the current pixel
* //           size, unscale it so we have the value as it _would_ be at
* //           96DPI/ScaleFactor 1 (i.e. DIPs), offset it by 10 DIPs, scale
* //           it back to Pixels and and then move it.
* // Get the current scaled size (pixels) - assume we have a SCALEFACTOR of 1.5
ctrlSize = get_Property( myCtrl, "SIZE" )

* // Unscale it back to 96DPI/ScaleFactor 1.0 - i.e. to DIPs
ctrlSize = exec_Method( @window, "UNSCALESIZE", ctrlSize )

* // Adjust it to whatever we need (assume we want to offset it by 10 DIPs
* // (10 pixels at 96 DPI)
ctrlSize<1> = ctrlSize<1> + 10
ctrlSize<2> = ctrlSize<2> + 10
 
* // And ask the parent form to calculate where it _should_ be using the 
* // current scale factor
ctrlSize = exec_Method( @window, "SCALESIZE", ctrlSize )
 
* // And move it using pixels ...
call set_Property_Only( myCtrl, "SIZE", ctrlSize )

The previous example is rather contrived and is really only there to highlight how the methods can be used.  Another way of doing this would be to switch to DIPs using the SCALEUNITS property like so:

* // SCALEUNITS property equates - (from PS_WINDOW_EQUATES)
equ PS_SCU_PIXELS$ to 0
equ PS_SCU_DIPS$   to 1

* // Set the scaling units to DIPS 
scaleUnits = set_Property( @window, "SCALEUNITS", PS_SCU_DIPS$ ) 

ctrlSize = get_Property( myCtrl, "SIZE" )

* // Offset the control by 10 DIPs
ctrlSize<1> = ctrlSize<1> + 10 
ctrlSize<2> = ctrlSize<2> + 10

call set_Property_Only( myCtrl, "SIZE", ctrlSize )

* // And restore the SCALEUNITS
call set_Property_Only( @window, "SCALEUNITS", scaleUnits )

The AUTOSCALE property

By default OpenInsight maintains automatic scaling for all controls on a form, even after you’ve manually set a scaled property yourself.  However, you can opt out of this behaviour by using the boolean AUTOSCALE property:

  • When set to TRUE (the default value) it enables scaling for a control.
  • When set to FALSE no automatic scaling is performed.

This property applies to all controls (but not to WINDOW objects for obvious reasons).

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