News and notes from the bleeding edge of Revelation Software development
Category Archives: Presentation Server
Contains posts about the OpenInsight Presentation Server, which is the module responsible for managing GUI windows and controls on the Windows Desktop.
As work continues on OpenInsight 11 we’re still working on OpenInsight 10 to ensure that we’re fixing bugs and implementing a few new features wherever we can (and of course folding them into v11 as well).
OpenInsight 10.3 is the next release and about to hit Beta status, and we’ll announce the release date in due course. Originally we planned to release a smaller 10.2.5 version with just the bug fixes, but as we added a major component update and a few other new things, we decided to bump it up a little, so here’s what you’ll be getting:
An updated OIPI “Classic” (OCX) version
As you know, we supply two main printer engines in OpenInsight: One is based on OLE (OCX) technology (aka. Get/Set_Printer1) , and the other is based on .NET technology (aka. Get/Set_Printer2). The reason for adding the latter version to OpenInsight was mainly because the OLE version didn’t support Unicode at all, and this was becoming a vital requirement for modern applications. The big downside of the .NET version, however, was the speed, and it was generally slower than the OLE version, which was painfully noticeable on some long reports and print jobs.
The good news is that with version 10.3 we’ve implemented the following in the OIPI OCX version:
Full Unicode support in the document object and PDF exporter.
Support for compression, passwords and encryption in the PDF exporter.
Improved the UI feedback in the PDF Exporter
Vastly improved the speed of the PDF exporter (we had some reports go from 12 ppm to 600 ppm!)
Added text-selection functionality in the Print Preview window to match the .NET version.
So hopefully with that you’ll get the best of both worlds – Unicode and speed!
Added the “Find” API to the WEBVIEW control
Microsoft finally added a Find API in the WEBVIEW control so we’ve now exposed that too. This means that you’ll be able to programmatically control the “Find” functionality in your WEBVIEW controls (You can even use your own Find dialog if you prefer closer integration with your normal OpenInsight GUI forms instead).
Added support for multiple-selection listboxes and drag-reordering
LISTBOX controls have always supported a simplified Drag and Drop ability via the ALLOWDRAGREORDER property, but it was always limited to single-select mode. Due to popular demand we’ve updated this to include multiple-selection mode too (And for those of you wondering, we’re hoping to get full OLE Drag and Drop finished for version 11, honest… ).
Of course there are several other bug fixes and performance improvements we’ve added as well, so be sure to check out the Beta release when it goes live!
Over the years there have been several different and disparate ways of managing directories in OpenInsight, and not all of them fully documented. In this post we’re going to take look at the “official” preferred methods, along with a mention of the deprecated ones too.
As you may be aware, an OpenInsight application runs in one of the following contexts:
Event Context – This applies when your Basic+ programs are called in response to an event from a standard OpenInsight application form or control (i.e. an application managed by the Presentation Server).
Non-Event Context – This applies to applications that run Basic+ programs outside of the Presentation Server using the RevCAPI interface to manage an instance of RevEngine. These are usually “Inet” or O4W web applications, but also include other methods like the RevRun.exe program too.
Therefore, one of the most fundamental considerations when choosing which directory management method to use is the context in which it is called: As a rule, when running in Event Context, you should always prefer to use the FILESYSTEM object for directory management, otherwise you should choose the RTI_OS_Dir stored procedure instead.
Note that if there is a possibility that your Basic+ programs will be executed in different contexts at runtime (i.e. you share them between contexts) then you should invoke the IsEventContext stored procedure to determine which method to use.
For example, here is a simple context-aware code snippet that removes a directory:
Declare Function IsEventContext, RTI_OS_Dir, Exec_Method
$Insert PS_FileSystem_Equates
$Insert RTI_SSP_Equates
ErrText = ""
If IsEventContext() Then
// Use the FILESYSTEM object
If Exec_Method( "FILESYSTEM", "REMOVEDIR", DirName ) Else
ErrInfo = Get_Property( "FILESYSTEM", "FILEOPRESULT" )
ErrCode = ErrInfo<PS_FOR_ERRORCODE$>
ErrText = ErrInfo<PS_FOR_ERRORTEXT$>
End
End Else
// Use RTI_OS_DIR
Call Set_Status( SETSTAT_OK$ )
If RTI_OS_Dir( "REMOVEDIR", DirName ) Else
Call Get_Status( ErrText )
End
End
Using the FILESYSTEM object
The FILESYSTEM object supports the following methods to manage directories. It integrates fully with the Windows Shell and provide the best user experience in Event Context:
This stored procedure was added to OpenInsight 10 to provide a non-Event Context version of the functionality exposed by the FILESYSTEM object. It supports the following methods:
COPYDIR
DIREXISTS
GETTEMPDIR
MAKEDIR
MOVEDIR
REMOVEDIR
RENAMEDIR
The RTI_OS_Dir stored procedure is fully documented here.
(Note that there is no direct equivalent of the FILESYSTEM GETSPECIALDIR method, due to the fact that it is very Windows-specific – the GET_SPECIAL_FOLDER stored procedure should be used instead.)
The Utility stored procedure (Deprecated)
As long-time readers of this blog will know, this stored procedure was deprecated some years ago at the outset of the OpenInsight 10 project, and it is now basically a thin wrapper around several SYSTEM and FILESYSTEM object methods. It supports the following directory management methods:
MAKEDIR
REMOVEDIR
RENAMEDIR
When called in Event Context each of these methods forwards the request to the FILESYSTEM object. When called outside of Event Context these methods call the following stored procedures instead:
UtilityMakeDir
UtilityRemoveDir
UtilityRename
See below for more details on these.
The RTI_OS_Directory stored procedure (Deprecated)
This stored procedure was deprecated in favor of RTI_OS_Dir as it is very platform specific and makes use internally of RTI_OS_Dir, Utility, and an OLE interface to the Windows Shell, making it less performant than calling those procedures directly. Like Utility it is still supported but will not be updated further with any new functionality.
DirExists stored procedure (Deprecated)
This stored procedure is a simple wrapper around the RTI_OS_Dir DIREXISTS method and so has been deprecated, but can still be used by existing code.
MkDir stored procedure (Deprecated)
This venerable stored procedure is a DLL function that calls the Windows API CreateDirectory function. It has been deprecated in favor of the RTI_OS_Dir MAKEDIR method but can still be used by existing code.
UtilityMakeDir, UtilityRemoveDir and UtilityRename stored procedures (Deprecated)
These three functions form part of the original internals of the Utility stored procedure, and use a “raw” C interface that requires explicit null-terminated strings. They are all still available to use, but are deprecated in favor of the RTI_OS_Dir MAKEDIR, REMOVEDIR and RENAMEDIR methods.
Conclusion
As you can see, version 10 has pulled together the many different historical methods of directory management and consolidated them into two entities that can be used dependent on the execution context. These should be the preferred methods of directory management in your OpenInsight applications.
(As of the time of writing the full documentation for RTI_OS_DIR and GET_SPECIAL_FOLDER is in preparation and will be available shortly. All methods described here apply to version 10.2.3 and later.)
A recent question on the Revelation forum touched on the subject of using checkbox items in LISTBOX and TREELISTBOX controls, and this highlighted the need to document some of the new properties in OpenInsight v10 that support this functionality. In this post we’ll take a look at how you can use these to make adding checkbox items to your controls a simple task.
In previous versions of OpenInsight adding checkboxes to items was done with “smoke and mirrors”, i.e. actual item images were used to represent the checkbox, and the control itself had no concept of a checked state for any of it’s contents. Usually, when the user clicked on the item’s checkbox image, the UPDATE method was used toggle the image from checked to unchecked and vice versa. The checked state would be obtained by looking at the image number using the LIST property. Unfortunately this technique has two main drawbacks:
The code for detecting the mouse clicks can be complex, and when added to the image manipulation code itself the intent of the code can become obscured.
The images themselves must be maintained manually and updated to match the current Windows visual styling.
In OpenInsight v10 checkbox items are supported “natively” so that the control itself knows which items are “checked” and exposes the properties described below to support this. This results in less coding and a much cleaner program.
The CHECKBOXES property
This is a simple boolean property that can be set in the Form Designer or at runtime. Setting it to TRUE$ ensures that all items in the control are drawn with a checkbox – this is all that needs to be done to use checkbox items.
The CHECKED property
This property allows you to get or set the checked state of one or more items using an @fm-delimited dynamic array of boolean flags.
// Set the second and fourth items in the LISTBOX to checked, ensure the // third item is NOT checked.
The CHECKED property may also be used with the index parameter to get or set the state of a single item at a time:
// Set the ninth item to checked, and uncheck the tenth item Call Set_Property_Only( CtrlEntID, "CHECKED", TRUE$, 9 ) Call Set_Property_Only( CtrlEntID, "CHECKED", FALSE$, 10 )
The CHECKEDX property
This property is similar to the CHECKED property but only applies to TREELISTBOX controls, and gets or sets the checked state for all items in the fully expanded list.
CHECKEDLIST property
This property returns an @fm-delimited dynamic array of item indexes that have been checked. This is an optimization property so that you don’t have to iterate over the CHECKED property to find out what has been checked.
CheckedCount = FieldCount( AllCheckedItems, @Fm ) For N = 1 To CheckedCount CheckedItemNo = AllCheckedItems<N> Next
CHECKEDLISTTEXT property
This property is similar to the CHECKEDLIST property except that it returns an @fm-delimited dynamic array of text for the checked items rather than their index.
CheckedCount = FieldCount( AllCheckedItemsText, @Fm ) For N = 1 To CheckedCount CheckedItemText = AllCheckedItemsText<N> Next
Conclusion
So that wraps up this short post on checkbox items – hopefully you’ll find them much easier to use in your v10 applications.
Bonus Trivia
The CHECKED property name is a synonym for the original OpenInsight CHECK property name, and you may use either as it suits you – Here at Revelation we prefer to use CHECKED as it feels more natural. Note that this convention applies to other controls like the CHECKBOX control too.
First released with OpenInsight v10.2, the WEBVIEW object is a control that wraps the Microsoft WebView2 Edge Browser control and allows you to embed web technologies (HTML, CSS and JavaScript) in your OpenInsight forms.
This is just a quick post to let you know that the full documentation has been uploaded and can be found here:
OpenInsight 10.2 adds a new event called CELLPOSCHANGED to the EDITTABLE control. This is effectively the same as the normal POSCHANGED event but with the addition of an extra parameter called “ContextFlags” that provides more information on why the event was raised.
ContextFlags is a simple bitmask integer that contains the following flags:
Bit Flag Value
Description
0x00000001
If set then the cell position was changed via a keystroke.
0x00000002
If set then the cell position was changed via the mouse.
Equates for these flags can be found in the PS_EDITTABLE_EQUATES insert record:
Equ PS_EDT_CTF_NONE$ To 0x00000000;
Equ PS_EDT_CTF_KEYSTROKE$ To 0x00000001;
Equ PS_EDT_CTF_MOUSECLICK$ To 0x00000002;
Example – testing to see if the position (CARETPOS) was changed via a mouse click:
$Insert PS_EditTable_Equates
If BitAnd( ContextFlags, PS_EDT_CTF_MOUSECLICK$ ) Then
// CARETPOS was changed by using the mouse.
...
End
Notes on using the CELLPOSCHANGED event
The default promoted system CELLPOSCHANGED event handler performs the same processing as the default promoted system POSCHANGED event handler (i.e. data validation and required checking etc).
If a CELLPOSCHANGED event handler is defined by the developer then a standard POSCHANGED event will not be raised.
To preserve backwards compatibility with existing applications the default promoted system CELLPOSCHANGED event will not be compiled into a control if there is no CELLPOSCHANGED quick event handler. This is to ensure that POSCHANGED is always executed if CELLPOSCHANGED has not been explicitly set for a control by the developer.
CELLPOSCHANGED is available in OpenInsight 10.2 from the Beta 3 release onwards.
One of the new features that was added to the ListBox and TreeListBox controls in version 10 was the ability to use “in-place” editing on the items in the same manner as the Windows Explorer when you press “F2” or double-click an item. If you’ve done any work with the OpenInsight Menu Designers you will have seen this capability in action.
In-place editing for an item
The READONLY property
Enabling in-place editing is as simple as setting the READONLY property to False – once you’ve done that the user can press “F2” while using the control and edit the text of the currently selected item. Pressing “Enter” when editing will update the item text (as will losing focus or selecting another item), while pressing “Esc” will abandon the changes. Obviously that’s a very simple take on the topic and hardly worth a blog post in and of itself, so next we’ll take a look at some of the properties, methods and events that you can use to tailor the editing process.
The EDITING property
This is a simple boolean property that returns TRUE$ if an item is being edited.
The EDITORHANDLE property
This property returns the HANDLE of the editor control if an item is being edited.
The EDITKEY property
By default, hitting “F2” on an item puts the control into “edit mode”, just like the Windows Explorer. However, if you wish to change this then you may use the EDITKEY property to select another key instead. The edit key is a Windows virtual key code and constants for these codes can be found in the MSWIN_VIRTUALKEY_EQUATES insert record.
The EDITOPTIONS property
This property allows you to fine-tune some of the validation options for the editor:
TextCase – Specifies if the text entered is lower-case only, upper-case only or mixed (See the EDITLINE TEXTCASE property for more details).
ValidChars – Specifies which characters may be entered into the editor (See the EDITLINE VALIDCHARS property for more details).
MaxLimit – Specifies the maximum number of characters that may be entered into the editor (See the EDITLINE LIMIT property for more details).
At runtime this property is an @fm-delimited array – constants for use with this property can be found in the PS_LISTBOX_EQUATES insert record.
The INCLUDEEDITORTEXT property
By default getting item text from the ListBox (e.g. via the LIST property) will include the text from an item currently being edited, even if that edited text has not yet been saved. Setting this property to FALSE$ ensures that the returned item text ignores the value in the editor instead.
The BEGINEDIT method
This method allows you to programmatically put the ListBox into edit mode, as if the user had pressed “F2” (or whatever value the EDITKEY property is set to). You may specify the index of the item to edit, otherwise it will default to the current item.
This method allows you to programmatically stop the item editing process, optionally allowing any changes to be accepted as if the “Enter” key had been pressed (the default is to reject any changes as if the “Esc” key had been pressed).
This event is raised when the item is updated via the editor, i.e. the user hit the “Enter” key, the control lost the focus, or the EDITEDIT method was used with the bUpdate parameter set to TRUE$. The event passes the index of the item that has changed as well as the old and new data:
This event is raised when an item leaves edit mode without being updated, i.e. the user hit the “Esc” key or the EDITEDIT method was used with the bUpdate parameter set to FALSE$. The event passes the index of the item that was being edited:
So that wraps up this short post on ListBox editing – we’re sure that you’ll find many useful ways of implementing this new feature when expanding your application’s functionality.
As you can see, setting up the control is fairly easy, but the bulk of the work needs to be done in the HTTPREQUEST event where you examine the contents of the request and return the appropriate content.
Type of control firing the event – this is always “HTTPSERVER”
RequestID
Unique identifier for returning a response to the client – this is used with the various “SETRESPONSE” methods that set response data.
RequestHeaders
An @FM delimited dynamic array of data that describes the request. The format is similar to the HTTPRequest argument used in OECGI programming. The full format is described in the PS_HTTPSERVER_EQUATES insert record.
As mentioned above, the RequestHeaders parameter describes the details of the request using a format similar to the format used in OECGI programming. There are some differences that are worth highlighting however:
For a GET request the query values are already parsed into their own fields (<37> and <38>) as an associated multi-value pair. They are not found unparsed in field <1> as per OECGI.
For a POST or PUT request the content is obtained using the GETREQUESTCONTENT method (see below) – it is not passed in the RequestHeaders variable.
Cookies are already parsed into their own fields (<39> and <40>) as an associated multi-value pair.
Headers are already parsed into their own fields (<35> and <36>) as an associated multi-value pair.
Note that out of the box we do not enforce any restrictions or framework on how you handle the request – compare this to classic OECGI programming where the “PathInfo” field is used to determine which “INET_” procedure is executed to fulfill it (via the RUN_INET_REQUEST stored procedure) There is no such requirement with the HTTPSERVER control, and you may create your own framework if you wish (although see the note on RTI_RUN_HTTPSERVER_REQUEST below).
Returning a response
There are several methods described below that you may use to process the content that you return to the client.
GETREQUESTCONTENT
GETRESPONSECONTENT
GETRESPONSECOOKIE
GETRESPONSEFILE
GETRESPONSEHEADER
GETRESPONSESTATUS
ISPORTINUSE
SETRESPONSECONTENT
SETRESPONSECOOKIE
SETRESPONSEFILE
SETRESPONSEHEADER
SETRESPONSESTATUS
SENDRESPONSE
Note: With each of these you must use the unique RequestID parameter passed to you in the HTTPREQUEST event.
IPv6 – if TRUE$ then check the IPv6 bindings, otherwise check the IPv4 bindings
SENDRESPONSE method
Sends the response back to the client. This method should be called when you have finished setting the response details (Note that this is called by the promoted system HTTPREQUEST handler in case you forgot to do it in your own code!).
CookieValue is an @fm-delimited array formatted as follows:
<1> Value
<2> Path
<3> Domain
<4> Expires (internal date format)
<5> Max Age (seconds)
<6> Secure (TRUE$/FALSE$)
<7> HttpOnly (TRUE$/FALSE$)
<8> SameSite
SETRESPONSEFILE method
If you have a file that contains the content you wish to return then you should use this method to let the server read the file and return it to the client itself. This offers better performance than reading the contents via Basic+ and using the SETRESPONSECONTENT method as it avoids any unnecessary copying of data.
As part of version 10.2 we have included a sample HTTPREQUEST event handler called RTI_RUN_HTTPSERVER_REQUEST which you can examine and copy for your own applications if you wish. It emulates the core behavior of the OECGI RUN_INET_REQUEST handler in that it uses the “PathInfo” field to determine the stored procedure to fulfill the request. In this case it looks for a matching procedure that has the prefix “HTTPSVR_” and we have included a couple of example “HTTPSVR_” procedures for you to review as well.
Conclusion
With the addition of the HTTPSERVER control it is now possible to provide HTML content directly from your application, and also provide a means of web-development directly from your desktop without necessarily needing to install a dedicated web-server like IIS.
It is also a good solution for when you want to provide local HTML content in your application’s user-interface via an embedded browser control, because it can avoid the usual security restrictions that browsers enforce for such scenarios.
In previous versions of OpenInsight the usual way of accessing data from a multi-row select EditTable control was to get the SELPOS property and then iterate over the data pulling out the rows, e.g. something like this (not optimized, but you get the idea):
SelPos = Get_Property( CtrlEntID, "SELPOS" )
DataList = Get_Property( CtrlEntID, "LIST" )
SelList = ""
SelRows = SelPos<2>
SelCount = FieldCount( SelRows, @Vm )
For SelIdx = 1 To SelCount
SelRow = SelRows<0,SelIdx>
SelList<-1> = DataList<SelRow>
Next
However, in OpenInsight 10 we added a couple of new properties that allow you to access data in a multi-row select EditTable in a faster and more efficient way. These are:
The SELLIST property
The SELARRAY property
Both of these return data in the familiar LIST and ARRAY formats, but they only return data from the selected rows, thereby saving you the step of accessing SELPOS and iterating over the data yourself. So, to rewrite the example above we can now do this:
SelList = Get_Property( CtrlEntID, "SELLIST" )
Likewise, to return the data in ARRAY format we would use the SELARRAY property like so:
SelArray = Get_Property( CtrlEntID, "SELARRAY" )
Ergo, when asked the other day “What’s the fastest way of getting data from a specific column from the selected rows”, the answer was:
With the release of version 10.1 a new control type called DIRWATCHER (“Directory Watcher”) has been added to OpenInsight. This is a fairly simple control which allows you to monitor one or more directories on your system and then receive notifications when the contents are changed.
Using the control is very straightforward:
Use the WATCHDIR method to add a directory to monitor for changes.
Handle the CHANGED event to receive notifications of directory changes.
Use the STOP method to stop monitoring directories.
We’ll take a quick look at each of these methods and events below along with a couple of important properties:
The WATCHDIR method
This method allows you to specify a directory to monitor along with some optional flags. It may be called multiple times to watch more than one directory.
The flag values are specified in the MSWIN_FILENOTIFY_EQUATES insert record,
This method returns TRUE$ if successful, or FALSE$ otherwise.
The STOP method
This method stops the control monitoring its specified directories.
bSuccess = Exec_Method( CtrlEntID, "STOP" )
This method returns TRUE$ if successful, or FALSE$ otherwise.
(Note – to resume directory monitoring after the STOP method has been called the WATCHDIR method(s) must be executed again).
The CHANGED event
This event is raised when changes have been detected in the monitored directories.
bForward = CHANGED( NewData )
This event passes a single parameter called NewData which contains an @vm-delimited list of changed items (i.e. notifications). Each item in the list comprises an “action code” and the name and path of the affected file, delimited by an @svm.
Action codes are defined in the MSWIN_FILENOTIFY_EQUATES insert record like so:
equ FILE_ACTION_ADDED$ to 0x00000001
equ FILE_ACTION_REMOVED$ to 0x00000002
equ FILE_ACTION_MODIFIED$ to 0x00000003
equ FILE_ACTION_RENAMED_OLD_NAME$ to 0x00000004
equ FILE_ACTION_RENAMED_NEW_NAME$ to 0x00000005
Remarks
If a monitored directory experiences a high volume of changes (such as copying or removing thousands of files) it could generate a correspondingly high number of CHANGED events, which in turn could produce an adverse affect on your application and slow it down. In order to deal with this potential issue it is possible to “bundle up” multiple notifications with the NOTIFYTHRESHOLD property into a single CHANGED event so they may be processed more efficiently.
The NOTIFYTHRESHOLD property
The NOTIFYTHRESHOLD property is an integer that specifies the maximum number of notifications that should be bundled before a CHANGED event is raised.
The NOTIFYTIMER property is an integer that specifies the number of milliseconds before a CHANGED event is raised if the NOTIFYTHRESHOLD property value is not met.
If the NOTIFYTHRESHOLD property is set to a value greater than 1 then the control will try to bundle that number of notifications together before raising a CHANGED event. However, when this is set to a high value it is possible that the threshold may not be reached in a timely fashion and the CHANGED event not actually raised.
E.g. If the NOTIFYTHRESHOLD is set to 1000, and only 200 notifications are received then the CHANGED event would not be raised.
To prevent this problem the NOTIFYTIMER property may be used to specify the amount of time after receiving the last notification before a CHANGED event is raised even if the NOTIFYTHRESHOLD is not met.
E.g. in the example above, if the control had a NOTIFYTIMER of 50, then a CHANGED event would be raised 50ms after the last notification (200) was received, even though the NOTIFYTHRESHOLD of 1000 has not actually been met.
Developer Notes
The DIRWATCHER control is intended as a “non-visual” control and should probably be hidden at runtime in your own applications. However, it is actually derived from a normal STATIC control so all of the properties and methods that apply to a STATIC apply to the DIRWATCHER as well, and you may use them as normal if you wish.