Tag Archives: Properties

Listboxes and ToolTips

Following on from our previous post on the new TOOLTIP property, this time we’re going to look at the new tooltip functionality added to Listbox controls, namely tracking and in-place item tooltips.

Tracking tooltips are small popup windows that appear when a user hovers over a partially obscured item in the control: they display the full item text string instead, and thereby avoid the need for horizontal scrolling, which is always a preferable user experience.

listbox_tracking_tooltips

Offset tracking tooltip

In-place tooltips operate in a similar manner, but rather than appearing at an offset to the cursor position they actually appear over the item itself, hence the term “in-place”:

listbox_inplace_tooltips

In-place tracking tooltip

This behaviour is controlled by the new SHOWITEMTOOLTIPS property which can be set to one of the following values:

  "0" - Disabled : Item tooltips are not displayed
  "1" - Offset   : Item tooltips are displayed at an offset to the cursor
  "2" - In-place : Item tooltips are displayed over the item itself.

When set to “1” (Offset) or “2” (In-place) the SHOWITEMTOOLTIPS property overrides the normal TOOLTIP property, so a “normal” tooltip will not be displayed.

Displaying alternative item text

Sometimes it is desirable to display a different text string in the tooltip rather than the item text itself.  In this case you can set the new SHOWVALUESASTOOLTIPS property to specify that the tooltip should display the contents of the item’s VALUE property instead.

SHOWVALUESASTOOLTIPS is a simple boolean property of TRUE$ or FALSE$.

listbox_tracking_value_tooltips

Displaying the Value property as a tooltip

Note in this case the tooltip is always displayed at an offset, and is triggered regardess of whether or not the item is clipped.

 

Combobox and TreeListbox controls

Both new properties described here also apply to simple Combobox and TreeListbox controls.

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

 

TOOLTIP is the new HELPTEXT

A tooltip is a small pop-up window that describes a control being pointed to, usually referred to as a “tool”, and they are commonly used to provide labelling for controls that only display an image, such as toolbar buttons.

Simple tooltip

Simple tooltip

Earlier versions of OpenInsight supported some basic tooltip functionality for a very limited set of controls via the HELPTEXT property, but for version 10 the name HELPTEXT has been deprecated for all standard controls (i.e. for objects other than menu items) and renamed to TOOLTIP instead, thereby bringing it in line with standard Windows terminology.  The name HELPTEXT can still be used however, to preserve backwards compatibility with your existing code.

Unlike in previous versions the new TOOLTIP property is not a single string, because there are more attributes that can be set – It is now an @fm-delimited dynamic array structured as follows:

   <1> Text
   <2> Title
   <3> Icon
   <4> Large Icon
   <5> Balloon Style
   <6> Centered

Text

This is the text to display.  Multiple lines are delimited by @tm.  This attribute is required for the tooltip to be displayed.

Tooltip with multiple lines

Tooltip with multiple lines

Title

This is the title to display in the tooltip. This is an optional attribute.

Tooltip with title

Tooltip with title

Icon

This attribute is only valid if the Title attribute is set.  It should contain the name of an icon file or resource to display in the tooltip (as per the usual ICON property).  Alternatively you can pass one of the following special characters to use a standard Windows icon instead (in a similar manner to the Msg() function):

  • “*” – Use the standard Information icon
  • “!” – Use the standard Warning icon
  • “H” – Use the standard Error icon
Tooltip with icon

Tooltip with icon

Large Icon

Normally the tooltip uses a 16×16 pixel size icon.  When this attribute is set to TRUE$ the tooltip will use a 32×32 pixel size one instead.  The default value is FALSE$.

Tooltip with large icon

Tooltip with large icon

Balloon Style

Standard tooltips use a rectangle shape when they are displayed. When this attribute is set to TRUE$ a “balloon” shape is used instead, with a stem pointing to the owning tool.  The default value is FALSE$.

Balloon style tooltip

Balloon style tooltip

Centered

Tooltips normally display themselves at an offset to their tool – when this attribute is set to TRUE$ the tooltip is centered underneath the tool instead.  The default value is FALSE$.

Tooltip with center-style

Tooltip with center-style

Basic+ example:

   // Example: display a multiline balloon tooltip containing a warning icon 
   // and title for a static control called TXT_ALERT

   declare function rti_ErrorText
   $insert ps_Tooltip_Equates
   $insert logical

   tooltip = ""
   tooltip<PS_TOOLTIP_POS_TEXT$>    = rti_ErrorText( "SP", errorCodes )
   tooltip<PS_TOOLTIP_POS_TITLE$>   = "Woeful tidings"
   tooltip<PS_TOOLTIP_POS_ICON$>    = PS_TOOLTIP_ICON_WARNING$  ; // "!"
   tooltip<PS_TOOLTIP_POS_BALLOON$> = TRUE$

   call set_Property_Only( @window : ".TXT_ALERT", "TOOLTIP", tooltip )

The TOOLTIP property applies to all controls except OLE controls, as these usually provide their own tooltips.  Some controls, such as List Box and Toolbar controls, also support different types of tooltips such as in-place and tracking, and these will be described in a future post.

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

Object Notation Syntax

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

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

  • Get_Property
  • Set_Property_Only
  • Exec_Method

Using Properties

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

Get_Property syntax

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

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

Set_Property_Only syntax

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

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

Where:

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

Get_Property examples

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

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

Set_Property_Only examples

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

Using Methods

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

Exec_Method syntax

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

Where:

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

Exec_Method examples

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

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

Using Object Notation in Stored Procedures

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

  #Pragma PreComp Event_PreComp

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

  • Get_Property
  • Exec_Method

E.g.

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

   // ... etc ... 

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

Object Notation limitations

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

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

Object Notation troubleshooting

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

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

   #Pragma Output <table> <record>

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

   #Pragma Output SYSLISTS PRECMP_OUT

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

The VALUES and VALUE properties

When working with ListBox and ComboBox controls it can sometimes be a chore to derive an “internal” value from list of visible items.  In many cases this involves creating an associated array of internal values, storing them in the MISC property (or a custom “@” property), and then extracting this array and the SELPOS property at runtime to arrive at the required data.

For example:

// Set up a LIST and internal items
colorNames = "Red"    : @fm : "Green"  : @fm : "Blue"
colorVals  = 0x0000FF : @fm : 0x00FF00 : @fm : 0xFF0000

call set_Property_Only( @window : ".LIST_1", "LIST", colorNames )
call set_Property_Only( @window : ".LIST_1", "MISC", colorVals )

... 

// Then at runtime to access the currently selected internal value
colorVals = get_Property( @window : ".LIST_1", "MISC" )
selPos    = get_Property( @window : ".LIST_1", "SELPOS" )

colorVal  = colorVals<selPos>

...

// Or even more long-winded, find an internal value from the external one:
colorNames = get_Property( @window : ".LIST_1", "LIST" )
colorVals  = get_Property( @window : ".LIST_1", "MISC" )

locate "Green" in colorNames using @fm setting pos then
   greenVal = colorVals<pos>
end

...

// or perhaps setting the currently selected item from an internal code:
colorNames = get_Property( @window : ".LIST_1", "LIST" )
colorVals  = get_Property( @window : ".LIST_1", "MISC" )

locate 0x00FF00 in colorVals using @fm setting pos then
   greenName = colorNames<pos>
   call set_Property_Only( @window : ".LIST_1", "TEXT", greenName )
end

To make this style of coding somewhat cleaner, OpenInsight 10 introduces the new VALUES property, which allows you to set an “internal” value for each item in the control, along with property indexing support.  There is also a new VALUE property, which is the counterpart to the standard TEXT property, and provides access to the currently selected item via it’s internal value.

Here’s the above examples rewritten to use VALUES and VALUE instead:

// Set up a LIST and internal items
colorNames = "Red"    : @fm : "Green"  : @fm : "Blue"
colorVals  = 0x0000FF : @fm : 0x00FF00 : @fm : 0xFF0000

call set_Property_Only( @window : ".LIST_1", "LIST", colorNames )
call set_Property_Only( @window : ".LIST_1", "VALUES", colorVals )

... 

// Then at runtime to access the currently selected internal value
colorVal = get_Property( @window : ".LIST_1", "VALUE" )
...

// Find an internal value from the external one:
greenVal = get_Property( @window : ".LIST_1", "VALUES", "Green" )

// or by position
greenVal = get_Property( @window : ".LIST_1", "VALUES", 2 )

...

// or perhaps setting the currently selected item from an internal code:
call set_Property_Only( @window : ".LIST_1", "VALUE", 0x00FF00 )

VALUES and VALUE apply to both ListBox and ComboBox controls.

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

Sorting in EditTables

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

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

The SORT method

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

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

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

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

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

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

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

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

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

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

Using the SORT method triggers a new event called SORTED:

The SORTED event

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

The COLHEADERSORTINGMODE property

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

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

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

The SORTTRIM property

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

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

 

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

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

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

 

 

 

 

 

Size and Position

In previous versions of OpenInsight the position of a Window or control has always been determined by its SIZE property – an @fm-delimited array comprised of the Left, Top, Width and Height values.  This means, of course, that all of these values have to be processed together at the same time.

For example, if you only want to update a control’s Top attribute then you first have to get the current SIZE property, update the second field, and then set the entire array again like so:

ctrlSize    = get_Property( ctrlID, "SIZE" )
ctrlSize<2> = 100
call set_Property_Only( ctrlID, "SIZE", ctrlSize )

This can become tedious to write and it is also inefficient.

For version 10 we’ve exposed each of the SIZE fields as separate properties so you can now access them directly.  The new properties are:

  • LEFT
  • TOP
  • WIDTH
  • HEIGHT

Here’s the previous example updated to use the TOP property:

call set_Property_Only( ctrlID, "TOP", 100 )

Positioning using rectangle (RECT) coordinates

Those of you used to working with the Windows API will know that many API functions don’t use Width and Height values when dealing with positioning: they work with a “RECT” structure that uses Left, Top, Right and Bottom values to define an object’s position instead (i.e. the coordinates of the top-left corner and the bottom-right corner).

In some cases being able to update a position using Right and Bottom instead of Width and Height can actually be more efficient because it can mean less calculations needed in your own code, and so a new RECT property has been added to enable this functionality.

The new RECT property works in exactly the same way as the current SIZE property except that the Width and Height fields have been replaced by the Right and Bottom fields like so:

* // RECT property structure
* // 
* //   <1> Left
* //   <2> Top
* //   <3> Right
* //   <4> Bottom

As with the SIZE property we’ve also exposed the  individual fields as separate properties so there are two new properties to complement RECT which are:

  • RIGHT
  • BOTTOM

 

 SIZE, RECT and “stealth mode”

As you may know, when using the SIZE property with the Set_Property function you can set a “visible” attribute in the 5th field that can control the visibility of the object when it is moved. For example, setting the SIZE of an invisible WINDOW makes it visible by default unless you set this visible flag to “-1”.  This is still the case in version 10 and it also applies to the new RECT property as well.

However, we have also added a new 6th field that can contain a “Suppress Change Notification” flag.  When this flag is set to TRUE$ the object that has been moved receives no internal notification from Windows that it has been updated, so this will stop any MOVE and SIZE events from begin raised as well as preventing any autosize processing.  This is sometimes necessary when you have complex positioning requirements.

* // Full SIZE property structure when used with Set_Property
* // 
* //   <1> Left
* //   <2> Top
* //   <3> Width
* //   <4> Height
* //   <5> Visibility
* //   <6> Suppress Change Notification
* // Full RECT property structure when used with Set_Property
* // 
* //   <1> Left
* //   <2> Top
* //   <3> Right
* //   <4> Bottom
* //   <5> Visibility
* //   <6> Suppress Change Notification

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

The SCALED event

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

SCALED event

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

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

The event is passed the following event-specific arguments:

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

The system performs no default processing for this event.

 

Handling layout for scaled forms

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

There are basically two choices available:

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

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

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

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

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

SCALEUNITS property

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

It accepts the following values:

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

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

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

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

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

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

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

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

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

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

 

Scaling helper methods

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

  • SCALEFONT
  • SCALESIZE
  • SCALEVALUE

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

  • UNSCALEFONT
  • UNSCALESIZE
  • UNSCALEVALUE

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

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

SCALEFONT method

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

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

SCALESIZE method

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

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

SCALEVALUE method

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

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

UNSCALEFONT method

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

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

UNSCALESIZE method

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

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

UNSCALEVALUE method

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

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

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

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

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

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

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

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

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

ctrlSize = get_Property( myCtrl, "SIZE" )

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

call set_Property_Only( myCtrl, "SIZE", ctrlSize )

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

The AUTOSCALE property

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

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

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

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