Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

How to do DPI in Delphi?

6,078 views
Skip to first unread message

Ian Boyd

unread,
Jan 2, 2008, 3:40:33 PM1/2/08
to
i know Delphi forms can already scale themselves at runtime if the DPI on
the system is different than the DPI on the designed system. It's fine when
you let the streaming system perform the perform the positioning and
scaling. But then i have elements on the form that are positioned, sized,
and drawn myself. i need to perform similar scaling on these elements.

First first attempt involved using the PixelsPerInch property of a form. The
help says that PixelsPerInch is used by the form to do scaling when on a
different DPI system (and ScaleBy is set to true). i thought i could do
something like:

Button1.Left := MulDiv(THE_BUTTON_WIDHT, Screen.PixelsPerInch,
Self.PixelsPerInch);

i assumed that the forms PixelsPerInch (e.g. Self.PixelsPerInch) was a
property that was streamed with the form, and loaded at runtime. So if the
form was originally designed on a 96dpi system to have the button at 167px,
and was now running on a 108dpi system, the math would be

Button1.Left := MulDiv(167, 108, 96); //188px

But it turns out that when you read a form's PixelsPerInch property it is
not saved at 96dpi, but is actually 108dpi. So the math turns out to be:

Button1.Left := MulDiv(167, 108, 108); //167px


But that's not possible. Without the reference of the form's designed dpi,
how is Delphi's streaming system able to know what it needs to scale the
form by? Whatever mechanism that it is using, i can then use that same thing
to calculate my own sizings. Turns out that the documentation is lying, the
PixelsPerInch property is not used to perform any scaling or position of any
elements on the form.

Instead, the Delphi form streamer writes out a hidden value (TextHeight) to
the DFM. This is height in pixels of the the number '0' in the form's font.
This is the benchmark that the entire form is scaled by, not PixelsPerInch.
Problem is that TextHeight value is not a property of the form, it's stored
in the DFM by the streaming system, and read back just so it can be used to
scale everything on the form. This means that i have no access to the value.

i cannot access the PixelsPerInch of a form as it was designed, nor do i
have access to the font's height (in pixels) when it was designed. What then
do i use as my base scaling value? i could hard-code 96dpi, but then all
developers (even me) must be running at 96dpi.

i know nobody else honors dpi settings in Delphi, but how *would* you do it
if you had to? Everything would have been perfect if the designed
PixelsPerInch was available.

A future post will be asking why "DpiAware" Delphi application running on
Vista do not scale properly. i know nobody's done it because nobody has
asked about it.

NOTE: Never use SetProcessDPIAware, instead manifest your application to be
high dpi aware using:

<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"
xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" >
<asmv3:application>
<asmv3:windowsSettings
xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
<dpiAware>true</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
</assembly>

See "Why SetProcessDPIAware shouldn't exist" at
http://jamesfinnigan.spaces.live.com/blog/cns!9062539B2F0077D6!395.entry
for more information.


Ian Boyd

unread,
Jan 2, 2008, 8:53:04 PM1/2/08
to
Turns out that you perform scaling based on pixels per inch

Button1.Left := MulDiv(DESIGNED_LEFT, PixelsPerInch, 96);

that's because Delphi's form scaling routine when serializing it based on
font size (in pixels). There is a huge rounding error introduced by sizing
based on font sizes.

For example:

96dpi: 8pt = 11 px (10.666px)
108dpi: 8pt = 12 px (12.000px)


So if you had an item that had to be positioned at 250px (based on 96dpi
form) then the two conversion mechanisms are:

Method 1: Scale using DPI differences:
250px * (108dpi / 96dpi) = 281px (281.25px)

Method 2: Scale using font size differences (dfm streaming mechanism)
250px * (12px / 11px) = 273px (272.7272 px)

The difference between these two methods introduces a large error because of
the rounding of font pixel sizes.


So if i want my Delphi applications to be DPI compatible, i either need
access to the form's secret TextHeight value, or i need to go into every
form, turn off ScaleBy, and manually call ScaleBy(PixelsPerInch, 96) on
every form create.


It's no wonder nobody handles dpi settings corectly, and why in Vista MS
gave up and just stretches everyone's forms for them.

Ian Boyd

unread,
Jan 2, 2008, 10:58:32 PM1/2/08
to
If you let Vista's dpi scaling to scale a non dpi aware Delphi application,
the form is sized according to the rules of dpi. (e.g. scale the form by the
ratio of current dpi to 96 dpi)

For example, i had a form that had a ClientWidth of 400px (at 96dpi). When
Vista is running at 108dpi, the form is scaled (by Vista) to 450px.

400px * (108dpi / 96dpi) = 450px


On the other hand if i manifest the application to be dpi aware (which tells
Vista not to scale the form for me, my application will do it itself),
Delphi uses it's own scaling algorithm (based on highly error prone font
sizes) and gives a form client width of 431px (which doesn't actually add up
if you do the math):

GetTextHeight('0') = 13px (at 96dpi)
GetTextHeight('0') = 15px (at 108dpi)

400px * (15px / 13px) = 462px (461.53px)

As opposed to an actual ClientWidth of 431px.


Point being Delphi's scaling algorithm is so off the charts wrong, how does
anyone else do it?

Ian Boyd

unread,
Jan 3, 2008, 9:04:04 AM1/3/08
to
> Turns out that you perform scaling based on pixels per inch

Turns out that you *cannot* perform scaling based on pixels per inch


Ian Boyd

unread,
Jan 3, 2008, 10:25:21 AM1/3/08
to
If a form is designed by a developer running at 96 dpi, and then opened by a
developer running 108dpi, the form is not rescaled in the DFM. The only
change that happens in the DFM is

Before:
PixelsPerInch = 96
After
PixelsPerInch = 108

Then when the 108dpi developer goes to run the application everything is too
small because the DFM streaming system thinks that the form was designed at
108dpi.

So then the 108dpi developer has to reset the PixelsPerInch property of the
form back to 96dpi. This means that he has to design a form at 96dpi when
his machine is running 108dpi.

Ian Boyd

unread,
Jan 3, 2008, 10:54:58 AM1/3/08
to
i have two panels on a form. Both are 21 pixels high before scaling by
(108/96). After scaling one is 23 pixels high, the other is 24 pixels high.
Or to make it easier to read at a glance

Before Scaling
Panel1.Height = 21
Panel2.Height = 21

After Scaling
Panel1.Height = 23
Panel2.Height = 24

This is because rather than scaling the height of a control, they scale the
bottom of a control:

H := MulDiv(FTop + FHeight, M, D) - Y

Which in the one case is
H := MulDiv(220 + 21, 108, 96) - 248
= 271.125 - 248
= 271 - 248
= 23
and in the other
H := MulDiv(250 + 21, 108, 96) - 281
= 304.875 - 281
= 305 - 281
= 24


Which on my particular form, the difference is glaring - which is how i
noticed it.


BTW, there's a bug in the D5 scaling algorithm for height. Fortunatly it's
never come up because nobody has ever dared to touch the scaling Flags
(which default to all set):

if (sfWidth in Flags) and not (csFixedWidth in ControlStyle) then
if sfLeft in Flags then
W := MulDiv(FLeft + FWidth, M, D) - X
else
W := MulDiv(FWidth, M, D)
else
W := FWidth;

if (sfHeight in Flags) and not (csFixedHeight in ControlStyle) then
if sfHeight in Flags then <------- here, should be sfTop
H := MulDiv(FTop + FHeight, M, D) - Y
else
H := MulDiv(FTop, M, D ) <-------and here, should be FHeight
else
H := FHeight;


No wonder MS had to forget trying to get applications to behave nicely under
high dpi by just lying to the application and doing the scaling itself. It's
not even the developer's fault, it's the development environment that the
developer was using.


Ian Boyd

unread,
Jan 3, 2008, 1:29:41 PM1/3/08
to
i have an 8pt font on an existing designed form (at 96dpi). When the form is
opened at designed time on an 108dpi machine, the 8pt font becomes 7pt.
(Note: no controls are resized or repositioned)

i'm sure this is due to the fact that the 8pt font is actually 11px. But an
11px font at 108dpi is actually 7pt:

11px / 108dpi * 72pt/in = 7.33pt ==> 7pt

What makes no sense is that the only thing being adjusted on the form is the
font point sizes. No control is resized or repositioned.

Which makes no sense.


Peter Below (TeamB)

unread,
Jan 4, 2008, 3:49:20 AM1/4/08
to
Ian Boyd wrote:

> What makes no sense is that the only thing being adjusted on the form
> is the font point sizes. No control is resized or repositioned.

The problem is that the DFM file stores a fonts size on pixels, not in
points. The points size is calculated from the pixel size and the
PixelsPerInch value.

--
Peter Below (TeamB)
Don't be a vampire (http://slash7.com/pages/vampires),
use the newsgroup archives :
http://www.tamaracka.com/search.htm
http://groups.google.com

Ian Boyd

unread,
Jan 4, 2008, 9:42:08 AM1/4/08
to
>> What makes no sense is that the only thing being adjusted on the form
>> is the font point sizes. No control is resized or repositioned.
>
> The problem is that the DFM file stores a fonts size on pixels, not in
> points. The points size is calculated from the pixel size and the
> PixelsPerInch value.

You'd think that an 8pt font should be 8pt no matter what. And on a higher
dpi system it happens to take up more pixels.


Ian Boyd

unread,
Jan 4, 2008, 9:53:23 AM1/4/08
to
The single biggest problem that makes it very very difficult to honor dpi
settings in Delphi apps, is that Delphi will scale forms for you, but using
the wrong values. It scales forms by:

CurrentHeightOfZeroCharacter / OriginallyDesignedHeightOfZeroCharacter

rather than

CurrentDpiSetting / DesignedDpiSetting

This leads to a scaling fraction that you cannot recover, cannot reproduce
for your own calculations, and is also wrong. So the idea is to just live
with it as many forms as possible since it is pretty good. Then only on
forms that contain hard-coded pixel values must you disable the built-in
scaling and do it yourself.


Ian Boyd

unread,
Jan 4, 2008, 9:47:42 AM1/4/08
to
The best solution i've come up with so far is to not do anything on a form
as long as you don't have to mess with sizes or positions (in pixels) at
runtume. This includes:
control placement
control sizes
column widths
MoveTo, LineTo
stretch drawing
column widths
combobox item heights
etc

If you *do* have to do any work in pixels, then you have to modify all pixel
values at runtime to be:
pixels := MulDiv(pixels, Screen.Resolution, 96);

NOTE: substitute the dpi you were working at when you arrived at the
hard-coded "pixels" value.

You must then also disable the form's Scaled property, and during OnCreate
of your form you must manually call:
Self.ScaleBy(Screen.Resolution, 96);

NOTE: again substitute the dpi you were working at when you designed the
form.


Roy Lambert

unread,
Jan 4, 2008, 10:42:40 AM1/4/08
to
Ian


Have you tried anything like ElasticForm?

Roy Lambert

Ian Boyd

unread,
Jan 4, 2008, 11:31:23 AM1/4/08
to
> Have you tried anything like ElasticForm?

On wait, ElasticForm. Singular

http://home.flash.net/~qsystems/ElasticFormIndex.htm

No, and i wouldn't be able to explain why i want to spend $50 for a feature
that nobody except i will use.


Ian Boyd

unread,
Jan 4, 2008, 11:26:28 AM1/4/08
to
Oh, and you have to have to have to turn off the form's AutoScroll property.

With AutoScroll enabled, the form's size is saved using it's Width and
Height. With AutoScroll disabled, the form's size is saved using it's
ClientWidth and ClientHeight.


There is a non-dpi reason to always have AutoScroll off. In the transition
from Windows9x/2000 to XP/Vista, the Windows caption bar took up more space.
If the caption takes up more space, but the form is saved using a fixed
overall height, there is less space available for content. This pushes items
off the bottom of some forms (making a scrollbar appear.) And because a
scrollbar is on the right, there is now less horizontal space available for
content, and a horizontal scrollbar appears.

You can always pick out a Delphi application that was designed before XP
because things don't fit anymore.


Ian Boyd

unread,
Jan 4, 2008, 11:28:56 AM1/4/08
to
> Have you tried anything like ElasticForm?

i've never heard of it. And looks like nobody else has, with only 3 google
search results.

http://www.google.com/search?q=elasticforms

Roy Lambert

unread,
Jan 5, 2008, 4:50:41 AM1/5/08
to
Ian


How about you've already spent about $100 posting here <g>

Roy Lambert

Ian Boyd

unread,
Jan 5, 2008, 7:53:51 AM1/5/08
to
> How about you've already spent about $100 posting here <g>

Touche!

The software also seems to solve problems that i don't want solving -
stretching everything on a form as i resize it.

Roy Lambert

unread,
Jan 5, 2008, 11:03:48 AM1/5/08
to
Ian


>The software also seems to solve problems that i don't want solving -
>stretching everything on a form as i resize it.

Off the top of my head I can't see a way round that. You could try emailing Claudia (if she's back from working in Europe she should be pretty responsive). If I get a gap where I don't mind screwing my display I'll have a play around with ElasticForm and see if I can do anything. One thought does occur you can all components to an exclude list so only your own get resized.

Roy Lambert

Ian Boyd

unread,
Jan 8, 2008, 9:38:41 AM1/8/08
to
Based on the wealth of information, i take it that nobody tries to deal with
high dpi display's in Delphi.


John Herbster

unread,
Jan 8, 2008, 12:41:15 PM1/8/08
to

"Ian Boyd" <ian.borla...@avatopia.com> wrote

> Based on the wealth of information, i take it that nobody tries to deal with high dpi display's in Delphi.

Ian,

I have read a good bit of such information dealing with different DPI displays, particularly posts in these groups from Peter Below. Based on what I read, when I have a problem reported to me with my code, I am able to easily add a procedure method called FormFixUp to my code that makes all corrections on an ad hoc basis at runtime. The amazing thing to me, is that once fixed they stay fixed.

However, I have not had good luck solving the setting of the initial position of programs in multi-monitor systems; but this is another story.

Rgds, JohnH

Ian Boyd

unread,
Jan 8, 2008, 2:28:07 PM1/8/08
to
Since Delphi default scaling method when creating a form is
incorrect, and
not reproducable

it needs to be disabled. It is disabled by setting the Scaled property of
the form to False.

This then allows (and requires) that during the form's constructor you must
scale the form yourself.

procedure TForm1.FormCreate(Sender: TObject);
begin
ScaleBy(PixelsPerInch, 96);
end;

Note the form is assumed to be designed at 96dpi. This is true in a
development house where more than one developer is working on some software.
With the form no longer using some scaling when opened in the IDE, we need
to pick a dpi that the form was designed in. Since most people will be
using 96dpi, we'll use that. (If i were to design the form for my computer's
108dpi, then the unScaled form on another developer's computer would look
too big.)

The downside of this is that the Scaled property defaults to True. This
means that if you open the form in your high-dpi IDE, the form will be
scaled to 108dpi before you get a chance to turn off the Scaled property!

In order to get around this, you need to manually edit the (text) DFM file,
adding the Scaled property:

object Form1: TForm1
...
PixelsPerInch = 96
Scaled = False <-- add
TextHeight = 13
...
end

This will let you open the form in the IDE without having Delphi apply it's
incorrect scaling.

This is so much of a nightmare, and i won't be doing it much longer.

Why in the name of god Borland didn't you either
a) scale by dpi
b) expose the designed DPI/TextHeight
c) both a & b

and save anyone a hell of a lot of work?


Ian Boyd

unread,
Jan 23, 2008, 2:23:00 PM1/23/08
to
Well i've given up. i've tried for 3 weeks to find a workable solution -
there was even a nightmare involved.

i'd rather not, but i'm going to have to settle for having 2nd-class
applications.


Virgo Pärna

unread,
Jan 24, 2008, 3:23:20 AM1/24/08
to

I was busy with similar problem recently. I needed ability to resize
forms for customer, who had changed on Windows Display properties under
Appearance to use large fonts (Terminal service, so dpi changing was not
suitable). And our application had scaling disabled anyway. Things were
easier, because we use our own base form class for all forms. But in
most forms we were using specified font sizes - not windows default. In
doing scaling we discovered, that at least under Delphi 5 it didn't
handle at all controls anchored to right/bottom. So I wrote custom
TControl, TWinControl, TCustomForm scaling functions using Borlands
initial code as base.
And I hardcoded initial DPI as 96 and scaling is enabled manually for
specific size (since systems actual DPI is same, it's only way anyway).
I seems to be mostly working now - so it's possible to make it work.

--
Virgo Pärna
virgo...@mail.ee

Ian Boyd

unread,
Jan 25, 2008, 5:00:28 PM1/25/08
to
It's sucky when i want to run at high dpi when developing, but no other
developers do. Then the forms need Scaled on in order to look proper when
they open them in their IDE. i never considered writing my own ScaleBy
method, that might do it.

Of course, if i wanted to really do it right, i've have a ScaleByX and
ScaleByY, since fonts can have different aspect ratios. We can all agree
that you cannot use Scaled, since that math is not reproducable. And you
can't use ScaleBy since it gives incorrect results.


You might be interested in a function that recursivly goes through all
controls on a form and "Standardizes" it's font. Any font that is
MS Sans Serif (the Delphi default)
Tahoma (the Windows 2000/XP default, and the Vista default if you don't
support Vista)
MS Shell Dlg (the Win9x internationalization font)
MS Shell Dlg 2 (the 2000/XP/Vista internationalization font)

and 8pt, will get converted to the current user's preference font face and
point. Which is by default:
2000/XP: Tahoma 8pt
Vista: Segoe UI 9pt

Oh, and it also forces ClearType on.


procedure StandardizeFont(AControl: TControl; ForceClearType: Boolean=False;
FontName: string=''; FontSize: Integer=0);
const
CLEARTYPE_QUALITY = 5;
var
i: Integer;
RunComponent: TComponent;
AControlFont: TFont;
// ncm: TNonClientMetrics;
CanChangeName: Boolean;
CanChangeSize: Boolean;
lf: TLogFont;
begin
if not Assigned(AControl) then
Exit;

{$IFDEF ForceClearType}
ForceClearType := True;
{$ELSE}
if g_ForceClearType then
ForceClearType := True;
{$ENDIF}

{
Font size of 0 means get system default.
Font size of -1 means don't change it
}
if FontSize = 0 then
begin
{
The non-client metrics are things are not your application
CaptionFont,
SmallCaptionFont,
MenuFont,
StatusFont,
MessageBox font

ZeroMemory(@ncm, SizeOf(ncm));
ncm.cbSize := SizeOf(ncm);
if SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, @ncm, 0) then
begin
FontName := PChar(Addr(ncm.lfMessageFont.lfFaceName[0]));
FontSize := ncm.lfMessageFont.lfHeight;
}

ZeroMemory(@lf, SizeOf(lf));
if SystemParametersInfo(SPI_GETICONTITLELOGFONT, SizeOf(lf), @lf, 0) then
begin
FontName := PChar(Addr(lf.lfFaceName[0]));

FontSize := lf.lfHeight;
// FontSize := -1; //Our forms are not designed for font sizes other than
8. Let Delphi's dpi scaling handle that
// if ForceClearType then
// ncm.lfMessageFont.lfQuality := CLEARTYPE_QUALITY;
end
else
begin
FontName := 'MS Shell Dlg 2';
FontSize := -1; //we can't get it, stop trying
end;
end;

for i := 0 to AControl.ComponentCount-1 do
begin
RunComponent := AControl.Components[i];
if RunComponent is TControl then
StandardizeFont(TControl(RunComponent), ForceClearType, FontName,
FontSize);
end;

if AControl is TForm then
AControlFont := TForm(AControl).Font
else if AControl is TLabel then
AControlFont := TLabel(AControl).Font
else if AControl is TEdit then
AControlFont := TEdit(AControl).Font
else if AControl is TMemo then
AControlFont := TMemo(AControl).Font
else if AControl is TButton then
AControlFont := TButton(AControl).Font
else if AControl is TCheckBox then
AControlFont := TCheckBox(AControl).Font
else if AControl is TRadioButton then
AControlFont := TRadioButton(AControl).Font
else if AControl is TRadioButton then
AControlFont := TRadioButton(AControl).Font
else if AControl is TListBox then
AControlFont := TListBox(AControl).Font
else if AControl is TComboBox then
AControlFont := TComboBox(AControl).Font
else if AControl is TGroupBox then
AControlFont := TGroupBox(AControl).Font
else if AControl is TRadioGroup then
AControlFont := TRadioGroup(AControl).Font
else if AControl is TPanel then
AControlFont := TPanel(AControl).Font

{ Additional }
else if AControl is TBitBtn then
AControlFont := TBitBtn(AControl).Font
else if AControl is TSpeedButton then
AControlFont := TSpeedButton(AControl).Font
//maskedit
//stringgrid
//Checklistbox
else if AControl is TStaticText then
AControlFont := TStaticText(AControl).Font

{ Win32 }
else if AControl is TTabControl then
AControlFont := TTabControl(AControl).Font
else if AControl is TPageControl then
AControlFont := TPageControl(AControl).Font
else if AControl is TDateTimePicker then
AControlFont := TDateTimePicker(AControl).Font
//MonthCalendar
else if AControl is TTreeView then
AControlFont := TTreeView(AControl).Font
else if AControl is TListView then
AControlFont := TListView(AControl).Font
else if AControl is TStatusBar then
begin
TStatusBar(AControl).UseSystemFont := True;
// AControlFont := TStatusBar(AControl).Font
AControlFont := nil; //StatusBar has their own system font
end
else if AControl is TToolbar then
AControlFont := TToolbar(AControl).Font
else if AControl is TCoolbar then
AControlFont := TCoolbar(AControl).Font

{ System }
else if AControl is TPaintBox then
AControlFont := TPaintBox(AControl).Font

{ TEditListView }
{$IFNDEF NoEditListView}
else if AControl is TCustomListView then
AControlFont := TEditListView(AControl).Font
{$ENDIF}

{ Cannot fontify}
else if AControl is TToolButton then
AControlFont := nil
else if AControl is TBevel then
AControlFont := nil
{ else if AControl is TImage then
Exit
else if AControl is TProgressBar then
Exit
else if AControl is TPaintBox then
Exit
else if AControl is TShape then
Exit
}
else
begin
AControlFont := nil;
// ShowMessage('Unknown font class: '+AControl.Name+'
('+AControl.Classname+')');
end;

//Standardize the font if it's currently "MS Sans Serif" (the Delphi
default)
//or "Tahoma" (when "MS Shell Dlg 2" should have been used)
if not Assigned(AControlFont) then
Exit;

CanChangeName :=
(AControlFont.Name = 'MS Sans Serif') or
(AControlFont.Name = 'Tahoma') or
(AControlFont.Name = 'MS Shell Dlg 2') or
(AControlFont.Name = 'MS Shell Dlg');
CanChangeSize :=
(FontSize <> 0) and
(FontSize <> -1) and
(AControlFont.Size = 8) and
(AControlFont.Height <> FontSize);

if CanChangeName or CanChangeSize or ForceClearType then
begin
if GetObject(AControlFont.Handle, SizeOf(TLogFont), @lf) <> 0 then
begin
//Change the font attributes and put it back
if CanChangeName then
StrPLCopy(Addr(lf.lfFaceName[0]), FontName, LF_FACESIZE);
if CanChangeSize then
lf.lfHeight := FontSize;
if ForceClearType then
lf.lfQuality := CLEARTYPE_QUALITY;
AControlFont.Handle := CreateFontIndirect(lf);
end
else
begin
if CanChangeName then
AControlFont.Name := FontName;
if CanChangeSize then
begin
if FontSize > 0 then
AControlFont.Size := FontSize
else if FontSize < 0 then
AControlFont.Height := FontSize;
end;
end;
end;
end;

"Virgo Pärna" <virgo...@mail.ee> wrote in message
news:4798...@newsgroups.borland.com...
Our application had scaling disabled anyway. I wrote custom

Virgo Pärna

unread,
Jan 31, 2008, 2:47:59 AM1/31/08
to
Ian Boyd wrote:
>
> You might be interested in a function that recursivly goes through all
> controls on a form and "Standardizes" it's font. Any font that is
> MS Sans Serif (the Delphi default)
> Tahoma (the Windows 2000/XP default, and the Vista default if you don't
> support Vista)
> MS Shell Dlg (the Win9x internationalization font)
> MS Shell Dlg 2 (the 2000/XP/Vista internationalization font)
>

While it's interesting, it does not really suite when UI uses many
labels/buttons/edit boxes (data entry forms), because there you need to
change positions and sizes of those controls (larger font would not fit
into existing control). And scaling function actually specifies larger
font to use, as a part of scaling. We have specified the fonts to be
used on those controls in design time and specifying larger font does
not change proportions. But if I'd change font face, then it could
happen, that controls proportions don't suit for large fonts. I'm not
even sure, that there is perfect solutions for scaling problems because
in perfect case I would be using fonts from users preferences.
Luckily we all use small fonts in our development computers, so we
don't have additional problems caused by that factor.

--
Virgo Pärna
virgo...@mail.ee

Ian Boyd

unread,
Feb 1, 2008, 12:16:09 PM2/1/08
to
If you read Microsoft new guidelines for Vista it details how scaling is
supposed to be different between horizontal and vertical (i.e. Borland's
ScaleBy method isn't useful).

Windows Vista User Experience Guidelines
Layout Metrics
http://msdn2.microsoft.com/en-us/library/bb847924.aspx

also
Layout
http://msdn2.microsoft.com/en-us/library/aa511279.aspx


Since scaling should be done vertically based on font height, and
horizontally based on average character width, scaling of controls in the
horizontal and vertical directions is different.

For example, the 2000/XP default font Tahoma 8pt, at 96dpi is:
height: 13 pixels
average character width: 6 pixels

The Vista default font Segoe UI 9pt, at 96dpi is:
height: 15 pixels
average character width: 7 pixels

Getting these numbers for the users's current font choice is not easy, since
it requires a form canvas that you can actually measure a length of text,
and figure out the average width that way. You can get the average font face
and size by using:

procedure GetUserFontPreference(out FaceName: string; out PixelHeight:
Integer);
var
lf: LOGFONT;
begin


ZeroMemory(@lf, SizeOf(lf));
if SystemParametersInfo(SPI_GETICONTITLELOGFONT, SizeOf(lf), @lf, 0)
then
begin

FaceName := PChar(Addr(lf.lfFaceName[0]));
PixelHeight := lf.lfHeight;
end
else
begin
{
If we can't get it, then assume the same non-user preferences
that
everyone else does.
}
FaceName := 'MS Shell Dlg 2';
PixelHeight := 8;
end;
end;

So now you can change the font your form uses. The value here is that the
font height returned (in pixels) honors the users's DPI settings.

But you still don't know the metrics of that font. The Microsoft KB article:
How to calculate dialog box units based on the current font in Visual C++
http://support.microsoft.com/kb/145994

talks about having to do it. Basically you measure the length of the string
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" and divide by 52 to
get the average character width. The height

function GetDialogBaseUnitsReal(const AForm: TForm): TSize;
var
dc: HDC;
tm: TTextMetric;
BaseUnitX: LongInt;
BaseUnitY: LongInt;
Size: TSize;
begin
dc := AForm.Canvas.Handle;
if dc = 0 then
RaiseLastWin32Error;

Win32Check(GetTextMetrics(dc, tm));
BaseUnitY := tm.tmHeight;
Win32Check(GetTextExtentPoint32(
dc,
PChar('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'),
52,
Size));
BaseUnitX := (Size.cx div 26 + 1) div 2;

Result.cx := BaseUnitX;
Result.cy := BaseUnitY;
end;

This function returns the average size of the font (in pixels):
Segoe UI, 9pt, 96dpi: (15, 7)
Tahoma, 8pt, 96dpi: (13, 6)

Once you measure the average width and height of the users's current font
choice, and set all controls on the form to that font, you can then scale
all those controls' sizes and positions. Since you know that you always
design your form with Tahoma 8pt 96dpi, you can use your ScaleFormBy
routine:

procedure ScaleFormBy(AForm: TForm; Myy, Dy: Integer; Mx, Dx: Integer);


ScaleFormBy(Self, FontSize.cy, 13, FontSize.cx, 6);

It's important though that the ScaleFormBy routine not try to scale fonts.
That needs to be done separatly by a routine that cycles through all
controls on a form, and anything that is 8pt Tahoma/MS Shell Dlg/MS Sans
Serif convert to the user preference font. i have a routine that does that
called StandardizeFont

StandardizeFont(Self);
FontSize := GetDialogBaseUnitsReal(Self);
ScaleFormBy(Self, FontSize.cy, 13, FontSize.cx, 6);

i suppose all three of these could be put into a new super fixall function.

i just now need to write the ScaleFormBy() function.

0 new messages