views:

1877

answers:

6

I've come up with the call that gets the user's UI font preference (as opposed to Borland's hard-coded choice of "MS Sans Serif").

Let's pretend the user's font preference is:

Segoe Print, 15pt

I set the font of all items, on all forms, in all applications to:

Segoe Print, 15pt

Problem is that things are now cut off. Buttons are too small - too narrow, too short. Text in labels is cut off, etc..

The form has it's Scaled property, but that doesn't change depending on font sizes. The scaled property scaled the form when it is serialized in based on the height of the numeral "0".

I can't find anything in the help for how Borland intended me to support the user's Windows application preferences.

How do I handle user font preferences?

Note: I cross posted this from Embargadero's news group server, since Embargadero's news server seems to be dying, or censoring, or broken, or requiring a login.


Update 1

i'm talking about a user's font preference, not DPI settings. i.e.: imagine the following language neutral pseudo-code:

procedure TForm1.FormCreate(Sender: TObject);
var
    FontFace: string;
    FontHeight: Integer;
begin
    GetUserFontPreference(out FontFace, out FontHeight);
    Self.Font.Name := FontFace;
    Self.Font.Height := FontHeight;
end;

Note: This isn't my actual code (it is language neutral pseudo-code after all). But additionally, you need to recursivly go through every control on the form, changing the font when it needs to be changed. When a font has a different style applied than its parent (e.g. bold), and no longer inherits from its parent, it needs to be manually set.


As per lkessler's request, here's the code to retrieve the user's UI font preference from Windows:

procedure GetUserFontPreference(out FaceName: string; out PixelHeight: Integer);
var
    lf: LOGFONT;
begin
    ZeroMemory(@lf, SizeOf(lf));
     //Yes IconTitleFont (not SPI_GETNONCLIENTMETRICS MessageFont)
    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;

Related questions

+7  A: 

First, just so we are clear, Borland doesn't own Delphi anymore. Embarcadero now owns Delphi, and we are in safe, secure hands now.

Okay, on to your question.

The trick is to set TForm.AutoScroll to False AND make sure that your development machine is set to Small fonts. Leaving TForm.Scaled alone (it's default value is True).

That's how we do it internally here, and the IDE handles everything just fine.

Nick Hodges
AFAICS the IDE does not handle everything just fine. On my laptop screen (set to 124 DPI) all the lines in the Delphi 2007 object inspector are cut off...
mghie
Telling developers to "make sure that your development machine is set to Small fonts" does not constitute a trick in my book. It's either ignorance or sadism. Just try small fonts on a 145 DPI display...
mghie
You're not reading the question; assuming different DPI settings. i'm talking about different fonts. Lets assume for the purposes of this question that every machine in the world is set for 96dpi.
Ian Boyd
"Embarcadero now owns Delphi, and we are in safe, secure hands now." i'll wait 10 years before agreeing.
Ian Boyd
+1  A: 

I'm feeling with you. But in all fairness: Proper GUI layout simply can not be created with a pixel-based layout mechanism as employed by the VCL. What is needed is a dynamic layout engine, which lays out controls only after

  1. the proper font has been set for each control (this depends on Windows version, user preferences, and last but not least on the type of control); and

  2. the text in the control has been translated to the current locale, because this may decrease or increase the space necessary for the control.

Since this is all depending on run-time properties creating dialogs by placing controls can not work. Layout mechanisms like those of GTK and QT or the sizers in wxWidgets are better suited.

I know of no such thing for Delphi programs. What I do in some programs is manual sizing and positioning of controls after font setting and translation of text. A lot of work, but depending on your audience it may be worth it.

You could also look into the code Jordan Russell has written for Inno Setup. He does not use the Scaled property of forms, but has written code for custom scaling of controls. Maybe it will also work for very large fonts on high DPI screens; I notice that at least on my 124 DPI laptop screen setup dialogs look quite good.

mghie
i disagree. The concept of DLUs works very well. Microsoft almost got it right in WinForms, but missed a critical points, so it's just as bad as Borlands.
Ian Boyd
Using DLUs won't help when a) different controls use different fonts (like on Vista); b) text length will change at runtime, depending on translation; and c) the layout engine is to be used for runtime resizing of dialogs as well. There are layout engines that support all three. DLUs don't help.
mghie
a) DLUs scale with fonts (like on Vista) b) i've never written anythingn except in english c)
Ian Boyd
a) Vista uses different fonts for different controls (see http://msdn.microsoft.com/en-us/library/aa511282.aspx), and they need to be scaled accordingly. Which DLU is it going to be? But let's stop this, it's not going anywhere really.
mghie
+2  A: 

To do this right, I think you would have to:

1 - Load the user's font preference

2 - Apply it as you've described

3 - Determine the width and height of all captions (in pixels) and compare that to the control's width & height, and adjust accordingly.

Unfortunately, the "adjust accordingly" part is hard. You could widen a button, but then you'd have to check whether that is overlapping with another control.

Your best bet might be to create slightly oversized controls, and hope the user doesn't choose a 32-point font size.

JosephStyons
:( <-sad face, and this text is here only to make the comment long enough so that stackoverflow will permit my two character comment to be accepted as a valid comment.
Ian Boyd
+1  A: 

Kogus had the right idea, but left out the function you need to use. It would be the Windows GetTextExtentPoint32 routine. You pass it a string and it calculates the width and height of the string using the currently selected font. You'll find it in Windows.pas.

You might want to calculate in advance what the maximum space you would need for fonts you allow.

Or you might use the function to dynamically adjust the size of the area displaying the text so that it fits.

This would be fine for one or two controls, but either method would be a lot of work if you're trying to do this on your whole user interface.

lkessler
A: 

I'd say this was similar to translation. In our app, we have made the buttons, labels, edits, everything that little bit bigger so that longer words that are needed by some languages can be easily accommodated. It doesn't hurt the design a bit.

mj2008
That won't work. Pick Segoe UI, 18pt as your font.
Ian Boyd
+1  A: 

I like using this function. It's based on Graphics.GetFontData

procedure SystemFont(Font: TFont);
var
  LogFont: TLogFont;
begin
  if SystemParametersInfo(SPI_GETICONTITLELOGFONT, SizeOf(TLogFont), @LogFont, 0) then
    begin
      Font.Height := LogFont.lfHeight;
      Font.Orientation := LogFont.lfOrientation;
      Font.Charset := TFontCharset(LogFont.lfCharSet);
      Font.Name := PChar(@LogFont.lfFaceName);

      Font.Style := [];

      if LogFont.lfWeight >= FW_BOLD then
        Font.Style := Font.Style + [fsBold];

      if LogFont.lfItalic = 1 then
        Font.Style := Font.Style + [fsItalic];

      if LogFont.lfUnderline = 1 then
        Font.Style := Font.Style + [fsUnderline];

      if LogFont.lfStrikeOut = 1 then
        Font.Style := Font.Style + [fsStrikeOut];

      case LogFont.lfPitchAndFamily and $F of
        VARIABLE_PITCH: Font.Pitch := fpVariable;
        FIXED_PITCH: Font.Pitch := fpFixed;
        else Font.Pitch := fpDefault;
      end;
    end;
end;

You simply use it on all TForm.OnCreate events. An alternative could be to create a new class that does this on creation or looping Screen.Forms.

If you change some of the properties of the default font of any of the controls on the form they will still use the old font. If you want custom properties on some controls you will have to adjust them after you call SystemFont.

Changing Graphics.DefFontData at runtime could help if the form designer only wrote the changed properties to the .dfm files.

Zoxc
This does not address the issue that non-standard fonts may make the text in controls too large, so that resizing and repositioning is necessary.
mghie
i was about to say the same thing. i have no problem *getting* the default font, the question is "Handling user’s font preference"
Ian Boyd