Author Archives: Captain C

Dynamic arrays and offset caching

As you know, dynamic arrays in OpenInsight are internally represented as delimiter-separated strings commonly containing fields, values and sub-values. Traditionally, locating an element required scanning the string from the start to find the required delimiters at the specified index . This can be a performance issue on large arrays, so one of the new features we’ve added to OpenInsight 11 is index/offset caching.

This is a technique that involves caching the byte offsets of previously located field, value and sub-value indexes when you use the “<>” operators, and the Insert/Extract/Replace/Delete functions. This means that we don’t always have to start scanning from the beginning of the string each time an element is accessed.

For example, suppose field 67 is located at byte offset 234, and field 68 begins at offset 245.

  • Without caching, locating field 68 requires rescanning the dynamic array from the beginning to find all preceding field marks.
  • With caching, once field 67 has already been located, the search for field 68 can begin from offset 234 instead of offset 1.

This optimization is especially effective when forward-iterating through large dynamic arrays, where successive accesses are close together. In previous versions, repeatedly accessing successive fields could result in near O(n²) scanning behaviour for large arrays. Offset caching eliminates much of this repeated work.

Consider the following program which processes 10000 SYSOBJ keys:

compile function test_dyn_cache( void )
$insert rti_Struct_Equates
$insert logical
select @file_sysobj
eof = FALSE$
idList = ""
loop
readNext id else eof = TRUE$
until eof
idList := id : @fm
repeat
idList[-1,1] = ""
idList = field( idList, @fm, 1, 10000 )
ctr = fieldCount( idList, @fm )
pf = blank_Struct( "MSWIN_LARGE_INTEGER" )
pcStart = blank_Struct( "MSWIN_LARGE_INTEGER" )
pcEnd = blank_Struct( "MSWIN_LARGE_INTEGER" )
call msWin_QueryPerformanceFrequency( pf )
call msWin_QueryPerformanceCounter( pcStart )
for x = 1 to ctr
id = idList<x>
next
call msWin_QueryPerformanceCounter( pcEnd )
pcStart = struct_To_Var( pcStart, "MSWIN_LARGE_INTEGER" )
pcEnd = struct_To_Var( pcEnd, "MSWIN_LARGE_INTEGER" )
pf = struct_To_Var( pf, "MSWIN_LARGE_INTEGER" )
pcTime = ( ( pcEnd - pcStart ) * 1000 ) / pf
call send_Dyn( "TEST_DYN_CACHE processed " : ctr : " items -> " : pcTime : " ms" )
return ""

Running this benchmark on the same machine using v10.2.4 took approximately 360ms, while OpenInsight 11 completed the same test in around 1–2ms.

For small dynamic arrays the difference may not be noticeable, but performance improvements become increasingly significant as array sizes grow.

This means you can continue using the familiar “<>” syntax for sequential dynamic array processing without needing to rewrite code using LOOP/REMOVE or LOOP/[] parsing patterns.

Introducing OpenInsight 11

With the recent release of OI 10.2.4 it’s time to look ahead at OpenInsight 11 and some of the new features that will be heading your way with the next major release. We’ve been working on it for some time, with a focus on cloud-based features, and there’s plenty to talk about over the next few months as we get it ready, but in the meantime here’s a few highlights:

Revelation Application Server

This is a new native Windows Service application that can host one or more concurrent OpenInsight application instances in a variety of modes:

  • Background Task processing
  • Index Server processing
  • General-purpose “engine-server” with access via a Basic+/Windows DLL/.Net class library/Linux SO, Python wrapper etc.
  • Thin-Client mode for RTP57R (see below)
  • Web-CGI Server (Windows ISAPI, Linux S/CGI, FastCGI, CGI, PHP modules)
  • Internal Web Server with reverse proxying for use with Linux Ngnix/Apache, HTTPS and static file serving support.
  • MCP endpoint for AI access.

RTP57R – Cloud-Optimized Data Access

With the new Application Server we’ve introduced a specialized filing system driver engineered with the Thin-Client mode to meet the demands of distributed applications. As you know, when databases migrate to remote cloud servers and corporate WANs, connection stability and latency often become greater bottlenecks than raw bandwidth. RTP57R (the “R” stands for Remote) addresses these challenges by seamlessly offloading processing to the server when needed and minimizing “chatter” across the wire.

AI Integration

OpenInsight 11 will also feature AI integration features, including a “chat” interface to agentic AI that can be either self-hosted or available through subscription. The Application Server provides an MCP (Model Context Protocol) interface to expose tools and resources to an AI client – we provide a standard set of tools for stored procedure and general DB access, and you can define your own as well.

Basic+ DYNLIST data type

We’ve added a new data type called a “DynList” to Basic+. Basically this is similar to a dimensioned array but it can be resized at runtime. If you’re used to working with other languages then you can think of this as similar to a C++ vector or a C# List<> type etc. DynList variables can store other DynList variables so you can implement fully multi-dimensional data structures.

Basic+ JSON data type

We’ve also made JSON a first class citizen and implemented a set of new JSON functions into Basic+. OI10 included a set of JSON functions exported from RevJSON.dll, but there was always the issue of manual handle management, and some memory management issues. Having JSON in Basic+ removes these concerns.

And there’s more…

So that’s just a few new things that we can tell you about now, and of course there will be more work on improving the performance and functionality of other existing areas like the IDE, Form Designer and other tools. We’ll reveal more details in future posts.

$DEBUG – the safer alternative to Debug

Probably one of the most annoying issues that developers encounter is distributing programs to customers that contain unintentional ‘Debug’ statements. These are often left in from testing, and, with the pressure of deadlines to deliver, they are very easy to overlook.

In order to help alleviate this issue OpenInsight 10 implements a “$DEBUG” statement that you can use in place of the normal “Debug” statement: The difference here is that the debugger only triggers if the code is running on the same machine that it was compiled on, so if your program does manage to escape with a compiled $DEBUG statement it shouldn’t execute on other machines.

There are two variations of $DEBUG. The first is a simple “$DEBUG” statement like so:

Write Record To hFile, Key Else
// Failed etc...
$DEBUG
End

The second type of $DEBUG statement behaves in the same manner as described above, but also includes the ability to define a simple expression to test, and will only trigger the debugger if the test is met (This is similar to the way that the $ASSERT statement works). For example:

// Only debug if x is equal to 3
$DEBUG( x == 3 )

$DEBUG can be used in any version of OpenInsight 10 with the following caveats:

  • From version 10.2.3 onwards the raw workstation name (e.g. “REVWS07” ) is used to test if the debugger should execute.
  • Prior to this (version 10.2.2 and earlier), the entire contents of @station is used to test. The issue here is that @station usually includes the OpenInsight process ID (e.g. “REVWS07_45383” ) so it will only debug within the instance of OpenInsight that it was compiled on. This can be a little too restrictive unless you are willing to recompile your programs after restarting your application, hence the change for version 10.2.3 mentioned above.

Note that you can also disable $DEBUG statements when compiling, in which case the debug tests are never included in the object code.  To do this simply use the NODEBUG token with the #define statement like so:

#define NODEBUG

So, hopefully you can use the $DEBUG statement for your test debugging and have a smoother experience with your customers!

The new Scan Repository tool

OpenInsight 10.2.3 brings a much needed update to the venerable Scan Repository tool, providing an updated user interface, full support for new repository types, and some new methods for problem detection and repair. In this post we’ll take a look at each of these areas in more detail, and how the various options work when repairing your system.

A new user interface

The Scan Repository tool is accessed from the IDE main menu via the Tools\Repository menu item and then choosing the “Scan Repository For Errors” option (or alternatively by executing the RTI_IDE_SCAN_REP form via the System Monitor). Users of previous incarnations of the Scan Repository tool will notice an immediate change in the user interface as it is no longer a “wizard” style UI, but now shows all of the scanning options, progress information and results on a single form instead:

The new Scan Repository tool
The Scan Repository Tool

Using the scanner is a simple process:

  • Ensure the Scan Prerequisites are met.
  • Choose the Scan Options.
  • Click the “Analyze” button to begin the scan. Progress information will be shown at the top of the form and the scan can be cancelled at any point.
  • Review the results and, if necessary, select the appropriate repair action. These appear in dropdown lists in the “Action” column of the results table and can be one or more of the following options (depending on the error):
    • Add
    • Delete
    • Ignore
  • Make sure that the items you want to fix are highlighted (selected) in the list (Remember to use the context menu to help with the selection process!).
  • When ready click the “Process” button to begin the repair process.
  • Review the repair results.
    • Note that some repairs may not be able to be completed automatically. In this case the corresponding action item may change to an “Edit” button so that the problem item can be fixed manually.
    • Details of errors encountered during the repair process are shown in a tooltip when the mouse is hovered over a problem item.

Scan Prerequisites

Repairing errors in your repository involves possible changes to some fundamental system components, so the following requirements must be met before the scan can take place:

  • Ensure that you have backed up your system and check the “A recent backup is available” check box.
  • Ensure that all others users are logged out of your system, otherwise the scan cannot proceed. The details of other logged-in users will appear in the Users list to help identify them.

Scan Options

There are two categories of scanning options. The first, “Types and Classes”, scans the core repository records to ensure that they are all correct, while the second category, “Entities” scans the normal application components.

Scanning for Types and Classes

The functionality of the repository is based heavily on the information contained in the following three locations, so it is essential that these items are correct:

  • REPFAMILY entities (SYSREPOSTYPEFAMILIES records)
  • REPTYPE entities (SYSREPOSTYPES records)
  • REPCLASS entities (SYSREPOSCLASSES records)

These are referred to as FTC entities (Family/Type/Class). They can be one of two types:

  1. “Known” – these are the FTC entities are that are supplied by Revelation.
  2. “Custom” – these are non-Revelation FTC entities added by developers that have been marked as valid in a previous “Types and Classes” scan (When an “unknown” FTC entity is first encountered in a repository scan it can be flagged as “custom” so it is considered valid in subsequent scans – see the “Unknown” class of errors in the table below).

Verify Repository Types and Classes

The state of the FTC entities can be validated by checking the “Verify Repository Types and Classes” box which ensures that this process takes place before any other normal entities are checked.

Checks performed during this scan include:

  • All known FTC entities are present and not missing any critical information.
  • All custom FTC entities which have been flagged as valid are not missing any critical information.
  • All other FTC entities that are not flagged as custom are reported as “Unknown” so they can be reviewed.

The Verify Repository Types and Classes scan can produce the following errors:

ErrorDESCRIPTION
Missing Known Type Family,
Missing Known Type,
Missing Known Class
One of the known FTC entities is missing. The default version can be added back into the system using the “Add” action.
Unknown Type Family,
Unknown Type,
Unknown Class
An unknown FTC entity has been found. It can be added as a custom type using the “Add” action, or removed using the “Delete” action. Items that have been added are marked as custom and treated as a valid FTC type in future scans.
Missing Repository RecordThe raw FTC body record is missing a corresponding SYSREPOS header record. A default SYSREPOS record can be created using the “Add” action.

(Note: OpenInsight repository entities are usually comprised of two components: A “header” record in the SYSREPOS table, and a “body”. The latter can be a record in a table or an OS file depending on the type of entity).
Bad Repository Sub-keyThe SYSREPOS header record for the FTC entity has an invalid SUBKEY entry. The “Add” action may be used to correct this.
Missing Title,
Missing Icon,
Missing Image,
Missing Type Family,
Missing SCM Format,
Missing SCM Include,
Missing Storage Type,
Missing Location,
Missing RDK Extract Type,
Missing Key Format,
Missing Designer Tool
The FTC body record is missing some required information.

If this is a known FTC entity then the “Add” action may be used to reset the missing value to its default setting.

If this is a custom FTC entity the “Add” action will switch to an “Edit” button, which opens the entity for editing in the IDE.
Missing Event ClassA custom event (added via the Event Designer tool) is missing a corresponding SYSEPOSCLASSES record. The “Add” action may be used to add a default record.

Reset to default values

Selecting this option ensures that all known Revelation-supplied FTC entities are reset to their default values during the scan. This option can be used to ensure that your repository is always in a good state.

Scanning Entities

This part of the process scans the rest of the repository based on the options chosen:

OptionDescrIPTION
Scan for missing entity headersScans a set of core tables looking for missing repository header (SYSREPOS) records.

Due to the fact that it can allow “lost” entities to be readded to the repository it is recommended that this option be run before any others.

If this option is selected all of the other Entity scanning options will be disabled.
Scan repository keys onlyOnly checks the validity of repository header (SYSREPOS) keys and does not check the state of the entity itself.

The only other option allowed with this selection is “Use strict validation criteria for entity IDs”.
Validate “uses” dependenciesEnsures any entities flagged as “used” by the entity being scanned actually exist. If they don’t the link is automatically removed.
Validate “used by” dependenciesEnsures any entities flagged as “used-by” by the entity being scanned actually exist. If they don’t the link is automatically removed.
Validate “documentation” dependenciesEnsures any documentation entities linked to the entity being scanned actually exist. If they don’t the link is automatically removed.
Scan for orphaned event handlersEvent entities (source and executable) are checked to ensure that they are linked to their parent forms, and that the parent forms actually exist.
Scan for missing entity bodiesValidates that each repository entity has a corresponding body record or OS file as appropriate.
Use strict validation criteria for entity IDsValidates that keys in the SYSREPOS table conform to a limited set of characters.

When the “Scan for missing entity headers” box is checked the Entities scan can produce the following errors:

ErrorDescription
Orphan – Missing SYSREPOS [<id>]An entity body record is missing a corresponding SYSREPOS header record. The “Add” action may be used to add a default header record, the “Delete” action may be used to delete the orphaned body.
Orphan – Bad App [<id>]An entity body record is linked to a non-existent application ID. The “Delete” action may be used to delete the record (and any associated SYSREPOS header record).
Orphan – Suspected Bad App [<id>]Due to the polymorphic nature of the records in some tables (primarily SYSPROCS and SYSOBJ) it is not always possible to accurately determine what type of record is being checked.

This error can be raised in such circumstances, and the “Delete” action may be used to remove the record in question.

However, it is advised that you review the record in question before you delete it!
Orphan – Bad Key Format [<id>]A known type has a badly formatted key. The “Delete” action may be used to remove the record in question.

When the “Scan for missing entity headers” box is not checked the Entities scan can produce the following errors:

ERRORDescription
Bad KeyThe entity has a badly formatted SYSREPOS key. The “Delete” action may be used to remove the entity.
Bad AppThe APPID of the SYSREPOS key refers to an invalid application ID. The “Delete” action may be used to remove the entity.
Bad TypeThe TYPEID of the SYSREPOS key refers to an invalid SYSREPOSTYPES type ID. The “Delete” action may be used to remove the entity.
Bad ClassThe CLASSID of the SYSREPOS key refers to an invalid SYSREPOSCLASSES class ID. The “Delete” action may be used to remove the entity.
Deprecated EventAn entity has been found for a class of event that is no longer supported in version 10. The “Delete” action may be used to remove the event in question.
Strict TestThe SYSREPOS header key failed the “strict” test and is considered to be badly formatted. The “Delete” action may be used to remove the entity.
No locationThe SYSREPOS header record is missing a required location (e.g. an IMAGE type is missing the location of the OS image file). The “Delete” action may be used to remove the entity.
No BodyThe entity is missing a body record or OS File as determined by its type. This can happen if the body record exists but is null, or the ACCESS method returns null. The “Delete” action may be used to remove the SYSREPOS header record.
No Body <FSError>The entity’s body record cannot be accessed because of a filing system error. The “Delete” action may be used to remove the SYSREPOS header record.
Open ErrorA table open error occurred when checking a DBTABLE type entity. The “Delete” action may be used to remove the SYSREPOS header record.
Orphan – Missing FormAn event script entity is referring to a form entity that does not exist (i.e. there is no SYSREPOS header record for the form). The “Delete” action may be used to remove the event entity.
Orphan – Missing Form BodyAn event script entity is referring to a form entity whose body record does not exist (i.e. there is no SYSREPOSWINS or SYSREPOSWINEXES record for the form). The “Delete” action may be used to remove the event entity.
Orphan – Missing ControlAn event script entity is linked to a control that does not exist on the form. The “Delete” action may be used to remove the event entity.
Orphan – Event Not LinkedAn event script entity has been found for a control or form that does exist but it is not linked to it. The “Add” option may be used to create the link, or the “Delete” action may be used to remove the event.

(Note: Please review the control and event in question before you decide to add it as there may be a good reason that it is no longer linked!)
Orphan – Missing Script ExeAn event script debug-table entity has no corresponding event script executable entity. The “Delete” action may be used to remove the event script debug-table entity.
Orphan – Event Cannot Be LinkedAn executable event script entity cannot be dereferenced to an owner. The “Delete” action may be used to remove the executable event script.

The Scan Repository context menu

The results list of the Scan Repository tool includes a comprehensive context menu to help with selecting the items to repair along with an export option and an easy way to view the details of a specific entity:

Menu ItemDescription
Process all itemsExecutes the specified repair action (if appropriate) for all entities in the results list.
Process selected items onlyExecutes the specified repair action (if appropriate) for all selected entities in the results list.
Set all items to “Add”Sets the action option of all items in the results list to “Add” (but only if they have an “Add” option).
Set all items to “Delete”Sets the action option of all items in the results list to “Delete” (but only if they have a “Delete” option).
Set all items to “Ignore”Sets the action option of all items in the results list to “Ignore” (but only if they have an “Ignore” option).
Set all selected items to “Add”Sets the action option of all selected items in the results list to “Add” (but only if they have an “Add” option).
Set all selected items to “Delete”Sets the action option of all selected items in the results list to “Delete” (but only if they have a “Delete” option).
Set all selected items to “Ignore”Sets the action option of all selected items in the results list to “Ignore” (but only if they have an “Ignore” option).
Select all itemsSelects all of the items in the results list.
Clear all selectionsUnselects all of the items in the results list.
View <entity>Attempts to open both the SYSREPOS header record and body record (if possible) for the specified entity. These are opened as “raw” records in the IDE.
Export resultsExport the contents of the results list to a CSV file.

Conclusion

The new Scan Repository tool is available in OpenInsight v10.2.3 onwards. Hopefully it will allow you to keep your repository in good shape moving forwards!

Directory Management in OpenInsight 10

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.

Preferred methods

  • The FILESYSTEM object (for Event Context)
  • RTI_OS_Dir stored procedure (for non-Event Context)

Deprecated methods

  • Utility stored procedure
  • RTI_OS_Directory stored procedure
  • DirExists stored procedure
  • MkDir stored procedure
  • UtilityMakeDir stored procedure
  • UtilityRemoveDir stored procedure
  • UtilityRename stored procedure

A note on directory management and context

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:

  • COPYDIR
  • DIREXISTS
  • GETSPECIALDIR
  • MAKEDIR
  • MOVEDIR
  • REMOVEDIR
  • RENAMEDIR

The FILESYSTEM object is fully documented here.

Using the RTI_OS_Dir stored procedure

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

(EDIT: Full documentation for RTI_OS_DIR and GET_SPECIAL_FOLDER is now live on the Revelation Wiki.)

ListBoxes and TreeListBoxes – Checkbox Items

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.

CheckedItems = Get_Property( CtrlEntID, "CHECKED" )
CheckedItems<2> = TRUE$
CheckedItems<3> = FALSE$
CheckedItems<4> = TRUE$

Call Set_Property_Only( CtrlEntID, "CHECKED", CheckedItems )

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.

AllCheckedItems = Get_Property( CtrlEntID, "CHECKEDLIST" )

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.

AllCheckedItemsText = Get_Property( CtrlEntID, "CHECKEDLISTTEXT" )

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.

WEBVIEW object documentation

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:

   495 – WEBVIEW object.pdf

Migration tool updates in OpenInsight 10.2.1

Due to a significant rise in OpenInsight migrations in the past few months and the resulting feedback, we’ve fixed several bugs in the migration tool for the next release and we’ve also made one major change which we’ll describe here.

The previous migration tool post had a small note at the end called “Bonus Trivia – a quick note on migrating data” in which we recommended ignoring the option to move or copy your data tables with the tool and do it manually instead. This is still the preferred course of action, but the tool has been updated to offer more useful functionality instead: it now has the ability attach the same list of tables as your v9 system but at a different location, which should save you time when migrating an application with a large number of tables.

Attaching tables in the migration tool

OpenInsight v10.2.1 Migration Tool showing new "attach tables" option
Migration Tool Start Page

If you are familiar with previous versions of the tool you will see the following changes have been made:

  • The Move and Copy Table actions have been removed from the list of Actions
  • The “Attach” action has been renamed to “Attach Original”. This attaches the tables at their existing location as defined in your v9 application DBT file.
  • An “Attach New” action has been added. This attaches the same list of tables as the v9 application but at a different location instead. When you select this option you must specify the location in the “New Location” column.
  • A “Save database definition” check box has been added. This is enabled when you have selected at least one of the “Attach Original” or “Attach New” actions.

So, the recommended course of action for handling your tables when migrating your application for the first time is to:

  1. Move or copy your existing data tables to a new location if you wish.
  2. If your tables have been moved/copied then specify the “Attach New” action for each affected data volume and enter the new location.
  3. If your tables have not been moved then choose the “Attach Original” action instead.
  4. Check the “Save database definition” check box.

On subsequent re-runs of the tool you may simply select the “Ignore” action for your data volumes.

Hopefully this update will make migrating systems with many tables much easier for you.

Profile logging updates in OpenInsight 10.2

Previous releases of OpenInsight have included a simple logging system that records all stored procedure calls and the time taken to execute them. This logging system is enabled by creating a blank file called “RevProfile.log” in the OpenInsight directory before the application is started. If this file is present when the system boots it creates a new log file (with a name based on the application’s Windows Process ID) and writes all stored procedure activity to it until the application is stopped.

Whilst this can be a useful tool it does suffer from a few issues:

  • The log file records all activity. This usually results in very large files that are difficult to navigate and process, making them very tedious to use.
  • There is no facility to annotate parts of the log, so it can be difficult to narrow down the actual section of code that needs to be profiled.
  • The profile timing uses the classic Windows “tick count” which has a granularity of 16ms. Many stored procedures take much less time to execute than this, so entries in the log can be wildly inaccurate, usually appearing as “0ms” or “16ms”.
  • There are no tools to actually parse the log so that it can be analyzed more easily.

With this release of OpenInsight we have provided some new functionality to address these issues, namely:

  • The “RevProfileLog” function, and
  • The “RevProfile Log Analyzer” form.

In this post we’ll take a look at these additions and examine how they can help in your application development.

The RevProfileLog function

This is a new stored procedure for version 10.2 that allows much greater control of the profile logging process and offers the following benefits:

  • It can be started and stopped as needed, allowing the scope of recording to be narrowed to a critical area (we call this “Dynamic Logging“, as opposed to the old way of profile logging mentioned above, which we call “Static Logging“).
  • The log file can be created using a custom name and location.
  • The file can be appended to each time it is started, or cleared.
  • The log can exclude specific stored procedures.
  • The log can record notes against a specific stored procedure to help identify a particular call in the log.
  • The log can record arbitrary text in the file to help with subsequent analysis.

All of this is controlled via the new RevProfileLog stored procedure (this is compiled as a subroutine so has no return value):

Call RevProfileLog( Method, Param1, Param2 )

The Method parameter is a numeric value that specifies the logging operation to perform; the Param1 and Param2 parameters depend on Method as described below:

MethodDescriptionParam1Param2
1Start Log: Starts the profile logging activity.The name of the log file to use or create.A boolean flag – if TRUE$ then the log file is cleared before use. Defaults to FALSE$.
2Stop Log: Stops the profile logging activity.N/a.N/a.
3Set Note: Appends text to the next stored procedure call written to the log. It is reset to null afterwards.The note text.N/a.
4Output Text: Outputs text to the log.The text to output.The indent to output to. Specify “-1” (the default) to output at the current indent, otherwise specify a value between 1 and 256 if desired.
5Exclude Proc: Prevents a stored procedure from being included in the log file.The name of the stored procedure (upper- case).N/a.
6Include Proc: Removes a stored procedure from the list of excluded stored procedures so that it may be written to the log again.The name of the stored procedure (upper-case).N/a.
7Clear Excluded: Clears the excluded stored procedure list.N/a.N/a.

Equates for these methods can be found in the RTI_REVPROFILE_EQUATES insert record.

Example: Profiling a call to Get_Property

Compile Subroutine Test_RevProfileLog( void )

   Declare Function Get_Property
   $Insert RTI_RevProfileLog_Equates
   $Insert Logical
   
   LogFile = "c:\temp\test_revprofile.log"
   
   // Create the log file and clear it before it is written to:
   Call RevProfileLog( RPL_STARTLOG$, logFile, TRUE$ )
   
   // Let's not see any RTP57/A calls (if any)
   Call RevProfileLog( RPL_EXCLUDEPROC$, "RTP57" )
   Call RevProfileLog( RPL_EXCLUDEPROC$, "RTP57A" )
   
   // Add a note so we can see why we are calling Get_Property
   Call RevProfileLog( RPL_SETNOTE$, "Getting SYSTEM VERSION" )
   Version = Get_Property( "SYSTEM", "VERSION" )
   
   // Add some text to the log, but don't indent it.
   Call RevProfileLog( RPL_OUTPUTTEXT$, "Done with this log now ***", 0 )
   
   // Stop the log
   Call RevProfileLog( RPL_STOPLOG$ )

Return

Produces the following log file:

		GET_PROPERTY 0.00 Getting SYSTEM VERSION
			PS_GET_PROPERTY 0.12
				OEREQINFO 0.15
				OEREQINFO 2.29 2.14
			PS_GET_PROPERTY 2.31 2.19
			PS_GET_PROPERTY 2.32
				OEREQINFO 2.32
				OEREQINFO 2.42 0.10
			PS_GET_PROPERTY 2.43 0.12
			PS_GET_PROPERTY 2.44
				OEREQINFO 2.44
				OEREQINFO 2.53 0.09
			PS_GET_PROPERTY 2.55 0.11
			PS_GET_PROPERTY 2.55
				OEREQINFO 2.56
				OEREQINFO 2.65 0.09
			PS_GET_PROPERTY 2.66 0.11
			RTP65 2.67
			RTP65 2.67 0.01
			PS_GET_PROPERTY 2.68
				OEREQINFO 2.69
				OEREQINFO 2.78 0.09
			PS_GET_PROPERTY 2.79 0.11
		GET_PROPERTY 2.80 2.80
>> Done with this log now ***

Things to note:

  • Text lines added with the “Output Text” method are prefixed with a “>>” string to denote that they are text.
  • The timings now use the Windows high resolution “QueryPerfomanceCounter” functions to record timings, so the results are far more accurate and we’re now reporting at a 100th of a millsecond.
  • The indenting does not begin at “level 1” because the system call stack was already several levels in before logging was started.
  • Calls to RevProfileLog itself are always excluded.
  • As we mentioned above we call the original method of profile logging “static logging” (i.e. create a file called “RevProfile.log” and record everything). This feature still works in the same way, but with the added bonus of using the improved timings and respecting all of the RevProfileLog function methods except for the “Start Log” and “Stop Log” methods. They are ignored during static logging.

The RevProfile Log Analyzer form

This is a new form (called RTI_IDE_ANALYZE_REVPROFILELOG) that can parse a RevProfile log file and present the information in a format that is easy to use.

For example, here is the small log file example shown above processed and loaded into the form:

An image of the RevProfile Log Analyzer form (RTI_IDE_ANALYZE_PROFILELOG).

Using this form is fairly simple: just select the log file you want to analyze and wait for the form to parse and process the information. Once this has been done the data from the log is presented in two sections:

  1. The Call Trace Log (on the left), and
  2. The Stored Procedure Call Details (on the right)

The Call Trace Log

This is a TreeListBox control that shows the sequence of calls along with the time taken for that call. It mirrors the structure of the actual log file and displays text notes using a different icon (a green globe), rather than the blue one used for stored procedure calls.

Double-clicking on a stored procedure call selects it in the Stored Procedure Call Details section on the right.

The Stored Procedure Call Details

This section comprises two EditTable controls:

  • The top one contains a list of all the stored procedures found in the log, along with the number of times each was was executed and the total time spent executing.
  • The bottom one details the individual calls to the selected stored procedure – each entry in the table shows the index of the call in the Call Trace Log, the line number in the actual log file where the call was recorded, and the time taken by that call. Double-clicking a call selects the matching entry in the Call Trace Log opposite.

Things to note

  • Both edit tables support sorting via double-clicking on the appropriate column header.
  • Annotated stored procedure calls (i.e those with a note added to them via the RevProfileLog “Set Note” method) are separated from calls to the same stored procedure that are not annotated. This is to ensure that it is easy to isolate and find them in the output.

Very large log files – a cautionary note

RevProfile log files can grow rather large, especially the “static” log files that record the entire lifetime of the application. Whilst these files can be easily read and parsed in smaller chunks, representing their call trace structure in a something like a TreeListBox control can be challenging due to the time taken to load items. For example, something like a 15MB log file with nearly 200,000 calls may take several seconds to appear in the control once the log has been parsed and we set the control’s LIST property (i.e. “Loading call trace list …” appears in the progress bar), and the UI may appear frozen. This is not the case – if you are loading a very large file be sure to exercise a little patience before you reach for the Task Manager!

Conclusion

The new RevProfileLog function provides much better and much-needed profile logging functionality to OpenInsight, making the information much easier to use, and we’re sure it will help you improve the performance of your applications.

(This functionality is available in OpenInsight 10.2 from the Beta 3 release onwards)

(Edit 26 Jun 24 – Corrected name of analyzer form)

EditTables – The CELLPOSCHANGED event

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.

bForward = CELLPOSCHANGED( CtrlEntID,   |
                           CtrlClassID, |
                           NextColumn,  |
                           NextRow,     |
                           ContextFlags )

ContextFlags is a simple bitmask integer that contains the following flags:

Bit Flag ValueDescription
0x00000001If set then the cell position was changed via a keystroke.
0x00000002If 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

  1. 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).
  2. If a CELLPOSCHANGED event handler is defined by the developer then a standard POSCHANGED event will not be raised.
  3. 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.
  4. CELLPOSCHANGED is available in OpenInsight 10.2 from the Beta 3 release onwards.