[ACCEPTED]-How to control the font DPI in .NET WinForms app-dpi

Accepted answer
Score: 64

Assuming you do not try to honor the user's 48 UI font choice (SystemFonts.IconTitleFont), and 47 hard-code your forms for one font size only 46 (e.g. Tahoma 8pt, Microsoft Sans Serif 8.25pt), you 45 can set your form's AutoScaleMode to ScaleMode.Dpi.

This will scale 44 the size of the form and most of it child controls 43 by the factor CurrentDpiSetting / 96 by calling Form.Scale(), which in turns 42 calls the protected ScaleControl() method recursivly on 41 itself and all child controls. ScaleControl will increase 40 a control's position, size, font, etc as 39 needed for the new scaling factor.

Warning: Not all 38 controls properly scale themselves. The 37 columns of a listview, for example, will 36 not get wider as the font gets larger. In order 35 to handle that you'll have to manually 34 perform additional scaling as required. i 33 do this by overriding the protected ScaleControl() method, and scaling 32 the listview columns manually:

public class MyForm : Form
{
   protected override void ScaleControl(SizeF factor, BoundsSpecified specified)
   {
      base.ScaleControl(factor, specified);
      Toolkit.ScaleListViewColumns(listView1, factor);
   }
}

public class Toolkit  
{
   /// <summary>
   /// Scale the columns of a listview by the Width scale factor specified in factor
   /// </summary>
   /// <param name="listview"></param>
   /// <param name="factor"></param>
   /// <example>/*
   /// protected override void ScaleControl(SizeF factor, BoundsSpecified specified)
   /// {
   ///    base.ScaleControl(factor, specified);
   ///    
   ///    //ListView columns are not automatically scaled with the ListView, so we
   ///    //must do it manually
   ///    Toolkit.ScaleListViewColumns(lvPermissions, factor);
   /// }
   ///</example>
   public static void ScaleListViewColumns(ListView listview, SizeF factor)
   {
      foreach (ColumnHeader column in listview.Columns)
      {
          column.Width = (int)Math.Round(column.Width * factor.Width);
      }
   }
}

This is all 31 well and good if you're just using controls. But 30 if you ever use any hard-coded pixel sizes, you'll 29 need to scale your pixel widths and lengths 28 by the current scale factor of the form. Some 27 examples of situations that could have hard-coded 26 pixel sizes:

  • drawing a 25px high rectangle
  • drawing an image at location (11,56) on the form
  • stretch drawing an icon to 48x48
  • drawing text using Microsoft Sans Serif 8.25pt
  • getting the 32x32 format of an icon and stuffing it into a PictureBox

If this is the case, you'll 25 need to scale those hard-coded values by 24 the "current scaling factor". Unfortunatly the "current" scale 23 factor is not provided, we need to record 22 it ourselves. The solution is to assume 21 that initially the scaling factor is 1.0 20 and each time ScaleControl() is called, modify the running 19 scale factor by the new factor.

public class MyForm : Form
{
   private SizeF currentScaleFactor = new SizeF(1f, 1f);

   protected override void ScaleControl(SizeF factor, BoundsSpecified specified)
   {
      base.ScaleControl(factor, specified);

      //Record the running scale factor used
      this.currentScaleFactor = new SizeF(
         this.currentScaleFactor.Width * factor.Width,
         this.currentScaleFactor.Height * factor.Height);

      Toolkit.ScaleListViewColumns(listView1, factor);
   }
}

Initially 18 the scaling factor is 1.0. If form is then 17 scaled by 1.25, the scaling factor then becomes:

1.00 * 1.25 = 1.25    //scaling current factor by 125%

If 16 the form is then scaled by 0.95, the new scaling 15 factor becomes

1.25 * 0.95 = 1.1875  //scaling current factor by 95%

The reason a SizeF is used (rather 14 than a single floating point value) is that 13 scaling amounts can be different in the 12 x and y directions. If a form is set to 11 ScaleMode.Font, the form is scaled to the new font size. Fonts 10 can have different aspect ratios (e.g. Segoe UI is taller 9 font than Tahoma). This means you have to scale 8 x and y values independantly.

So if you wanted 7 to place a control at location (11,56), you would 6 have to change your positioning code from:

Point pt = new Point(11, 56);
control1.Location = pt;

to

Point pt = new Point(
      (int)Math.Round(11.0*this.scaleFactor.Width),
      (int)Math.Round(56.0*this.scaleFactor.Height));
control1.Location = pt;

The 5 same applies if you were going to pick a 4 font size:

Font f = new Font("Segoe UI", 8, GraphicsUnit.Point);

would have to become:

Font f = new Font("Segoe UI", 8.0*this.scaleFactor.Width, GraphicsUnit.Point);

And extracting 3 a 32x32 icon to a bitmap would change from:

Image i = new Icon(someIcon, new Size(32, 32)).ToBitmap();

to

Image i = new Icon(someIcon, new Size(
     (int)Math.Round(32.0*this.scaleFactor.Width), 
     (int)Math.Round(32.0*this.scaleFactor.Height))).ToBitmap();

etc.

Supporting 2 non-standard DPI displays is a tax that all developers should pay. But the 1 fact that nobody wants to is why Microsoft gave up and added to Vista the ability for the graphics card to stretch any applications that don't say they properly handle high-dpi.

Score: 17

Set the AutoScaleMode to Inherit everywhere 5 (ie all your UserControls) via a global 4 search/replace, then set the AutoScaleMode 3 to Dpi on your main form.

I also find that 2 layout containers work better than anchors 1 for this type of situation.

Score: 2

I know it's somewhat drastic, but consider 2 to rewrite your app in WPF. WPF applications 1 have the same look on every DPI setting.

More Related questions