How to convert DLU into pixels?

Solution 1:

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.

Solution 2:

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.