вторник, 25 августа 2009 г.

DPI independent Delphi application (problem with large fonts)

When run-time DPI varies from design-time DPI you usually get a messed up GUI with Delphi.
For example, I develop at 150 DPI:
If user has the same DPI everything looks fine:
When user uses a smaller DPI, e.g., 96 DPI, everything is messed up:
When user has a larger DPI, e.g., 200 DPI, everything is messed up again:
Articles on the web suggest to use ScaleBy procedure, Scale property, or PixelsPerInch property in various combinations.
If you set TForm.Scale to true, Delphi does scale some controls for you, but not all of them. For example, it scales TButton, TEdit, TPanel, but does not scale TForm, TPageControl. There's one simple trick to fix that.
Suppose you want to put TEdit on TForm and have it look good with any DPI.
1. Set TForm.Scale property to true.
2. Put TPanel on top of TForm and make it occupy the whole form, DO NOT alter the Align property, you MUST have it set to alNone.
3. Put TEdit on top of TPanel and place it as you would place it on TForm.
4. In TForm's constructor (or OnCreate procedure) do the following:
// DPI fix begin
ClientWidth := TPanel.ClientWidth;
ClientHeight := TPanel.ClientHeight;
TPanel.Align := alClient;
// DPI fix end

Delphi scales TPanel correctly, and TForm keeps the design time size. That's why we make TForm have TPanel's size.
Then we expand TPanel to occupy the whole TForm as it was intended at design time.
That's all. Just 3 lines of code.

Now when user has 96 DPI:
When user has 200 DPI:
Source code of the example for Delphi7:

Unit1.pas:
unit Unit1;

interface

uses
Classes,
ComCtrls,
Controls,
ExtCtrls,
Forms,
StdCtrls;

type
TForm1 = class(TForm)
Panel1: TPanel;
Edit1: TEdit;
PageControl2: TPageControl;
TabSheet1: TTabSheet;
TabSheet2: TTabSheet;
Panel2: TPanel;
Edit2: TEdit;
PageControl1: TPageControl;
TabSheet3: TTabSheet;
Panel3: TPanel;
TabSheet4: TTabSheet;
Edit3: TEdit;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
// DPI fix begin
ClientWidth := Panel1.ClientWidth;
ClientHeight := Panel1.ClientHeight;
Panel1.Align := alClient;
Panel2.Align := alClient;
Panel3.Align := alClient;
// DPI fix end
end;

end.


Unit1.dfm:
object Form1: TForm1
Left = 317
Top = 190
Width = 588
Height = 339
Anchors = [akLeft, akTop, akRight, akBottom]
Caption = 'Form1'
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -17
Font.Name = 'Arial'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
PixelsPerInch = 150
TextHeight = 19
object Panel1: TPanel
Left = 0
Top = 0
Width = 580
Height = 305
BevelOuter = bvNone
TabOrder = 0
DesignSize = (
580
305)
object Edit1: TEdit
Left = 8
Top = 8
Width = 564
Height = 27
Anchors = [akLeft, akTop, akRight]
TabOrder = 0
Text = 'Edit1'
end
object PageControl2: TPageControl
Left = 8
Top = 40
Width = 561
Height = 257
ActivePage = TabSheet1
Anchors = [akLeft, akTop, akRight, akBottom]
TabOrder = 1
object TabSheet1: TTabSheet
Caption = 'TabSheet1'
object Panel2: TPanel
Left = 4
Top = 4
Width = 541
Height = 205
BevelOuter = bvNone
TabOrder = 0
DesignSize = (
541
205)
object Edit2: TEdit
Left = 8
Top = 8
Width = 525
Height = 27
Anchors = [akLeft, akTop, akRight]
TabOrder = 0
Text = 'Edit2'
end
object PageControl1: TPageControl
Left = 8
Top = 48
Width = 525
Height = 148
ActivePage = TabSheet3
Anchors = [akLeft, akTop, akRight, akBottom]
TabOrder = 1
object TabSheet3: TTabSheet
Caption = 'TabSheet1'
object Panel3: TPanel
Left = 0
Top = 0
Width = 513
Height = 113
BevelOuter = bvNone
TabOrder = 0
DesignSize = (
513
113)
object Edit3: TEdit
Left = 8
Top = 8
Width = 497
Height = 27
Anchors = [akLeft, akTop, akRight]
TabOrder = 0
Text = 'Edit3'
end
end
end
object TabSheet4: TTabSheet
Caption = 'TabSheet2'
ImageIndex = 1
end
end
end
end
object TabSheet2: TTabSheet
Caption = 'TabSheet2'
ImageIndex = 1
end
end
end
end

Note that my form is designed at 150 DPI so it will open distorted in your IDE if you use other DPI value. This article talks about run time DPI fix only :)