Tag Archives: Methods

EditTables – The Sub-Object Interface

One of the changes we wanted to make with EditTables in v10 was to expose all of their runtime functionality easily through the normal property/method API.  Some of this was already available in previous versions, but usually involved a method to set a plethora of style bits, which is not really satisfactory unless you’re a C++ programmer (as we have been reminded by various developers on several occasions!).

Besides the EditTable as a whole, there are essentially three other main programmable areas:

  1. Columns
  2. Rows
  3. Cells

Providing unique properties and methods to address the capabilities of these areas is not really practicable, as it leads to an “explosion” of property names: For example, accessing the “text” associated with each of these would require three new properties such as:

  • COLUMNTEXT
  • ROWTEXT
  • CELLTEXT

Of course that’s only three, but considering that each of the aforementioned areas has something like 30 properties, you would suddenly add 90+ new property names to the product!  Bear in mind also that columns, rows and cells each have many properties like TEXT in common, so a new name for each of these seems extremely wasteful and unnecessary.

(We could also have used something similar like the existing TEXTBYPOS method to achieve this, but then we’d have 30’ish new “BYPOS” methods instead, and methods aren’t properties anyway).

So, to keep the property namespace under control we decided to use this commonality and implement a set of  “sub-objects” instead, one for each area.  Unsurprisingly these are named:

  • COLUMNS
  • ROWS
  • CELLS  

Each of these sub-objects are indexed and can be used to access a specific column, row or cell in the EditTable.  They all share many common properties and methods, but also expose a few type-specific ones as well.   As an example, this is how to set the CUEBANNER property for each sub-object:

ctrlEntID  = @window : ".EDT_TEST"
cueBanner  = "Test"

// Set the CUEBANNER for the second column
call set_Property( ctrlEntID : ".COLUMNS", "CUEBANNER", cueBanner, 2 )
// Set the CUEBANNER for the third row
call set_Property( ctrlEntID : ".ROWS", "CUEBANNER", cueBanner, 3 )

// Set the CUEBANNER for the cell at column 5, row 7
call set_Property( ctrlEntID : ".CELLS", "CUEBANNER", cueBanner, 5 : @fm : 7 )

// And here's the same using Object Notation Syntax
@ctrlEntID.columns{2}->cueBannner = cueBanner
@ctrlEntID.rows{3}->cueBannner    = cueBanner
@ctrlEntID.cells{5,7}->cueBannner = cueBanner

We’ll take a look at each of these sub-objects in turn over the next few posts to examine their functionality in more detail.

 

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

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

 

 

Object Notation Syntax

The OpenInsight event compiler supports an enhanced “shorthand” syntax for working with the Presentation Server object model, much like that provided in standard Basic+ for use with OLE objects.  Like the OLE notation, this provides a more natural API to working with properties and methods, rather than the relatively verbose and flat interface provided by the familiar Get/Set_Property and Exec_Method functions.

In a nutshell, object notation provides the use of a special “->” operator to allow an object to refer to its properties and methods, along with the “{}” operators to specify object or property indexes. It may be used in place of the following function calls:

  • Get_Property
  • Set_Property_Only
  • Exec_Method

Using Properties

The general format for accessing properties via object notation is illustrated below. In all cases objects that support sub-objects (such as controls that support an IMAGE sub-object) may reference the sub-object by suffixing them to the main object with a “.” character as a delimiter.

Get_Property syntax

  value = object->property                        ; // Non-Indexed
  value = object{index}->property                 ; // Object-Indexed
  value = object->property{index}                 ; // Property-Indexed

  // With sub-object support
  value = object.subObject->property              ; // Non-Indexed
  value = object.subObject{index}->property       ; // Object-Indexed
  value = object.subObject->property{index}       ; // Property-Indexed

Set_Property_Only syntax

  object->property = value                        ; // Non-Indexed
  object{index}->property = value                 ; // Object-Indexed
  object->property{index} = value                 ; // Property-Indexed

  // With sub-object support
  object.subObject->property = value              ; // Non-Indexed
  object.subObject{index}->property = value       ; // Object-Indexed
  object.subObject->property{index} = value       ; // Property-Indexed

Where:

  • object is either:
    1. An equated constant (suffixed with a “$” symbol), or
    2. The contents of a variable (prefixed with an “@” symbol), or
    3. An embedded name (prefixed with the “$” symbol), or
    4. A path prefix (prefixed with the “.” symbol to represent the name of the object’s parent window (i.e. “@Window”)
  • property can be an equated constant, the contents of a variable, or an embedded name. It may also be the special token “@@” which means use the DEFPROP property.
  • index is either a one or two dimensional index value, delimited by a “,” character and surrounded by curly braces.

Get_Property examples

 // Get_Property object notation using variable contents
 CtrlID = @Window : ".MY_LISTBOX"
 
 // PropVal = Get_Property( CtrlID, "TEXT" )
 PropVal = @CtrlID->Text
 
 // PropVal = Get_Property( CtrlID, "LIST", 4 )
 PropVal = @CtrlID->List{4}
 
 // PropVal = Get_Property( CtrlID, "LIST", ItemIdx )
 ItemIdx = Get_Some_Index()
 PropVal = @CtrlID->List{ItemIdx}
 
 // PropVal = Get_Property( CtrlID, "DEFPROP" )
 PropVal = @CtrlID->@@
 
 // PropVal = Get_Property( @Window, "TEXT" )
 PropVal = @@Window->Text
 
 EdtID   = @Window : ".MY_EDITTABLE"; Col = 2; Row = 3
 
 // PropVal = Get_Property( EdtID : ".CELLS", "TEXT", Col : @fm : Row )
 PropVal = @EdtID.Cells{Col,Row}->Text
 // Get_Property object notation using a path-prefix 
 
 // PropVal = Get_Property( @Window : ".MY_LISTBOX", "TEXT" )
 PropVal = .My_ListBox->Text
 
 // PropVal = Get_Property( @Window : ".MY_LISTBOX", "LIST", 4 )
 PropVal = .My_ListBox->List{4}
 
 // PropVal = Get_Property( @Window : ".MY_EDITTABLE.CELLS", "TEXT", 2 : @fm : 3 )
 PropVal = .My_EditTable.Cells{2,3}->Text
 // Get_Property object notation using equated constants  
 Equ CTRLID$ To "MYWIN.MY_LISTBOX"
 
 // PropVal = Get_Property( CTRLID$, "TEXT" )
 PropVal = CTRLID$->Text
 
 // PropVal = Get_Property( CTRLID$, "LIST", 4 ) 
 PropVal = CTRLID$->List{4}
 
 Equ EDTID$ To "MYWIN.MY_EDITTABLE"
 
 // PropVal = Get_Property( EDTID$ : ".CELLS", "TEXT", 2 : @fm : 3 )
 PropVal = EDTID$.Cells{2,3}->Text
 // Get_Property object notation using an embedded name
 
 // FocusID = Get_Property( "SYSTEM", "FOCUS" )
 FocusID = $System->Focus 

 // PropVal = Get_Property( "MYWIN.MY_CONTROL", "TEXT" )
 PropVal = $MyWin.My_Control->Text

Set_Property_Only examples

 // Set_Property_Only object notation using variable contents
 CtrlID = @Window : ".MY_LISTBOX"
 
 // Call Set_Property_Only( CtrlID, "TEXT", PropVal )
 @CtrlID->Text = PropVal
 
 // Call Set_Property_Only( CtrlID, "LIST", PropVal, 4 )
 @CtrlID->List{4} = PropVal
 
 // Call Set_Property_Only( CtrlID, "DEFPROP", PropVal )
 @CtrlID->@@ = PropVal
 
 // Call Set_Property_Only( @Window, "TEXT", PropVal )
 @@Window->Text = PropVal
 
 EdtID = @Window : ".MY_EDITTABLE"; Col = 2; Row = 3
 
 // Call Set_Property_Only( EdtID : ".CELLS", "TEXT", PropVal, Col : @fm : Row )
 @EdtID.Cells{Col,Row}->Text  = PropVal
 // Set_Property_Only object notation using an embedded name
 
 // Call Set_Property_Only( "SYSTEM", "FOCUS", focusID )
 $System->Focus = FocusID
 
 // Call Set_Property_Only( "MYWIN.MY_CONTROL", "TEXT", PropVal )
 $MyWin.My_Control->Text = PropVal
 // Set_Property_Only object notation using a path-prefix 
 
 // Call Set_Property_Only( @Window : ".MY_LISTBOX", "TEXT", PropVal )
 .My_ListBox->Text  = PropVal
 
 // Call Set_Property_Only( @Window : ".MY_LISTBOX", "LIST", PropVal, 4 )
 .My_ListBox->List{4} = PropVal
 
 // Call Set_Property_Only( @Window : ".MY_EDITTABLE.CELLS", "TEXT", PropVal, |
 //                         2 : @fm : 3 )
 .My_EditTable.Cells{2,3}->Text = PropVal
 // Set_Property_Only object notation using equated constants
 Equ CTRLID$ To "MYWIN.MY_LISTBOX"
 
 // Call Set_Property_Only( CTRLID$, "TEXT", PropVal )
 CTRLID$->Text  = PropVal
 
 // Call Set_Property_Only( CTRLID$, "LIST", PropVal, 4 ) 
 CTRLID$->List{4} = PropVal
 
 Equ EDTID$ To "MYWIN.MY_EDITTABLE"
 
 // Set_Property_Only( EDTID$ : ".CELLS", "TEXT", PropVal, 2 : @fm : 3 )
 EDTID$.Cells{2,3}->Text = PropVal

Using Methods

The general format of the Exec_Method object notation is described below.  It may be used to execute the method as a subroutine (i.e. no return value) or as a function.

Exec_Method syntax

  object->method( arg1, arg2, … argN )            ; // Call as subroutine
  result = object->method( arg1, arg2, … argN )   ; // Call as function

Where:

  • object is either:
    1. An equated constant (suffixed with a “$” symbol), or
    2. The contents of a variable (prefixed with an “@” symbol), or
    3. An embedded name (prefixed with the “$” symbol), or
    4. A path prefix (prefixed with the “.” symbol to represent the name of the object’s parent window (i.e. “@Window”)
  • method can be an equated constant, the contents of a variable, or an embedded name.

Exec_Method examples

 // Exec_Method object notation using variable contents
 CtrlID = @Window : ".MY_LISTBOX"
 
 // Pos = Exec_Method( CtrlID, "INSERT", -1, Item )
 Pos = @CtrlID->Insert( -1, Item )
 
 // Call Exec_Method( CtrlID, "DELETE", 4 )
 @CtrlID->Delete( 4 )
 // Exec_Method object notation using a path-prefix 
 
 // Pos = Exec_Method( @Window : ".MY_LISTBOX", "INSERT", -1, Item )
 Pos = .My_ListBox->Insert( -1, Item )
 
 // Call Exec_Method( @Window : ".MY_LISTBOX", "DELETE", 4 )
 .My_ListBox->Delete( 4 )

 // Call Exec_Method( @Window : ".MY_EDITTABLE", "APPEND", RowData )
 .My_EditTable.Rows->Append( RowData )
 // Exec_Method object notation using equated constants
 Equ CTRLID$ To "MYWIN.MY_LISTBOX"
 
 // Pos = Exec_Method( CTRLID$, "INSERT", -1, Item )
 Pos = CTRLID$->Insert( -1, Item )
 
 // Call Exec_Method( CTRLID$, "DELETE", 4 ) 
 CTRLID$->Delete( 4 )
 // Exec_Method object notation using an embedded name
 
 // RetVal = ( "SYSTEM", "CREATE", createStruct )
 RetVal = $System->Create( createStruct )
 
 // Call Exec_Method( "SYSTEM", "DESTROY", ctrlID )
 $System->Destroy( ctrlID )

Using Object Notation in Stored Procedures

Object Notation was originally designed for use with the event compiler, and therefore prior to version 10 could only be used with event scripts.  In the current version however, it may be used in Stored Procedures by including the event pre-compiler in the compilation chain.  This is done by adding the following at the top of the program before the other statements:

  #Pragma PreComp Event_PreComp

You should also declare the following functions before you use any object notation – the pre-compiler does not insert these into the program itself:

  • Get_Property
  • Exec_Method

E.g.

 Compile Function MyWin_Events( CtrlEntID, Event, Param1, Param2 )
 
   #Pragma PreComp Event_PreComp
   
   Declare Function Get_Property, Exec_Method
   $Insert Logical
   
   Locate Event In "CREATE,CLICK,CLOSE" Using "," Setting Pos Then
      On Pos GoSub OnCreate,OnClick,OnClose
   End
   
 Return RetVal 

   // ... etc ... 

Unlike in previous versions this object notation may also be used safely with OLE object notation in the same Stored Procedure.

Object Notation limitations

The current version of Object Notation is currently handled by a pre-compiler, rather than the actual Basic+ compiler itself, thus its parsing accuracy is somewhat limited in comparison.  Because of this, the following guidelines should be adhered to:

  1. The passing of complex expressions to the object notation Set_Property_Only and Exec_Method statements should be avoided; It is better to resolve them to a variable first, and then pass that variable as an argument instead.
  2. The curly-brace Calculate operators (“{” and “}”) are also used to resolve the value of a dictionary column at runtime, and should not be used on the same line as an object notation statement: These operators are interpreted as object or property index tokens instead, and will lead to parsing errors if used incorrectly.

Object Notation troubleshooting

Behind the scenes the pre-compiler converts the object notation syntax to actual Get_Property, Set_Property_Only and Exec_Method calls before passing them to the Basic+ compiler.  If you use object notation and run into problems that you cannot resolve easily you can see exactly what gets passed to the compiler by using the Output compiler directive, which will write the pre-compiler output to a specified record.

To enable this functionality, place the following statement at the top of your program (before or after the Event_PreComp statement), and replace <table> and <record> with the table and record names of your choice:

   #Pragma Output <table> <record>

E.g. Send the output to the PRECMP_OUT record in SYSLISTS

   #Pragma Output SYSLISTS PRECMP_OUT

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

The CHOOSEDIR and CHOOSEFILE methods

In light of the Utility function being deprecated (as mentioned in this post) I was recently asked where the CHOOSEFILE and CHOOSEDIR methods had ended up.  There was some discussion about this as the aforementioned article cited that they were moved to the SYSTEM object, and a case was made for moving them to the FILESYSTEM object instead.

In the end it was decided to move them to the FILESYSTEM object (as feedback indicated that was where they were expected to be), but to expose them from the SYSTEM object as well, meaning that we wouldn’t have to alter any existing code.

For future reference here are the official details of both methods:

FILESYSTEM CHOOSEDIR method

This method displays the common “Choose Folder” dialog box to allow a user to select a folder.  It takes two arguments:

  1. The name of an owner window
  2. A @fm-delimited array of initialization data
<1> Dialog text
<2> Initial folder to select
<3> HideNewFolder flag: If TRUE$ then hide the "New Folder" button
<4> ShowFiles flag: If TRUE$ then show files in the dialog as well as folders

The CHOOSEDIR method is basically a wrapper around the Windows SHBrowseForFolder function.

Example:

dlgOptions    = ""
dlgOptions<1> = "Please select the destination RDK folder"
dlgOptions<2> = "c:\RevSoft\RDK"
dlgOptions<3> = FALSE$ ; * // allow new folders
dlgOptions<4> = FALSE$ ; * // don't show files

folderName = Exec_Method( "FILESYSTEM", "CHOOSEDIR", @window, dlgOptions )

// This works too...
folderName = Exec_Method( "SYSTEM", "CHOOSEDIR", @window, dlgOptions )

 

FILESYSTEM CHOOSEDIR method

This method displays the common “Choose File” dialog box to allow a user to open a folder.  It takes two arguments:

  1. The name of an owner window
  2. A @fm-delimited array of initialization data
<1> Mode: If FALSE$ then show an "Open File" dialog, if TRUE$ then show 
    a "Save As" dialog instead.
<2> Filters: contains an "/" delimited list of filter items in the format:
    
       <displayText> "/" <filter> "/"

    Where <displayText> is the string to show in the filter dropdown list
    and <filter> is the string to apply to the selected folder.

<3> Filter Index: Specifies the index of the filters in <2> to be displayed.
<4> Default file name
<5> OFN (OpenFileName) flags. This is a bitmask number specifying the flags 
    to apply to the dialog. They are defined in the following insert record:
    
        MsWin_GetOpenFileName_Equates

<6> Initial folder to select
<7> Default extension: This is appended to the file name if the user fails 
    to type an extension. This string can be any length, but only the first 
    three characters are appended. The string should not contain a 
    period (.). 
<8> Dialog title

The CHOOSEFILE method is a basically wrapper around the Windows GetOpenFileName function.

Example:

$insert msWin_GetOpenFileName_Equates

* // Create a filter string for common image files
filters = "Bitmap Files (*.bmp;*.rle;*.dib)/*.bmp;*.rle;*.dib/" |
        : "GIF Files (*.gif)/*.gif/"                            |
        : "JPeg Files (*.jpg,*jpeg,*.jpe)/*.jpg;*jpeg;*.jpe/"   |
        : "PNG Files (*.png)/*.png/"                            |  
        : "All Files (*.*)/*.*/"

* // Create a bitmask of flags
ofnFlags   = OFN_READONLY$
ofnFlags   = bitOr( ofnFlags, OFN_HIDEREADONLY$ )
ofnFlags   = bitor( ofnFlags, OFN_FILEMUSTEXIST$ )

dlgOptions    = ""
dlgOptions<1> = FALSE$  ;* // "Open"
dlgOptions<2> = filters
dlgOptions<3> = 4       ; * // Pre-select the PNG filter
dlgOptions<4> = "example.png"
dlgOptions<5> = ofnFlags
dlgOptions<6> = ".\images"
dlgoptions<7> = ""
dlgOptions<8> = "Please select an Image"

imageName = Exec_Method( "FILESYSTEM", "CHOOSEFILE", @window, dlgOptions )

* // This works too ...
imageName = Exec_Method( "SYSTEM", "CHOOSEFILE", @window, dlgOptions )

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