38

我为一家小型企业创建了一个应用程序。办公室中的一些员工无法正确查看表格。原因是他们将 DPI 设置设置为 96dpi 以上。有人知道控制这种情况的方法吗?

对于所有使用过 winforms 应用程序的人来说,如何控制表单布局以使 DPI 不会影响应用程序的外观?

4

3 回答 3

63

假设您不尝试尊重用户的 UI 字体选择 (SystemFonts.IconTitleFont),并且仅将表单硬编码为一种字体大小(例如 Tahoma 8pt、Microsoft Sans Serif 8.25pt),您可以将表单设置AutoScaleModeScaleMode.Dpi.

这将通过调用来缩放窗体和大部分子控件的大小,这反过来又在自身和所有子控件上递归调用受保护的方法。将根据新缩放因子的需要增加控件的位置、大小、字体等。CurrentDpiSetting / 96Form.Scale()ScaleControl()ScaleControl

警告:并非所有控件都能正确缩放。例如,列表视图的列不会随着字体变大而变宽。为了处理这个问题,您必须根据需要手动执行额外的缩放。我通过覆盖受保护的ScaleControl()方法并手动缩放列表视图列来做到这一点:

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);
      }
   }
}

如果您只是使用控件,这一切都很好。但是,如果您曾经使用任何硬编码的像素大小,则需要通过表单的当前比例因子来缩放像素的宽度和长度。可能具有硬编码像素大小的一些情况示例:

  • 绘制一个 25px 高的矩形
  • 在表单上的位置 (11,56) 绘制图像
  • 将图标拉伸到 48x48
  • 使用 Microsoft Sans Serif 8.25pt 绘制文本
  • 获取 32x32 格式的图标并将其填充到 PictureBox

如果是这种情况,您需要通过“当前缩放因子”来缩放这些硬编码值。遗憾的是没有提供“当前”比例因子,我们需要自己记录。解决方案是假设最初缩放因子为 1.0,并且每次ScaleControl()调用时,将运行的缩放因子修改为新因子。

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);
   }
}

最初的比例因子是1.0。如果 form 然后按 缩放1.25,则缩放因子变为:

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

如果窗体随后按 缩放0.95,则新的缩放因子变为

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

使用a SizeF(而不是单个浮点值)的原因是缩放量在 x 和 y 方向上可能不同。如果表单设置为ScaleMode.Font,则表单将缩放为新的字体大小。字体可以有不同的纵横比(例如 Segoe UI的字体比Tahoma高)。这意味着您必须独立缩放 x 和 y 值。

因此,如果您想在 location 放置一个控件(11,56),则必须将定位代码从以下位置更改:

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

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

如果您要选择字体大小,这同样适用:

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

必须变成:

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

将 32x32 图标提取到位图将变为:

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

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

等等

支持非标准 DPI 显示是所有开发者都应该缴纳的税。但没人愿意这样做的事实就是为什么微软放弃并在 Vista 中增加了图形卡扩展任何不能正确处理高 dpi 的应用程序的能力

于 2008-10-14T17:59:48.707 回答
16

通过全局搜索/替换将 AutoScaleMode 设置为随处继承(即所有用户控件),然后在主窗体上将 AutoScaleMode 设置为 Dpi。

我还发现在这种情况下布局容器比锚点效果更好。

于 2008-10-09T14:56:21.417 回答
2

我知道这有点激烈,但请考虑在 WPF 中重写您的应用程序。WPF 应用程序在每个 DPI 设置上都具有相同的外观。

于 2014-11-06T16:39:02.977 回答