Tag Archives: Methods

The Saga of ShellExecute

One of the most popular “raw” Windows API functions that OpenInsight developers have used over the years is the ShellExecute function, which allows you to launch an application via its filename association, e.g. you can launch Word by using a document file name, or Excel using a spreadsheet filename and so on.

However, because it was never really made an “official” part of the product (it was normally passed on in forums), developers were left to create their own DLL Prototype definitions in order to use it – this gave rise to many variations over the years, many of which were not compatible with others. For example, some use LPCHAR as an argument type, some use LPSTR or LPASTR, whilst others use LPVOID with GetPointer(); some definitions use the “Wide” version of the function, some the “Ansi” version, and there are many different aliases, with or without the “A/W” suffix too. The list goes on.

For OpenInsight 10 we decided that we couldn’t move forward with this as we would run the risk of conflicting with established applications, so we moved all of the DLL Prototypes we used into a new namespace called “MSWIN_” and claimed it as our own. This left developers to bring forward their own DLL prototypes into version 10 as and when needed, and therefore we didn’t supply a “ShellExecute” function as such, though we did supply “MsWin_ShellExecute” instead (see below).

Another decision we took was to try and move away from the need for developers to use raw Windows API function calls as much as possible, as some of them can be complex and require knowledge of C/C++ programming, which is not necessarily a skill set that everyone has the time or desire to learn. Ergo, we moved a lot of functionality into the Presentation Server (PS) and created some Basic+ wrapper functions around others to shield developers from the sometimes gory internals.

(We also chose to use the “W” versions of functions rather than the “A” versions where possible, because these would translate better when in UTF8 mode and remove the need for an extra “A”->”W” conversion in Windows itself.)

So, coming back to ShellExecute, and in light of the above, we have three “official” and supported ways of calling it in OpenInsight 10 as detailed below:

  • The SYSTEM object SHELLEXEC method
  • The RTI_ShellExecuteEx stored procedure
  • The MSWin_ShellExecute DLL Prototype stored procedure

The SYSTEM object SHELLEXEC method

If your program is running in “Event Context”, (i.e. it is executing in response to an event originating from the PS) then you may use the SYSTEM SHELLEXEC method which invokes ShellExecuteW internally.

RetVal = Exec_Method( "SYSTEM", "SHELLEXEC", OwnerForm, Operation, File, |
                      Parameters, WorkingDir, ShowCmd )
ParameterRequiredDescription
OwnerFormNoName of a form to use as a parent for displaying UI messages.
OperationNoOperation to be performed; “open”, “edit”, “print” etc.
FileYesFile to perform the operation on.
ParametersNoIf File is an executable file this argument should specify the command line parameters to pass to it.
WorkingDirNoThe default working directory for the operation. If null the current working directory is used.
ShowCmdNoDetermines how an application is displayed when it is opened (as per the normal VISIBLE property).

The return value is the value returned by ShellExecuteW.

The RTI_ShellExecuteEx method

This stored procedure is a wrapper around the Windows API ShellExecuteExW function (which is used internally by ShellExecuteW itself), and may be used outside of event context – it can also return the handle to any new process it starts as a result of executing the document. As you can see it’s quite similar to the SHELLEXEC method:

RetVal = RTI_ShellExecuteEx( Hwnd, Verb, File, Parameters, |
                             Directory, nShow, hProcess )
ParameterRequiredDescription
HwndYesHandle of a window to use as a parent for displaying UI messages, or null (0) to use the desktop.
VerbNoOperation to be performed; “open”, “edit”, “print” etc.
FileYesFile to perform the operation on.
ParametersNoIf File is an executable file this argument should specify the command line parameters to pass to it.
DirectoryNoThe default working directory for the operation. If null the current working directory is used.
nShowNoDetermines how an application is displayed when it is opened (as per the normal VISIBLE property).
hProcessNoReturns the handle to the new process.

The return value is the value returned by ShellExecuteExW.

The MSWin_ShellExecute DLL Prototype stored procedure

This is the “raw” DLL function that is included with OI10, and the definition can be found in the MSWIN_SHELL32 DLLPROTOTYPE entity:

Shows the MSWIN_SHELL32 DLLPROTOTYPE entity

Because we’re using LPWSTR data types there is no need to null-terminate any of your variables so using it is quite simple:

RetVal = MsWin_ShellExecute( 0, "open", "stuff.docx", "", "c:\docs", 1 )

Migrating ShellExecute

Whilst you are free to use one of the methods outlined above, this may not be optimal if you are still sharing code between your existing version 9 application and your new version 10 one. In this case there are a couple of options you could use:

  • Define your preferred DLL prototype in v10.
  • Use a wrapper procedure and conditional compilation.

Defining your own prototype

This is probably the easiest option – you simply use the same prototype in v10 that you did in version 9, with the same alias (if any), and this way the code that uses it doesn’t need to be changed. The only downside to this if you’ve used any 32-bit specific data types instead of 32/64-bit safe types like HANDLE (this could happen if you have a really old prototype) – you must ensure that you use types that are 64-bit compliant.

Using conditional compilation

This is a technique we used when writing the initial parts of v10 in a v9 system so our stored procedures would run the correct code depending on the platform they were executing on (it was actually first used to share code between ARev and OI many years ago!).

The v10 Basic+ compiler defines a token called “REVENG64” which is not present in the v9 compiler – this means that you can check for this in your source code with “#ifdef/#ifndef” directives and write code for the different compiler versions.

For example, you could write your own wrapper procedure for ShellExecute that looks something like this:

Compile Function My_ShellExecute( hwnd, verb, file, params, dir, nShow )

#ifdef REVENG64
   // V10 Compiler - use RTI function
   Declare Function RTI_ShellExecuteEx
   RetVal = RTI_ShellExecuteEx( hwnd, verb, file, params, dir, nShow, "" )
#endif

#ifndef REVENG64
   // V9 Compiler - use existing "raw" prototype
   Declare Function ShellExecute
   RetVal = ShellExecute( hwnd, verb, file, params, dir, nShow )
#endif

Return RetVal

And then call My_ShellExecute from your own code.

So, there ends the Saga of ShellExecute … at least for now.

Methods, Events, and Documentation

In a recent post we provided a preview of the OpenInsight IMAGE API documentation for the upcoming release of version 10.1. As that proved quite popular we thought we’d provide some more, this time dealing with the Common GUI API (i.e. the basic interface that virtually every GUI object supports) and the WINDOW object API – two core areas of OI GUI programming.

Methods, not Events

One thing you may notice as you look through these documents is the addition of many new methods, such as SHOWOPTIONS or QBFCLOSESESSION – this is an attempt to tidy up the API into a more logical and coherent format that is a better fit for an object-based interface.

As we went through the product in order to document it, it became very apparent that there were many instances where events were being used to mimic methods, such as sending a WRITE event to save the data in a form, or sending a CLICK event to simulate a button click and so on. In object-based terminology this sort of operation would be performed by a method, which is a directive that performs an action – the event is a notification in response to that action. So, for example, you would call a “write” method to save your data and the system would raise a “write” event so you could deal with it.

Of course, this distinction will probably not bother many developers – just API purists like myself, but this does have another advantage if you like to use Object Notation Syntax (I do) – you can now perform actions such as reading and writing form data by using the”->” notation, whereas before you would have to use the Send_Event stored procedure which essentially breaks the object-based paradigm.

So instead of:

   Call Send_Event( @Window, "WRITE" )

you would use the form’s WRITEROW method instead:

   @@Window->WriteRow( "" )

which is a more natural fit for this style of programming.

(It is also easier to explain to new OI programmers who are used to other object-based languages and environments where everything is properties, methods and events).

Methods, not Stored Procedures

This brings us finally onto the topic of Stored Procedures and the object API, where several of these also fulfill the role of methods. For example, take the venerable Msg stored procedure used to display a message box for a parent form – a different way of treating this would be to have a SHOWMESSAGE method for the parent form rather than using a “raw” Msg call. Likewise for starting a new form: instead of using the raw Start_Window procedure, the SYSTEM and WINDOW objects now support a STARTFORM method instead.

Of course, none of this changes your existing code, nor is it enforced, it’s just something you can use if and when you wish to. However, even if my API pedantry hasn’t persuaded you to change your coding style, some of the new methods are worth investigating as they provide a better opportunity for us to extend the product’s functionality further – take a look at the WINDOW READROW and WRITEROW methods for an example of this – they support new features that we couldn’t do with just sending events.

In any case, here are the links – hopefully some light reading for your weekend!

Screenshots with the CAPTUREIMAGE method

Bitmap controls in OpenInsight 10 have a method called CAPTUREIMAGE, which allows you to “screenshot” the contents of another OI control or form into the Bitmap control’s IMAGE sub-object. As you can see, it has a very simple interface:

SuccessFlag = Exec_Method( BitMapCtrlID, "CAPTUREIMAGE", CaptureID )

Where “CaptureID” is the fully qualified name of the control to screenshot.

E.g.

If we have a form called TEST_CAPTUREIMAGE, with a BITMAP control called BMP_SCREENSHOT, then we can screenshot the contents of the IDE into it like so:

Call Exec_Method( "TEST_CAPTUREIMAGE.BMP_SCREENSHOT", "CAPTUREIMAGE", |
                   "RTI_IDE" )
Shows a captured image of the OpenInsight IDE in a Bitmap control.

(N.B. The captured image you see displayed above is scaled – the screenshot is stored at full resolution in the control itself)

One obvious use for this is for support purposes, e.g:

  • Take a screen-shot with CAPTUREIMAGE.
  • Use The SAVETOFILE method in the IMAGE API to save it to a file.
  • Create an email message with the image file attached or embedded and send it to your support desk.

We’re sure you can think of more.

Bonus trivia:

  • CAPTUREIMAGE works with any object that supports the Windows WM_PRINTCLIENT message.
  • BITMAP controls are basically an alias for STATIC controls, so all STATIC controls support this method.

The SHOWDATABINDING method

In the next release of OpenInsight we’ve added a new feature that allows you to quickly display runtime databinding information for the controls in your application – the aptly named SHOWDATABINDING method.

It’s a simple method that is supported by all controls, and can be invoked like so:

Call Exec_Method( CtrlEntID, "SHOWDATABINDING" )

If the control is bound to a database table then it displays a view-only dialog of data binding information for that control. The following example shows the information for a bound column in an EditTable control:

Dialog box showing an example of the databinding information displayed via the SHOWDATABINDING method.

The Description, Validation, Heading and Formula attributes all have their own sub-dialog boxes to display their full details.

If the control is not databound a simple message is displayed to inform the user of the fact.

This method can easily be added to menu or contextmenu QuickEvents in your own applications if you wish to expose this information to your users, or just for your own diagnostic purposes.

The IMAGE Object API (Redux)

Once upon a time, in a blog post far far away, we looked at a very early incarnation of the version 10 Image API which described how images were used with OpenInsight forms and controls. Since then the API has changed quite a bit, so this post gives you a PDF link to a preview of the API documentation which covers the capabilities in the next release, some of which hasn’t been described before.

An overview of the Image API

Many GUI types managed by the Presentation Server support images in one form or another; some have background images, some have glyphs, some have image lists and so on. However, many of these images have properties of their own, and in most cases these image properties are common to all.

Because of this most Presentation Server types that support images expose them as intrinsic “sub-objects”, thorough a model we refer to as the “Image Object API”. The image object’s lifetime is managed by the system and it cannot be programmatically destroyed from Basic+, but it can be manipulated by the Get/Set_Property and Exec_Method functions just like any other Presentation Server object.

The entire Image API is described in the linked PDF below but be aware that not all image objects support all of the API. For example, a PUSHBUTTON type does not support an “INDEX” property for its background image, and its SplitGlyph image doesn’t support an ALIGN property: All such exceptions are documented in the relevant sections that describe each object type.

Supported image types

The Presentation Server supports the following image types via the WIC (Windows Imaging Component) sub-sytem:

  • BMP
  • ICO
  • PNG
  • GIF
  • JPEG
  • TIFF

Image Object Properties

NameDescription
ALIGNSpecifies the horizontal and vertical alignment of the image
within its parent.
AUTOSCALESpecifies if the image should be scaled along with its parent
object.
COLORKEYSpecifies the color in the image that should be treated as the
“transparent color”.
COUNTSpecifies the number of sub-images within an image file.
FILENAMEReturns the name of the image file being displayed.
FILENAMESSpecifies an array of DPI-specific image files to display.
FRAMECOUNTReturns the number of “frames” within an image.
FRAMEDELAYReturns the delay time in milliseconds for a multi-frame image.
FRAMEINDEXSpecifies the frame to display within a multi-frame image.
INDEXSpecifies the index of the sub-image to display within a multiimage file.
OFFSETSpecifies the point within the image (not the object) to begin
drawing from.
ORIGINSpecifies the point within the object (not the image) to begin
drawing from.
SIZEReturns the width and height of the image in pixels
STYLESpecifies how the image is drawn into an object (Tiled,
Stretched, Clipped or Scaled).
TRANSLUCENCYSpecifies the degree of transparency applied to an image when
it is drawn

Image Object Methods

NameDescription
SAVETOFILESaves the current image to a file.
SETHBITMAPLoads an image from a Windows BITMAP handle (HBITMAP).
SETIMAGELoads an image from an array of “raw” image bytes.
SETREPOSIMAGESpecifies the image file(s) using an OpenInsight repository
IMAGE entity.

Here’s the link to the Image Object API PDF – adding animated GIFs to your applications has never been so easy!

Coloring your tabs with the ITEMSTYLE property

A new facility introduced in version 10 is the ability to set the styling information for the tabs in the TabControl using the new ITEMSTYLE and ITEMSTYLES properties, and in this post we’ll explain how to use them.

TabStates and ItemStyles

Each tab in a TabControl can be in one of the following states at runtime:

  • Normal (Unselected tab)
  • Hot (Mouse is over a Normal tab)
  • Disabled
  • Selected
  • Hot Selected (Mouse is over the selected tab)

And for each one of these states you can specify the following styling information for the tabs:

  • ForeColor
  • BackColorFrom
  • BackColorTo
  • Bold
  • Italic
  • Underline
  • Translucency
  • CloseButton ForeColor
  • CloseButton BackColor

To do this at run-time you can use one of the following properties:

  • The ITEMSTYLE property
  • The ITEMSTYLES property

and we’ll take a look at each of these in turn.

 

The ITEMSTYLE property

   prevStyle = Get_Property( ctrlEntID, "ITEMSTYLE", itemState )
   currStyle = Set_Property( ctrlEntID, "ITEMSTYLE", newStyle, itemState )

This property allows you to get or set the ITEMSTYLE for a single state.  The property itself is an @fm-delimited array of styling information like so:

    <1> ForeColor             (COLORREF)
    <2> BackColor From        (COLORREF)
    <3> BackColor To          (COLORREF)
    <4> Bold                  (1/0)
    <5> Italic                (1/0)
    <6> Underline             (1/0)
    <7> Translucency          (0-100)
    <8> CloseButton ForeColor (COLORREF)
    <9> CloseButton BackColor (COLORREF)

Equates for these array positions can be found in the PS_TABCONTROL_EQUATES insert record:

   equ TCIS_POS_FORECOLOR$           to 1 ; * // COLORREF
   equ TCIS_POS_BACKCOLOR_FROM$      to 2 ; * // COLORREF
   equ TCIS_POS_BACKCOLOR_TO$        to 3 ; * // COLORREF
   equ TCIS_POS_BOLD$                to 4 ; * // Boolean
   equ TCIS_POS_ITALIC$              to 5 ; * // Boolean
   equ TCIS_POS_UNDERLINE$           to 6 ; * // Boolean
   equ TCIS_POS_TRANSLUCENCY$        to 7 ; * // UInt (0-100)
   equ TCIS_POS_CLOSEBTNFORECOLOR$   to 8 ; * // COLORREF
   equ TCIS_POS_CLOSEBTNBACKCOLOR$   to 9 ; * // COLORREF

You must also use the index parameter with the Get_Property and Set_Property to specify the tab state that you are setting, which is an integer between 1 and 5:

   equ TCIS_NORMAL$                   to 1
   equ TCIS_HOT$                      to 2
   equ TCIS_DISABLED$                 to 3
   equ TCIS_SELECTED$                 to 4
   equ TCIS_HOTSELECTED$              to 5

Example: Setting the Hot and Hot Selected styles

   $insert colors
   $insert logical

   // Set the mouseover text to change to red 
   itemStyle = ""
   itemStyle<TCIS_POS_FORECOLOR$> = RED$
   
   Call Set_Property_Only( ctrlEntID, "ITEMSTYLE", |
                           itemStyle,              |
                           TCIS_HOT$ )

   // Set the mouseover text for a selected item to change to red
   // and bold
   itemStyle = ""
   itemStyle<TCIS_POS_FORECOLOR$> = RED$
   itemStyle<TCIS_POS_BOLD$>      = TRUE$
   
   Call Set_Property_Only( ctrlEntID, "ITEMSTYLE", |
                           itemStyle,              |
                           TCIS_HOTSELECTED$ )

 

The ITEMSTYLES property

   prevStyles = Get_Property( ctrlEntID, "ITEMSTYLES" ) 
   currStyles = Set_Property( ctrlEntID, "ITEMSTYLES", newStyles )

This property is very similar to the ITEMSTYLE property except that it allows you to get or set the styles for all states at once.

The property itself is an @fm/@vm delimited array: each state is delimited by @fm, and the styling information for each state is delimited by @vm.

   <1> ItemStyle for the Normal State
   <2> ItemStyle for the Hot State
   <3> ItemStyle for the Disabled State
   <4> ItemStyle for the Selected State
   <5> ItemStyle for the Hot Selected State

(You will note these map onto the “TCIS_” state equates shown above)

For each one of these states the ItemStyle information is an @vm delimited array using the same structure as for the ITEMSTYLE property, i.e:

   <0,1> ForeColor             (COLORREF)
   <0,2> BackColor From        (COLORREF) 
   <0,3> BackColor To          (COLORREF) 
   <0,4> Bold                  (1/0) 
   <0,5> Italic                (1/0) 
   <0,6> Underline             (1/0) 
   <0,7> Translucency          (0-100) 
   <0,8> CloseButton ForeColor (COLORREF) 
   <0,9> CloseButton BackColor (COLORREF)

So you can use the “TCIS_POS_” equates shown above as well.

Example: Setting the Hot and Hot Selected styles using ITEMSTYLES

   $insert colors
   $insert logical

   itemStyles = Get_Property( ctrlEntID, "ITEMSTYLES" )

   // Set the mouseover text to change to red 
   itemStyles<TCIS_HOT$, TCIS_POS_FORECOLOR$> = RED$
   
   // Set the mouseover text for a selected item to change to red 
   // and bold
   itemStyles<TCIS_HOTSELECTED$,TCIS_POS_FORECOLOR$> = RED$
   itemStyles<TCIS_HOTSELECTED$,TCIS_POS_BOLD$>      = TRUE$
   
   Call Set_Property_Only( ctrlEntID, "ITEMSTYLES", itemStyles )

 

Setting ItemStyles in the Form Designer

Item styling for the tab control can also be specified at design time by using the “ItemStyles” property that is available when you select the tab control in the form designer.  When you click the button for this property you will see an ItemStyles editor dialog that lets you specify the styling information:

TabControl ItemStyles Editor

(Note that in the current release (10.0.6) you will not see this applied to the design control – this will be fixed in an upcoming release)

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