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:
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:
- The original X DPI value
- The original Y DPI value
- The original SCALEFACTOR value
- The new X DPI value
- The new Y DPI value
- 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:
- 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.
- 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.
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):
The “SCALE” methods perform a DIPs to Pixel conversion.
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).
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 )
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 )
This method takes an unscaled value and scales it relative to the current scale factor of the form.
scaledVal = exec_Method( @window, "SCALEVALUE", origVal )
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 )
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 )
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).