How to convert DLU into pixels?
Asked Answered
L

4

10

Microsoft uses dialog length units (DLU) in their guidelines for UI. How can I convert them into pixels?

As I know, DLU depending on system font size. Can you advise some simple method of such conversion in Delphi for Win32?

Launcher answered 29/7, 2011 at 8:25 Comment(0)
D
11

You should use the MapDialogRect() function.

Pass in a RECT in dialog units, and the equivalent RECT in pixel units is returned. Note that you need a handle to a dialog in order to give MapDialogRect() sufficient context. The function needs to know the font in order to perform the conversion.


In case you are tempted to use GetDialogBaseUnits(), remember what Raymond Chen said, GetDialogBaseUnits is a crock.

As you can guess from the title of this entry, GetDialogBaseUnits is a crock. Since there is no HWND parameter to GetDialogBaseUnits, it doesn't know which dialog box's DLUs you want to retrieve. So it guesses.

And it always guesses wrong.

GetDialogBaseUnits returns the dialog base units for dialog boxes that use the default system font. But nobody uses the default system font any more. It screams "old and dorky". But it remains the default for compatibility reasons. (And therefore so too does GetDialogBaseUnits.)

If you have to calculate pixel dimensions from DLUs, and you don't have a handle to a dialog, then you must use the method outlined here: How To Calculate Dialog Base Units with Non-System-Based Font


However, you made it clear in the comments that, for your problem, you do not actually need to convert from DLUs to pixels. You can use Delphi's built in form scaling to ensure that your forms are sized appropriately for the prevailing font scaling.

Documentation answered 29/7, 2011 at 9:14 Comment(4)
This function requires the Dialog's HWND, but what if my window is not a dialog? Can I get some standard dialog's HWND somewhere in the system?Launcher
dialog units are meaningless without a context. You have to have a dialog.Documentation
Simple example: I want to set the size of my command buttons to the standard height. Standard height = 14 DLU. I don't have any dialog window in my app. :(Launcher
DLUs only have meaning relative to some font. Set your buttons to 23 pixels height in 96dpi dfm and let delphi form scaling do the rest.Documentation
P
20

First we start with what a dialog unit is.

For that i'll quote one of my own un-answered questions:

What's a dialog unit?

A dialog is a unit of measure based on the user's preferred font size. A dialog unit is defined such that the average character is 4 dialog units wide by 8 dialog units high:

enter image description here

This means that dialog units:

  • change with selected font
  • changed with selected DPI setting
  • are not square

i'll also quote another of my own un-answered questions:

You can check the Windows UX Guidelines to see where these measurements come from. The short version is:

  • dlu = dialog unit
  • dlu is based on the font size (items change with user's font size)
  • a horizontal dlu is different from a vertical dlu (dlu's are not square)

This comes from the definition of a dialog unit: the average character is 8dlus high by 4dlus wide.

Georgia 14pt:

enter image description here

If you use a smaller font (i.e. 8pt Tahoma verses 14pt Georgia), the dlus get smaller:

Segoe UI 9pt:

enter image description here

Note: You'll notice that resolution (i.e. dpi) has no impact on the discussion.

So what you need is the average size of a character. Microsoft has an official technique for calculating the average character size.

  • average height:

    GetTextMetrics(dc, {var}textMetrics);
    averageHeight := textMetrics.tmHeight;
    
  • average width:

    Measure the string ABCDEFGHIJLKMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz using GetTextExtentPoint32, and divide by 52:

    GetTextExtentPoint32(dc,
          PChar('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'), 52, Size));
    averageWidth := size.cx / 52.0;
    

So now you need the the size of a horizontal and a vertical dialog units. Remember that a horizontal dialog unit is 1/4 the average character width, and a vertical dlu is 1/8 the average character height:

procedure GetDlus(dc: HDC; out HorizontalDluSize, VerticalDluSize: Real);
var
   tm: TTextMetric; 
   size: TSize;
begin
   GetTextMetric(dc, tm);
   VerticalDluSize := tm.tmHeight / 8.0;

   GetTextExtentPoint32(dc,
         PChar('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'), 52,
         size);
   HorizontalDluSize := size.cx / 52.0;
end;

Note: Any code is released into the public domain. No attribution required.

Prosecutor answered 21/5, 2012 at 22:34 Comment(0)
D
11

You should use the MapDialogRect() function.

Pass in a RECT in dialog units, and the equivalent RECT in pixel units is returned. Note that you need a handle to a dialog in order to give MapDialogRect() sufficient context. The function needs to know the font in order to perform the conversion.


In case you are tempted to use GetDialogBaseUnits(), remember what Raymond Chen said, GetDialogBaseUnits is a crock.

As you can guess from the title of this entry, GetDialogBaseUnits is a crock. Since there is no HWND parameter to GetDialogBaseUnits, it doesn't know which dialog box's DLUs you want to retrieve. So it guesses.

And it always guesses wrong.

GetDialogBaseUnits returns the dialog base units for dialog boxes that use the default system font. But nobody uses the default system font any more. It screams "old and dorky". But it remains the default for compatibility reasons. (And therefore so too does GetDialogBaseUnits.)

If you have to calculate pixel dimensions from DLUs, and you don't have a handle to a dialog, then you must use the method outlined here: How To Calculate Dialog Base Units with Non-System-Based Font


However, you made it clear in the comments that, for your problem, you do not actually need to convert from DLUs to pixels. You can use Delphi's built in form scaling to ensure that your forms are sized appropriately for the prevailing font scaling.

Documentation answered 29/7, 2011 at 9:14 Comment(4)
This function requires the Dialog's HWND, but what if my window is not a dialog? Can I get some standard dialog's HWND somewhere in the system?Launcher
dialog units are meaningless without a context. You have to have a dialog.Documentation
Simple example: I want to set the size of my command buttons to the standard height. Standard height = 14 DLU. I don't have any dialog window in my app. :(Launcher
DLUs only have meaning relative to some font. Set your buttons to 23 pixels height in 96dpi dfm and let delphi form scaling do the rest.Documentation
B
0

Here's C code for converting DLU ↔ pixels:

HWND hDlg = ...;                   // The handle to the dialog

LPDLGTEMPLATE *dlgTemplate = ...;  // The template for the same dialog
SIZE dlgSize;                      // Only needed for converting DLU -> pixels
if (dlgTemplate->style == 0xFFFF0001)
{
    dlgSize.cx = ((DLGTEMPLATEEX *)dlgTemplate)->cx;
    dlgSize.cy = ((DLGTEMPLATEEX *)dlgTemplate)->cy;
}
else
{
    dlgSize.cx = dlgTemplate->cx;
    dlgSize.cy = dlgTemplate->cy;
}

RECT rc = { 0, 0, 4, 8 };
MapDialogRect(hDlg, &rc);

// To convert dlgSize to pixels, use:
SIZE wndSize = { dlgSize.cx * rc.right / 4, dlgSize.cy * rc.bottom / 8 };

// To convert wndSize to DLUs, use:
SIZE dlgSize2 = { size.cx * 4 / rc.right, size.cy * 8 / rc.bottom };
assert(dlgSize1 == dlgSize2);
Bettyebettzel answered 15/1, 2022 at 6:17 Comment(0)
P
-4

For base value (and naturally, system font) call GetDialogBaseUnits. See also remarks paragraph there for the alternate method of translating dialog units <-> pixels with GetTextMetrics and/or GetTextExtentPoint32 without dialog HWND.

Plectron answered 30/7, 2011 at 11:50 Comment(6)
The system font is used only if the dialog box template fails to specify a font. Most dialog box templates specify a font; as a result, this function is not useful for most dialog boxes. For a dialog box that does not use the system font, the base units are the average width and height, in pixels, of the characters in the dialog's font. You can use the GetTextMetrics and GetTextExtentPoint32 functions to calculate these values for a selected font. However, by using the MapDialogRect function, you can avoid errors that might result if your calculations differ from those performed by the system.Documentation
@David Heffernan, your original proposal simply will not work with VCL. Read remarks, they are vital for understanding concept.Plectron
VCL doesn't use DLUs. VCL forms aren't appropriate for GetDialogBaseUnits either. Did you read the msdn text that says not to use GetDialogBaseUnits?Documentation
@David Heffernan, yes, i did. I also did not stop at those words and read other text, which shows how to resolve the problem making your solution not working.Plectron
so you are claiming that MapDialogRect() doesn't work now? You need to get past your facile vendettas.Documentation
@David Heffernan, it was never appropriate solution, however you forgot to delete comment where you admitted this fact :)Plectron

© 2022 - 2024 — McMap. All rights reserved.