Tag Archives: WINDOW

The Case of the Extra Pixel

In the current OpenInsight 10.1 Beta program, Martyn at RevSoft UK had reported a bug about the Height property creeping up by a pixel each time he opened a form. This was traced to the point when the menu structure was parsed and created: it was actually inserting an extra pixel, so this was fixed and the form Height now stayed the same between each opening.

However, a little more testing against a form without a menu revealed another issue – in some cases a pixel was added to the Height but it didn’t creep up on each subsequent opening:

E.g. when set to a Height of 400 and saved, the form would re-open with a Height of 401, but it would stay at the same value afterwards; not the same bug as before but it did need investigating

The Height and the ClientHeight properties

As some of you will know, Windows forms are split into two areas (1):

  1. The “Nonclient area” which contains items such as the borders, menus and title bars, and
  2. The “Client area”, which is the part that contains the controls.

When an OpenInsight form definition is saved the actual Width and Height properties are not used. Instead, the ClientWidth and ClientHeight properties (i.e. the dimensions of the Client area) are saved because Windows can change the size of the Nonclient parts when a form is run on different systems with different visual styles, and this can make the form’s Client area the wrong size when created from a saved Width and Height (as we all found out many years ago when Windows XP was released). In our particular case above, when the form was saved and reopened, the ClientWidth and ClientHeight properties were correct, but the Height had gained a pixel.

E.g. When set to a Height of 400, the saved form ClientHeight was 365. When the same form was reopened the ClientHeight was still 365, but the Height was now reported as 401.

Height, ClientHeight and High DPI

I run my primary and secondary monitors at different DPI settings to ensure that scaling works correctly, and in this case, at 192 DPI (i.e. scaled to 200%), it transpired that the integer rounding used during scaling was the issue because:

  • Setting the Height to 400 resulted in a ClientHeight of 365.
  • Setting the Height to 401 also resulted in a ClientHeight of 365.
  • Setting the ClientHeight to 365 resulted in a Height of 401.

I.e. setting the ClientHeight to a specific value, and then retrieving the form’s actual height in real pixels, and then scaling it back to 96 DPI (all values in the Form designer are shown and stored at 96 DPI), gave the extra pixel. Because we don’t record the Height in the form definition we have no way of knowing that the ClientHeight was set from a value of 400 rather than 401 when the form was reopened in the designer, so we have to go with the 401. Mystery solved!

Of course, this looks odd, but it’s just an artifact of the scaling calculations. The crucial value is the ClientHeight because this is the value that is recorded and used, and this is what needs to be preserved when forms are saved and reopened. To help put your mind at ease about this, the ClientWidth and ClientHeight properties have now been exposed (for forms only) in the Form Designer, so you can be confident that the correct size is always being maintained (ClientWidth and ClientHeight are normally runtime only properties).

E.g. In the following two images (saved and reopened) you can see that the pixel height has increased, but in both cases the ClientHeight (365) is preserved and is correct:

Image of form with a saved Height property of 400
Form saved with a Height of 400
Image of reopened form with a Height property of 401
Form reopened, now with a Height of 401

Conclusion

  • Windows XP taught us many years ago that the Width and Height properties are not reliable when creating a forms as they can produce inconsistent results on different systems, so we always rely on the ClientWidth and ClientHeight properties instead.
  • Don’t be concerned if you see a slightly different Height value when you reopen a form if you’re running at a high DPI setting – the crucial property is the ClientHeight value – as long as this is consistent there is no actual problem.
  • To make sure you can monitor this yourself the ClientWidth and ClientHeight properties have been exposed in the Form Designer, and you can edit these directly if you wish.

(Note: the ClientHeight and ClientWidth properties are only exposed after builds later than the current Beta 3 release)

(1) If you are not familiar with Client and Nonclient areas in Windows GUI programming you can find out more information here).

The Case of the Jumping Dialog

We recently noticed a new bug in the IDE where a dialog that hadn’t been updated for quite some time suddenly started misbehaving: it would appear at the wrong coordinates (0,0), and then jump to the proper ones.

At first glance this this looked like a classic case of creating the dialog in a visible state, and then moving it during the CREATE event, but checking the dialog properties in the Form Designer showed that the dialog’s Visible property was “Hidden”, so this wasn’t the source of the problem.

Stepping through the CREATE event in the debugger showed that the dialog was indeed created hidden, but then became visible during an RList() call that performed a SELECT statement, ergo RList() was allowing some queued event code to execute (probably from an internal call to the Yield() procedure) and that was changing the dialog’s VISIBLE property.

Checking the other events revealed that the SIZE event contained the following code:

Call Set_Property( @Window, "REDRAW", FALSE$ )

// Move some controls around

Call Set_Property( @Window, "REDRAW", TRUE$ )

The REDRAW property works by toggling an object’s WS_VISIBLE style bit – when set to FALSE$ the style bit is removed but the object is not redrawn. When set to TRUE$ the object is marked as visible and redrawn.

So, putting all this together: creating the dialog generated a queued SIZE event, which under normal circumstances would run after the CREATE event. However, the Yield() call in RList() executed the SIZE event before the dialog was ready to be shown, and the REDRAW operation made the dialog visible before the CREATE event had moved it to the correct position.

The fix for this was to ensure that the REDRAW property wasn’t set to TRUE$ if it’s original value wasn’t TRUE$, like so:

bRedraw = Set_Property( @Window, "REDRAW", FALSE$ )

// Move some controls around

If bRedraw Then
   Call Set_Property( @Window, "REDRAW", TRUE$ )
End

Conclusion

  • Always protect calls to the REDRAW property by checking its state before you set it, and then only turn it back on again if it was set to TRUE$ originally.
  • Calling Yield() can cause events to run out of the normal sequence, and you should be aware of this when writing your application.

Methods, Events, and Documentation

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

Methods, not Events

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

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

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

So instead of:

   Call Send_Event( @Window, "WRITE" )

you would use the form’s WRITEROW method instead:

   @@Window->WriteRow( "" )

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

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

Methods, not Stored Procedures

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

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

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

Menu Designer update in v10.0.8

Version 10.0.8 has seen each of the Menu Designer tools (Form and Context) get a substantial overhaul, both to fix some bugs and also to improve their usability.  This post will provide a quick overview of what has changed.

The Context Menu Designer

ContextMenu Designer

Context Menu Designer

  • The “Item Properties” have been moved from the IDE Property Panel onto the designer itself, adjacent to the menu structure outline.  The previous layout needed far too much mouse movement between the items and their properties.
  • The ability to specify an image list for the menu has been added (this has always been supported at runtime but was not exposed via the Menu Design tools).
  • Re-added the “OI Menu” and “Windows Menu” options.
    • These are no longer global like they were in version 9.x, rather they are specific to the menu in question.
  • Added back the leading “-” symbol for items that don’t have an image as per version 9.
  • Added back the missing “F11” and “F12” Accelerator Key options
  • Improvements to the validation of item properties, e.g:
    • Better generation of default Item IDs.
    • Better checks for duplicate IDs.
    • Prevent events for POPUP item types.
    • A warning message when indenting/un-indenting items will change the parent item type (for example, indenting an item could cause the preceding ITEM to become a POPUP which would remove any existing event code from it).
  • Added “Shift-key” functionality to the buttons to control “insert before/after” operations (normal operation is “insert before”, pressing Shift changes them “insert after”), i.e:
    • Shift + Insert button for “insert after current item”.
    • Shift + Insert Separator button for “insert separator after current item”.
    • Shift + Paste button for “paste after current item”.
  • Added a full keyboard interface for the menu item structure list-box:
    • F2 (or Double-Click) to edit Item text in place.
    • Enter to insert a new item after the current item and move automatically into  “edit mode” (as per “F2” above).
      • Down arrow on the last item will insert a new item as per above.
      • Esc on a new “untouched” item will delete it.
    • Left key to shift an item and any sub-items to the left (un-indent).
    • Right key to shift an item and any sub-items to the right (indent).
    • Del key to delete items and their child items.
    • Ctrl-C to copy an item and it’s sub-items to the Windows Clipboard.
    • Ctrl-X to cut an item and it’s sub-items to the Windows Clipboard.
    • Ctrl-P to Paste items from the Windows Clipboard into the menu structure.
  • Added a context menu to the menu item structure listbox that duplicates the buttons, and adds the following operations:
    • Reset All Item IDs – processes the entire menu and changes all item IDs to their defaults based on their text and the name of their parent item.
    • Copy All – Copies all items to the Windows Clipboard – useful for duplicating menus from one form to another.
    • Delete All – Removes all items from the menu.
  • You are not asked to save the details for each item as you select items in the designer, but you will be prevented from moving to a different item if there is a validation failure.

The Form Menu Designer

Form Menu Designer

Form Menu Designer

Likewise the Form Menu designer has received the same treatment with a few additional extras:

  • A tab has been added for maintaining Event Scripts.
  • The events tab has colored indicators (orange and blue) to denote if an item has a QuickEvent or an Event Script.
  • More validation:
    • Prevent Separators being top-level items.
    • Prevent events for top-level items (unfortunately they are not supported by the Presentation Server in this version).
  • Syntax checking.
    • Syntax is automatically checked if you attempt to select another item – you will be prevented from moving to a different item if the check fails.

You should be able to catch these improvements in the next release, so please try them out and let us know how they work for you!

Tracking the SAVEWARN property

As veteran OpenInsight programmers know, the system uses a simple boolean flag (exposed as the SAVEWARN property) to determine if the contents of a data-bound form have changed.  This flag can be updated in several ways, the most common being:

  • From the LOSTFOCUS event of a control.
  • From the POSCHANGED, INSERTROW and DELETEROW events of an EDITTABLE control.
  • From setting a control’s DEFPROP property.
  • From the CLOSE event of a form when the control with focus is inspected to see if it has changed.

It is checked during the CLEAR and CLOSE events to see if it has been set and an “Unsaved Changes” warning issued to the user if so.  Most of the time this system works quite well, but (as anyone who has spent several years working with OI systems knows) sometimes it gets triggered when you least expect it, and you’re left with no clue as to why.

To help with this situation the next version of OpenInsight introduces SAVEWARN tracking, so you can see which parts of the system update the SAVEWARN property and when they actually do it. In previous versions the system updated the SAVEWARN flag directly (it’s a simple variable in the “window common area”) but this has been changed to use the Set_Property function so it can be monitored effectively from a single point.

To track SAVEWARN you have two choices:

  • Use the SYSMSG event
  • Use the System Monitor

 

Using the SYSMSG event to track SAVEWARN

Every time SAVEWARN is set a standard SYSMSG event is raised with a SAVEWARNINFO code; the system itself does nothing with this message, but it’s there for you to use if you wish.  This option is probably more suited for run-time tracing as it’s something you could add to your applications easily if you needed to.

The PS_EQUATES insert record defines the SAVEWARNINFO message number that you can intercept:

equ SYSMSG_SAVEWARNINFO$ to 21  ; // Save warn has been changed - null msg

The Auxiliary parameter passed to the  SYSMSG event contains information that describes why the SAVEWARN property was changed.

 

Using the System Monitor to track SAVEWARN

The SetDebugger() function has been updated to support a new method called “SAVEWARN” that enables SAVEWARN tracking so that changes are displayed in the System Monitor.  This option is probably more suited to development use rather than run-time.

From the System Monitor execute:

setdebugger savewarn 1

to turn on tracing, and:

setdebugger savewarn 0

to turn it off.

E.g:

SAVEWARN tracing in the System Monitor

SAVEWARN tracing in the System Monitor

Setting the SAVEWARN property

If you wish to set SAVEWARN yourself you may use the “index” parameter to pass a description for the change, so this can be picked up in any tracing scenario like so:

Call Set_Property_Only( @Window, "SAVEWARN", TRUE$, "From My Stuff" )

This description is then passed in the Auxiliary parameter of the  SYSMSG event as noted above.

 

Hopefully you will find this facility useful if you ever suffer from problems with SAVEWARN in the future.

The COMMUTERMODULE Property

As most of you will probably know, “commuter module” is the term given to a stored procedure whose main purpose is to handle event processing for a specific form.  Rather than have each individual event processed in a separate event script, quick-events are used instead to call the commuter module directly, passing it various parameters such as the name of the object firing the event, the name of the event itself, and any relevant arguments.  The commuter module then branches off to different internal subroutines to handle the event.

Following this methodology offers several important advantages:

  • Simplified code management
  • Simplified deployment
  • Improved code sharing via internal subroutines
  • Lower memory overhead (single procedure vs. multiple event scripts)

While using a commuter module like this is the recommended way of developing applications in OpenInsight, there has never been any sort of formal link between the commuter module and the form itself, which means that it is usually necessary to adopt a naming convention for this approach to work.

For example, it is common to have a commuter module with the same name as the form, or with the same name as the form and suffixed with the string “_EVENTS”.  By doing this it was easy to define quick-events to call the commuter module in the old v9 Form Designer like so:

v9 Commuter Module Quick Event

v9 Commuter Module Quick Event

The OBJ_CALL_EVENT program looks for a stored procedure with the same name as the form, or one with the same name as the form and suffixed with “_EVENTS”.  It was also possible to avoid the overhead of a lookup and call the commuter module directly by using the “@WINDOW” placeholder like this:

v9 Commuter Module Quick Event (using @WINDOW)

v9 Commuter Module Quick Event (using @WINDOW)

Or the “@WINDOW_EVENTS” placeholder like this:

v9 Commuter Module Quick Event (using @WINDOW_EVENTS)

Of course, even with this approach the form still wasn’t actually linked to it’s commuter module, rather it was only linked to “OBJ_CALL_EVENTS” or a fictitious entity called “@WINDOW”.

In version 10 we’ve added a new WINDOW property called COMMUTERMODULE, which simply contains the name of the stored procedure to use (this can be any valid name – you are no longer limited to one based on the name of the form, though we do suggest you keep this convention to help organize your application):

CommuterModule Property

CommuterModule Property

When the form is saved and compiled this stored procedure is linked to the form with a “used-by” relationship, thereby making deployment easier as you will see the link when you create your RDK definition records.

Another benefit of this is that you can quickly open your commuter module from the form by using the “View Commuter Module” button on the Form Designer toolbar:

View Commuter Module button

View Commuter Module button

Finally you can easily set your quick events to call your commuter module in the Event Designer like so:

Commuter Module QuickEvent

Commuter Module QuickEvent

Note that it uses an “@COMMUTER” placeholder – this is replaced with the contents of the COMMUTERMODULE property at runtime.

(You can still use the previous @WINDOW/_EVENTS or Obj_Call_Events methods if you wish – those options still exist.)

So, this is the first step in tightening the relationship between a form and it’s commuter module.  There is certainly scope for more integration between the two and this is something that we hope to pursue during subsequent releases.

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

The SCALEFACTOR property

As we mentioned in our last post on High-DPI, the work needed to accommodate per-monitor DPI scaling in Windows 8.1 has also created the ability to scale OpenInsight forms to an arbitrary value outside of any system DPI settings.  This new functionality is exposed via the SCALEFACTOR property described below.

SCALEFACTOR property

This WINDOW property is a dynamic array comprising four fields:

<1> ScaleFactor
<2> Minimum ScaleFactor
<3> Maximum ScaleFactor
<4> ScaleFactor Increment

<1> ScaleFactor

This is a number that specifies how much to scale the form by.  A value of 1 means that the form has no scaling applied, a value of 1.5 scales the form to one-and-a-half times its normal size and so on.

Note that the scale factor is applied after any scaling applied for system DPI.  So, if your form runs on a 144 DPI monitor (150%) and has a scalefactor of 2 applied the actual scalefactor used is 3.0 (1.5 x 2.0).

<2> Minimum ScaleFactor

This specifies the minimum value that the ScaleFactor can be set to. By default it is set to “0.1”.  This value can be set at design time. See the note on “Scaling Restrictions” below.

<3> Maximum ScaleFactor

This specifies the maximum value that the ScaleFactor can be set to. By default it is set to “5.0”.  This value can be set at design time. See the note on “Scaling Restrictions” below.

<4> ScaleFactor Increment

If this field is set to a value other than 0 it allows the ScaleFactor to be adjusted via the  Mouse-wheel /Ctrl-key combination, or with a “pinch-zoom” gesture if running under a touch screen.  The increment value controls the rate at which the form grows or shrinks.  This value can be set at design time.

Example 1: Set a form’s scale to twice its designed size while allowing the user to adjust the scalefactor by the mouse or touchscreen:

* // Note that we ignore the min and max scalefactors, leaving them at their
* // defaults.
scaleFactor = ""
scaleFactor<1> = 2    ; * // twice normal size
scaleFactor<4> = 0.1  ; * // allow mousewheel/gesture - each wheel notch
                      ; * // adjusts the scalefactor by 0.1

Example 2: Comparing OpenInsight forms with a SCALEFACTOR of 0.5 and 1.0 respectively (both running on a 144 DPI desktop with DPISCALING disabled)

Comparing SCALEFACTOR 0.5 vs 1.0

Comparing SCALEFACTOR 0.5 vs 1.0

Example 3: Comparing OpenInsight forms with a SCALEFACTOR of 1.0 and 1.7 respectively (both running on a 144 DPI desktop with DPISCALING disabled)

Comparing SCALEFACTOR 1.0 vs 1.7

Comparing SCALEFACTOR 1.0 vs 1.7

DPI Image Lists and Image Scaling

In Example 3 above note the quality of the magnifying glass glyph on the buttons in the scaled form: it is much clearer and sharper on the Search button than it is on the Split button. This is because the Search button was designed using a “DPI Image List”, which means that an array of images, along with a corresponding array of DPI values, was specified for this glyph rather than just a single image. OpenInsight scans this DPI Image List looking for the closest match it can find when performing a scaling operation.  By contrast the Split button is using a single image designed for 96 DPI and stretched to fit, resulting in a blurry appearance.

(Note: We first mentioned this functionality in the section “Supporting images under High-DPI” in our original High-DPI post).

Or course, you may also find yourself in the position of not wanting a particular image scaled, and in this case we’ve added a new property to the Image API called IMAGEAUTOSCALE.  This is a simple boolean property that controls if an image is scaled by the system during the scaling process.  It’s default value is TRUE.

(We’ve also added a similar property to other areas of the system that use images as well, so there is a GLYPHAUTOSCALE property, a SPLITGLYPHAUTOSCALE property and so on).

 

Scaling Restrictions

The minimum and maximum size that a form can be rescaled to can be restricted by the minimum and maximum window sizes as defined by the OS.  As a general rule this size is usually slightly larger than the size of the entire desktop across all monitors (See the GetSystemMetrics() Windows API function along with the indexes SM_CXMAXTRACK, SM_CXMINTRACK, SM_CYMAXTRACK, and SM_CYMINTRACK for more details).

You can, however, override this behaviour if you set the TRACKINGSIZE property for a form, specifying values large enough to handle your desired scaling range.

 * // Example - Ensure the form will actually scale to the min and max factors
 * //           we've set
 
 winSize     = get_Property( @window, "SIZE" )
 scaleFactor = get_Property( @window, "SCALEFACTOR" )
 
 trackingSize    = ""
 trackingSize<1> = winSize<3> * scaleFactor<2>
 trackingSize<2> = winSize<4> * scaleFactor<2>
 trackingSize<3> = winSize<3> * scaleFactor<3>
 trackingSize<4> = winSize<4> * scaleFactor<3>
 
 call set_Property( @window, "TRACKINGSIZE", trackingSize )

 

Scaling Interaction

In our next post we’ll take a look at the new SCALED event and discuss how to interact with the system during a scaling operation.

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

OpenInsight and High-DPI – Part 2

In a previous post we looked at OpenInsight’s new features for running on High-DPI systems, and we described how forms can properly scale themselves to take advantage of better resolutions,  thereby avoiding unnecessary blurring as the window manager tries to compensate when faced with a non-DPI-aware application:

High-DPI comparison between v9 and v10

High-DPI comparison between v9 and v10

In the example above both forms are running on a desktop set to 144 DPI (150%). The top form is running under OpenInsight v9 and, as you can see, has been stretched by the system resulting in blurry outlines, icons and text.  In contrast, the bottom form is running under OpenInsight v10 and has been scaled correctly – the checkbox image and icon are sharp and the font has been scaled to the correct point size. (A word of caution – if your own system is set to use High-DPI don’t bother viewing this image on a Chrome-based browser unless you’ve set _it_ to use High-DPI as well – generally Chrome doesn’t handle automatic High DPI scaling like IE and FF, so the image above will still appear blurry, as will this text!).

However, with the release of Windows 8.1 Microsoft have made some significant changes in this area that has also led to changes in OpenInsight’s High-DPI handling as well.  Until Windows 8.1 the DPI setting for the system was constant across all monitors and fixed during the login process – selecting another DPI setting required logging out and back in again, or even a full reboot in the case of Windows XP.  Now, with the steady increase in monitor resolutions across different form factors, Microsoft have added the ability to set the DPI per monitor, which means that forms created on one monitor may look too small or too big when moved to another monitor with a different DPI.

In order to deal with this at runtime, top-level forms are now notified by a new Windows message called WM_DPICHANGED, which is sent when either of the following happens:

  1. The DPI of the monitor a form is displayed on changes, or
  2. The form is moved between monitors that have different DPI settings.

This message is used by OpenInsight to adjust the scale of a form dynamically as required, so if a form’s DPISCALING property is TRUE you will see this happen as you drag it across monitors with different DPIs.  All the rules described in the original post still apply of course:

  • Fonts are scaled
  • Coordinates are scaled
  • DPI-aware images are selected and/or scaled

(We’ve also added a new property called DPI to the WINDOW object, which returns the DPI of the monitor that the window is currently displayed on).

The fact that scaling has moved from a static to a dynamic operation has also led to the implementation of a new OpenInsight WINDOW property called SCALEFACTOR, which allows you to set the scale of a form to an arbitrary value at runtime, regardless of any DPI setting.  We’ll take a look at this property in the next post.

In the meantime, you can find more information on Windows 8.1 per-monitor DPI scaling here:

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