Watch, Follow, &
Connect with Us

For forums, blogs and more please visit our
Developer Tools Community.


Welcome, Guest
Guest Settings
Help

Thread: Best practices for high-dpi aware applications



Permlink Replies: 10 - Last Post: Mar 7, 2016 12:54 AM Last Post By: Michael Rabatsc...
Ed Dressel

Posts: 42
Registered: 10/10/99
Best practices for high-dpi aware applications
Click to report abuse...   Click to reply to this thread Reply
  Posted: Feb 29, 2016 11:30 AM
We are having problems with our application not being high-dpi aware. I thought jumping to Seattle would solve these problems, but alas, that was simplistic. For example, we make a lot of use of frames, and they don't have the Scaled property like forms, so they have to be manually configured.

For those who have cracked this egg on a fairly complex application, do you have any best practices for moving forward? Or how you did it?

Thank you,

Ed Dressel
Eric Fleming Bo...

Posts: 48
Registered: 8/11/02
Re: Best practices for high-dpi aware applications
Click to report abuse...   Click to reply to this thread Reply
  Posted: Mar 1, 2016 10:58 AM   in response to: Ed Dressel in response to: Ed Dressel
Hi Ed

Our application is fairly complex and extensively use Frames and we had to make it high-DPI aware... After a lot of research I found some solutions and was able to correctly scale the frames

The main issue is with frames that are created manually, if you just design a form and put a frame inside that form at design-time, your application will scale correctly but that is not usually the case so, I came up with this code:

procedure ScaleFrameFrom96DPI(Frame: TFrame);
var
  FormParent: TForm;
  TextHeight: Integer;
 
  // Retrieve the form parent
  function GetFormParent: TForm;
  var
    Parent: TWinControl;
    Owner: TComponent;
  begin
 
    // Default result
    Result := nil;
 
    // Get first parent
    Parent := Frame.Parent;
 
    // Loop to find form parent
    while Assigned(Parent) do
    begin
      if Parent is TForm then
        Exit(TForm(Parent))
      else
        Parent := Parent.Parent;
    end;
 
    // Could not find in parent, try on owner
    Owner := Frame.Owner;
 
    // Loop to find form owner
    while Assigned(Owner) do
    begin
      if Owner is TForm then
        Exit(TForm(Owner))
      else
        Owner := Owner.Owner;
    end;
 
  end;
begin
 
  // Get the form parent
  FormParent := GetFormParent;
 
  // Check if scale has already been initialized
  if (ScaleM = 0) or (ScaleD = 0) then
  begin
 
    // Check if we got the parent form
    if Assigned(FormParent) then
    begin
 
      // Scale by form scale
      if GetFormTextHeight(FormParent, TextHeight) then
      begin
        ScaleM := FormParent.Canvas.TextHeight('0');
        ScaleD := TextHeight;
        Frame.ScaleBy(ScaleM, ScaleD);
      end
 
      // Scale by DPI
      else
        Frame.ScaleBy(Screen.PixelsPerInch, 96);
 
    end
 
    // Scale by DPI
    else
      Frame.ScaleBy(Screen.PixelsPerInch, 96);
 
  end
 
  // Scale by stored scale value
  else
    Frame.ScaleBy(ScaleM, ScaleD);
 
  // Set the parent font height
  SetParentFont(Frame);
 
end;


That routine will basically scale the frame using the same scaling as used by the parent form, that works perfectly for me but I don't know if that would be a general code that would work for anybody... Also to use this code you must make sure your frames/forms are designed in 96DPI

The only limitation with my method is the way you have to call it:

  F := TMyFrame.Create(Owner);
  F.Parent := pnlFrames;
  ScaleFrameFrom96DPI(F); // Must scale before align and after setting parent
  F.Align := alClient;


Basically you have to call ScaleFrameFrom96DPI after setting the parent, but before setting any alignment..

I don't recall all the issues I ran into when working with scaling frames, but this pretty much works, although I don't know if that code is a best practice (probably not...)
Eric Fleming Bo...

Posts: 48
Registered: 8/11/02
Re: Best practices for high-dpi aware applications
Click to report abuse...   Click to reply to this thread Reply
  Posted: Mar 1, 2016 11:08 AM   in response to: Eric Fleming Bo... in response to: Eric Fleming Bo...
Ed

Forgot to add a few more code, the code that actually reads the stored DPI in the form:

////////////////////////////////////////////////////////////////////////////////
// Name: GetFormTextHeight
//
// Description:
//   Read the hidden property "TextHeight" from a form, in order to properly
//   scale forms and frames
//
// Author: Éric Fleming Bonilha
//
// Parameters List:
//   [IN]  Form       - Form to read
//   [OUT] TextHeight - Output variable to receive the TextHeight
//
// Returns:
//   [TRUE]  - Value was read
//   [FALSE] - Error reading the value
////////////////////////////////////////////////////////////////////////////////
function GetFormTextHeight(Form: TForm; var TextHeight: Integer): Boolean;
var
  HRsrc: THandle;
  HInst: THandle;
  ResourceStream: TResourceStream;
  StrStream: TStringStream;
  sTextHeight: string;
begin
 
  // Default result
  Result := FALSE;
 
  // Locate the HInstance for the class
  HInst := FindResourceHInstance(FindClassHInstance(Form.ClassType));
  if HInst = 0 then
    HInst := HInstance;
 
  // Locate the resource
  HRsrc := FindResource(HInst, PChar(Form.ClassName), PChar(RT_RCDATA));
  if HRsrc = 0 then
    Exit;
 
  // Create stream to read the resource
  ResourceStream := TResourceStream.Create(HInst, Form.ClassName, RT_RCDATA);
  try
 
    // Create the string stream to have DFM values in text
    StrStream := TStringStream.Create;
    try
 
      // Read object binary to text
      ObjectBinaryToText(ResourceStream, StrStream);
 
      // Try to read the TextHeight property
      if ExtractParameterValueFromParametersList(StrStream.DataString, '=', 'TextHeight', sTextHeight) then
        Result := TryStrToInt(sTextHeight, TextHeight);
 
    finally
      StrStream.Free;
    end;
 
  finally
    ResourceStream.Free;
  end;
 
end;


I also have these private variables in the unit, that is used:
  ScaleM: Integer = 0;
  ScaleD: Integer = 0;


When I have to correctly position a control in a scaled form at run-time I also use this function:"

////////////////////////////////////////////////////////////////////////////////
// Name: ScaledFrom96DPI
//
// Description:
//   Return the scaled value depending on fonts DPI
//
// Author: Éric Fleming Bonilha
//
// Parameters List:
//   [IN] Form  - Form to check scale
//   [IN] Value - Value
//
// Returns:
//   None
////////////////////////////////////////////////////////////////////////////////
function ScaledFrom96DPI(Value: Integer): Integer;
begin
 
  // Scale by pre-stored scale value
  if (ScaleM <> 0) and (ScaleD <> 0) then
    Result := MulDiv(Value, ScaleM, ScaleD)
 
  // Scale by DPI
  else
    Result := MulDiv(Value, Screen.PixelsPerInch, 96);
 
end;
Eric Fleming Bo...

Posts: 48
Registered: 8/11/02
Re: Best practices for high-dpi aware applications
Click to report abuse...   Click to reply to this thread Reply
  Posted: Mar 1, 2016 11:13 AM   in response to: Eric Fleming Bo... in response to: Eric Fleming Bo...
Just as a background, Delphi stores a property called TextHeight in the form DFM, that TextHeight is used to calculate scaling, but you cannot access it from runtime, that is why I created that "hack" routine, to read the stored DFM directly from .exe file and retrieve the TextHeight property.

If you just scale using 96DPI as base, your scaled Frame will not have the same scaling as your form because delphi uses the size of the text instead of actual DPI to scale forms... It is kinda weird... and took a lot of time to figure that out
Roy Lambert

Posts: 1,063
Registered: 8/7/01
Re: Best practices for high-dpi aware applications
Click to report abuse...   Click to reply to this thread Reply
  Posted: Mar 1, 2016 11:46 PM   in response to: Eric Fleming Bo... in response to: Eric Fleming Bo...
Eric

In your research did you find anything that tells Windows to ignore high-dpi and scaling for the application.

The reason I ask is I have an application that uses a lot of controls and is "heavily" formatted. Putting it on a machine with scaling set to 125% or 150% causes it to go from a well presented application to a "where the hell is the rest of the form" one.

Roy Lambert

Michael Rabatsc...

Posts: 125
Registered: 1/22/07
Re: Best practices for high-dpi aware applications
Click to report abuse...   Click to reply to this thread Reply
  Posted: Mar 2, 2016 1:29 AM   in response to: Roy Lambert in response to: Roy Lambert
Am 02.03.2016 um 08:46 schrieb Roy Lambert:
Eric

In your research did you find anything that tells Windows to ignore high-dpi and scaling for the application.

The reason I ask is I have an application that uses a lot of controls and is "heavily" formatted.
Putting it on a machine with scaling set to 125% or 150% causes it to

go from a well presented application to a "where the hell is the rest of
the form" one.

That usually happens in non high dpi aware code but the applications
manifest (or the SetProcessDpiAwareness api was used) is setup for high
dpi awareness... If the application is not
high dpi aware then Windows would tell your application that it
runs in a 96dpi environment and scales the application itself making it
a little blury...
You can easily check that by showing the screen.width value and check
that with the real screen resolution (or also check the
Screen.PixelsPerInch) value.

kind regards
Mike
Roy Lambert

Posts: 1,063
Registered: 8/7/01
Re: Best practices for high-dpi aware applications
Click to report abuse...   Click to reply to this thread Reply
  Posted: Mar 2, 2016 1:56 AM   in response to: Michael Rabatsc... in response to: Michael Rabatsc...
Michael

Roy Lambert

Michael Rabatscher <m dot rabatscher at gmail dot com> wrote on Wed, 2 Mar 2016 01:29:05 -0800

Am 02.03.2016 um 08:46 schrieb Roy Lambert:
Eric

In your research did you find anything that tells Windows to ignore high-dpi and scaling for the application.

The reason I ask is I have an application that uses a lot of controls and is "heavily" formatted.
Putting it on a machine with scaling set to 125% or 150% causes it to

go from a well presented application to a "where the hell is the rest of
the form" one.

That usually happens in non high dpi aware code but the applications
manifest (or the SetProcessDpiAwareness api was used) is setup for high
dpi awareness... If the application is not
high dpi aware then Windows would tell your application that it
runs in a 96dpi environment and scales the application itself making it
a little blury...
You can easily check that by showing the screen.width value and check
that with the real screen resolution (or also check the
Screen.PixelsPerInch) value.

So how do I stop it?

Roy
Michael Rabatsc...

Posts: 125
Registered: 1/22/07
Re: Best practices for high-dpi aware applications
Click to report abuse...   Click to reply to this thread Reply
  Posted: Mar 2, 2016 2:47 AM   in response to: Roy Lambert in response to: Roy Lambert
So how do I stop it?

I don't know which Delphi version you are using...
In recent Versions there is an optin for high dpi awareness
in the project options (application or version info I guess)...

As for Delphi 2010 I decided to use the high dpi awareness
api for that e.g. to enable or disable it via a registry entry.

kind regards
Mike


Roy
Michael Rabatsc...

Posts: 125
Registered: 1/22/07
Re: Best practices for high-dpi aware applications
Click to report abuse...   Click to reply to this thread Reply
  Posted: Mar 2, 2016 1:34 AM   in response to: Ed Dressel in response to: Ed Dressel
Am 29.02.2016 um 20:30 schrieb Ed Dressel:
We are having problems with our application not being high-dpi aware.
I thought jumping
to Seattle would solve these problems, but alas, that was simplistic.
For example, we make a lot of
use of frames, and they don't have the Scaled property like forms, so
they have to be manually configured.

For those who have cracked this egg on a fairly complex application,
do you have any best practices for moving forward? Or how you did it?

Thank you,

Ed Dressel
Check out

http://www.helpandmanual.com/downloads_delphi.html#sthash.jW5CeaUr.dpuf

that's quite a good start.

For frames I needed to insert following procedure to each frame (I use
Delphi 2010 by the way so I don't know if it's still a problem...):

procedure THighDPIFrame.Loaded;
begin
inherited;

if not Assigned(Parent) then
ChangeScale(Screen.PixelsPerInch, cDesignDPI);
end;

so... if you manage to put that piece of code into a base class your done...

I also needed to manually scale image lists and grids (row heights and
such..

kind regards
Mike
Eric Fleming Bo...

Posts: 48
Registered: 8/11/02
Re: Best practices for high-dpi aware applications
Click to report abuse...   Click to reply to this thread Reply
  Posted: Mar 2, 2016 11:29 AM   in response to: Michael Rabatsc... in response to: Michael Rabatsc...
if not Assigned(Parent) then
ChangeScale(Screen.PixelsPerInch, cDesignDPI);
end;

Mike
The only problem when scaling using the design DPI (Lets say 96) is that the scaled frame will not match the parent form scaling because the parent form is scaled automatically but it does not scale by using the DPI, it scale by using TextHeight property that is saved in the form (at design tim) compared to the new TextHeight at runtime. So Delphi basically scales using the difference between the font size as scale factor instead of difference in DPI which by the end of the day scales the frame using different scaling from parent form which makes it a little bit different from parent form, like the text and control positions will not be totally correct and this is why I read the TextHeight from parent form in the routines I presented
Michael Rabatsc...

Posts: 125
Registered: 1/22/07
Re: Best practices for high-dpi aware applications
Click to report abuse...   Click to reply to this thread Reply
  Posted: Mar 7, 2016 12:54 AM   in response to: Eric Fleming Bo... in response to: Eric Fleming Bo...
Am 02.03.2016 um 20:29 schrieb Eric Fleming Bonilha:
if not Assigned(Parent) then
ChangeScale(Screen.PixelsPerInch, cDesignDPI);
end;

Mike
The only problem when scaling using the design DPI (Lets say 96) is that the scaled frame will not match the parent form scaling because the parent form is scaled automatically but it does not scale by using the DPI, it scale by using TextHeight property that is saved in the form (at design tim) compared to the new TextHeight at runtime. So Delphi basically scales using the difference between the font size as scale factor instead of difference in DPI which by the end of the day scales the frame using diff
erent scaling from parent form which makes it a little bit different from parent form, like the text and control positions will not be totally correct and this is why I read the TextHeight from parent form in the routines I presented

Right - the Delphi scaling is based on textheight... so..

one way to get the proper scaling is:

function THighDpIForm.DelphiScaleFact: double;
begin
Result := FontHeightAtDPi(Screen.PixelsPerInch, self.Font.Size)/
FontHeightAtDPI(fOrigDPI, self.Font.Size);
end;

function THighDpIForm.FontHeightAtDpi(aDPI, aFontSize: integer): integer;
var tmpCanvas : TCanvas;
hdl : HDC;
begin
hdl:= GetDC(0);
tmpCanvas := TCanvas.Create;
try
tmpCanvas.Handle := hdl;
tmpCanvas.Font.Assign(self.Font);
tmpCanvas.Font.PixelsPerInch := aDPI;
tmpCanvas.Font.Size := aFontSize;
Result := tmpCanvas.TextHeight('0');
finally
tmpCanvas.Free;
end;

ReleaseDC(0, hdl);
end;

// handles WM_DPICHANGED -> shows an example how Delphi would calculate
// the scaling factor. Note this is some code I wrote for
// our internal handlers so forget about the fxyz variables
procedure THighDpIForm.WMDPIChanged(var Message: TMessage);
begin
inherited;

fOrigDPI := cDesignDPI;

fDelphiScaleFact := DelphiScaleFact;

fOrigDPI := LoWord(Message.WParam);
ChangeScale(FontHeightAtDPi(LoWord(Message.WParam), self.Font.Size),
FontHeightAtDPI(self.PixelsPerInch, self.Font.Size));
end;

Legend
Helpful Answer (5 pts)
Correct Answer (10 pts)

Server Response from: ETNAJIVE02