创建支持 DPI 的应用程序

我在 C # 中有一个表单应用程序。当我改变显示器的 DPI 时,所有的控件都会移动。 I used the code this.AutoScaleMode = AutoScaleMode.Dpi, but it didn't avoid the problem.

有人知道吗?

113173 次浏览

在 Windows 窗体中设计支持 DPI 的应用程序确实很难。您必须使用能够在 DPI 更改时正确调整大小的布局容器(例如 TableLayoutPanel 或 FlowLayoutPanel)。所有控件也需要调整大小。这些容器的配置可能是一个挑战。

对于简单的应用程序来说,它可以在合理的时间内完成,但对于大型应用程序来说,这确实是一项繁重的工作。

经验之谈:

  • 不要在窗体中使用 DPI 感知,除非非常关键
  • 为此,在应用程序的所有窗体和用户控件上始终将 AutoScaleMode属性设置为 None
  • The result: WYSIWYG type of interface when DPI settings change

编辑: 截至目前。NET 4.7,Windows 窗体改进了对高 DPI 的支持。它只适用于 Win 10 Creators Update 和更高版本,所以根据你的用户基础使用它可能还不可行。


困难,但并非不可能。最好的选择当然是转向 WPF,但这可能不太可行。

我在这个问题上花了很多时间。下面是一些不使用 FlowLayoutPanel 或 TableLayoutPanel 而使其正常工作的规则/指南:

  • 总是默认编辑/设计你的应用程序96 DPI (100%)。如果你设计在120DPI (125% 的外汇) ,它会变得非常糟糕,当你回到96 DPI 与它工作以后。
  • 我已经成功地使用了 AutoScaleMode.Font,但还没有怎么试过 AutoScaleMode.DPI。
  • 确保在所有容器(表单、面板、选项卡、用户控件等)上使用默认字体大小。8,25像素。最好不要设置在。Cs 文件,以便它使用容器类中的默认字体。
  • 所有容器使用相同的 AutoScaleMode
  • 确保所有容器在 Designer.cs 文件中都设置了以下代码行:

this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); // for design in 96 DPI

  • If you need to set different font sizes on labels/textboxes etc. set them per control instead of setting the font on the container class because winforms uses the containers font setting to scale it's contents and having f.ex a panel with a different font size than it's containing form is guaranteed to make problems. It might work if the form and all containers on the form use the same font size, but I haven't tried it.
  • 使用另一台机器或虚拟窗口安装(VMware,Virtual PC,VirtualBox)与较高的 DPI 设置立即测试您的设计。运行编译好的。从 DEV 机器上的/bin/Debug 文件夹下载 exe 文件。

我保证,如果你遵循这些指导方针,你会没事的,即使你已经放置了具体的锚控制,不使用流程面板。我们有一个应用程序建立了这种方式部署在数百台机器与不同的 DPI 设置,我们不再有任何投诉。所有表单/容器/网格/按钮/文本字段等大小的缩放都与字体一样正确。图像也可以工作,但他们往往得到一个小像素在高 DPI。

编辑: 这个链接有很多好的信息,特别是如果你选择使用 AutoScaleMode.DPI: 链接到相关的堆栈溢出问题

Since a Winform application form may content controls AND images, allowing the system to resize YOUR window is NOT a solution, but if you could manage to have one form per DPI resolution, with properly scaled images... And that's not a good idea, since as the screen size grow, the font size diminishes.

当使用不同的 DPI 分辨率时,系统会迫使你的表单重新定义其控件的大小、位置和字体,但不是图像,解决方案是在加载时在运行时改变表单的 DPI,这样一切都回到原来的大小和位置。

这是一个可能的解决方案,我已经在一个卡片游戏应用程序中测试过了,在这个应用程序中我有大约80个图像按钮,TabControls 等。

在每个 form form _ Load 事件中,添加以下代码段:

  Dim dpi As Graphics = Me.CreateGraphics
Select Case dpi.DpiX
Case 120
'-- Do nothing if your app has been desigbned with 120 dpi
        Case Else
'-- I use 125 AND NOT 120 because 120 is 25% more than 96
Me.Font = New Font(Me.Font.FontFamily, Me.Font.Size * 125 / dpi.DpiX)
End Select

此外,还有一个在同一台计算机上测试不同分辨率的快速技巧,不需要重新启动:

从控制面板,改变分辨率。 不要重新启动! 而是关闭您的会话,并用相同的用户打开一个新的会话。

还有另一个警告: 如果在运行时设置控件的大小和位置,则应该应用相同的 DPI 因子(例如125/DPI)。Dpix)到新坐标。因此,最好从 application.start 事件设置一个 DPIFactor 全局变量。

最后但同样重要的是:

不要在 VisualStudio 中从原始分辨率以外的其他分辨率打开应用程序,否则,当您打开每个表单时,所有您的控件都将移动并调整大小,而且没有退路..。

希望这对你有帮助,编程愉快。

我终于找到了解决方案的屏幕方向和 DPI 处理的问题。
微软已经提供了一份文件来解释这个问题,但是有一个小小的缺陷会完全扼杀 DPI 的处理能力。 只要按照下面“为每个方向创建单独的布局代码”文档中提供的解决方案操作即可 Http://msdn.microsoft.com/en-us/library/ms838174.aspx

那么重要的部分! 在横向()和纵向()方法的代码中,在每个方法的末尾添加以下代码行:

this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;

这两个方法的代码是这样的:

protected void Portrait()
{
this.SuspendLayout();
this.crawlTime.Location = new System.Drawing.Point(88, 216);
this.crawlTime.Size = new System.Drawing.Size(136, 16);
this.crawlTimeLabel.Location = new System.Drawing.Point(10, 216);
this.crawlTimeLabel.Size = new System.Drawing.Size(64, 16);
this.crawlStartTime.Location = new System.Drawing.Point(88, 200);
this.crawlStartTime.Size = new System.Drawing.Size(136, 16);
this.crawlStartedLabel.Location = new System.Drawing.Point(10, 200);
this.crawlStartedLabel.Size = new System.Drawing.Size(64, 16);
this.light1.Location = new System.Drawing.Point(208, 66);
this.light1.Size = new System.Drawing.Size(16, 16);
this.light0.Location = new System.Drawing.Point(192, 66);
this.light0.Size = new System.Drawing.Size(16, 16);
this.linkCount.Location = new System.Drawing.Point(88, 182);
this.linkCount.Size = new System.Drawing.Size(136, 16);
this.linkCountLabel.Location = new System.Drawing.Point(10, 182);
this.linkCountLabel.Size = new System.Drawing.Size(64, 16);
this.currentPageBox.Location = new System.Drawing.Point(10, 84);
this.currentPageBox.Size = new System.Drawing.Size(214, 90);
this.currentPageLabel.Location = new System.Drawing.Point(10, 68);
this.currentPageLabel.Size = new System.Drawing.Size(100, 16);
this.addressLabel.Location = new System.Drawing.Point(10, 4);
this.addressLabel.Size = new System.Drawing.Size(214, 16);
this.noProxyCheck.Location = new System.Drawing.Point(10, 48);
this.noProxyCheck.Size = new System.Drawing.Size(214, 20);
this.startButton.Location = new System.Drawing.Point(8, 240);
this.startButton.Size = new System.Drawing.Size(216, 20);
this.addressBox.Location = new System.Drawing.Point(10, 24);
this.addressBox.Size = new System.Drawing.Size(214, 22);


//note! USING JUST AUTOSCALEMODE WILL NOT SOLVE ISSUE. MUST USE BOTH!
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); //IMPORTANT
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;   //IMPORTANT
this.ResumeLayout(false);
}


protected void Landscape()
{
this.SuspendLayout();
this.crawlTime.Location = new System.Drawing.Point(216, 136);
this.crawlTime.Size = new System.Drawing.Size(96, 16);
this.crawlTimeLabel.Location = new System.Drawing.Point(160, 136);
this.crawlTimeLabel.Size = new System.Drawing.Size(48, 16);
this.crawlStartTime.Location = new System.Drawing.Point(64, 120);
this.crawlStartTime.Size = new System.Drawing.Size(248, 16);
this.crawlStartedLabel.Location = new System.Drawing.Point(8, 120);
this.crawlStartedLabel.Size = new System.Drawing.Size(48, 16);
this.light1.Location = new System.Drawing.Point(296, 48);
this.light1.Size = new System.Drawing.Size(16, 16);
this.light0.Location = new System.Drawing.Point(280, 48);
this.light0.Size = new System.Drawing.Size(16, 16);
this.linkCount.Location = new System.Drawing.Point(80, 136);
this.linkCount.Size = new System.Drawing.Size(72, 16);
this.linkCountLabel.Location = new System.Drawing.Point(8, 136);
this.linkCountLabel.Size = new System.Drawing.Size(64, 16);
this.currentPageBox.Location = new System.Drawing.Point(10, 64);
this.currentPageBox.Size = new System.Drawing.Size(302, 48);
this.currentPageLabel.Location = new System.Drawing.Point(10, 48);
this.currentPageLabel.Size = new System.Drawing.Size(100, 16);
this.addressLabel.Location = new System.Drawing.Point(10, 4);
this.addressLabel.Size = new System.Drawing.Size(50, 16);
this.noProxyCheck.Location = new System.Drawing.Point(168, 16);
this.noProxyCheck.Size = new System.Drawing.Size(152, 24);
this.startButton.Location = new System.Drawing.Point(8, 160);
this.startButton.Size = new System.Drawing.Size(304, 20);
this.addressBox.Location = new System.Drawing.Point(10, 20);
this.addressBox.Size = new System.Drawing.Size(150, 22);


//note! USING JUST AUTOSCALEMODE WILL NOT SOLVE ISSUE. MUST USE BOTH!
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); //IMPORTANT
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;   //IMPORTANT
this.ResumeLayout(false);
}

对我来说很有效。

  1. 如果你希望你的 WinForms 应用程序是 DPI-Aware 应用程序,除了 Trygve 好的答案,如果你有大的项目,你可能想自动缩放你的表单和它们的内容,你可以通过创建 ScaleByDPI 函数:

ScaleByDPI 函数将接收一个 Control 参数,该参数通常是一个窗体,然后递归地遍历所有子控件(if (Control)。HasChildren = = true) ,并将应用程序控件的位置和大小以及字体的大小和大小缩放到操作系统配置的 DPI。您可以尝试实现它也为图像,图标和图形。

ScaleByDPI 函数的特别说明:

a. For all controls with default Font sizes, you will need to set their Font.Size to 8.25.

b. You can get devicePixelRatioX and devicePixelRatioY values by (control.CreateGraphics().DpiX / 96) and (control.CreateGraphics().DpiY / 96).

c. You will need scale Control.Size & Control.Location by algorithm that based on control.Dock & control.Anchor values. Be noticed that control.Dock may have 1 of 6 possible values and that control.Anchor may have 1 of 16 possible values.

这个算法将需要设置下一个布尔变量的值 isDoSizeWidth,isDoSizeHeight,isDoLocationX,isDoLocationY,isDoRefactorSizeWidth,isDoRefactorSizeHeight,isDoRefactorLocationX,isDoRefactorLocationY,isDoClacLocationXBasedOnRight,isDoClacLocationYBasedOnbottom。

如果项目使用 Microsoft 控件以外的控件库,则此控件可能需要特殊处理。

关于上面(d) bool 变量的更多信息:

*Sometimes a group of controls (may be a buttons) need to be placed one after another on same vertical line, and their Anchor value include Right but not Left, or they need to be placed one after another on same horizontal line, and their Anchor value include Bottom but not Top, in this case you need to re-calculate controls Location values.

* 如果控件 Anchor 包含顶部和底部以及或者左侧和右侧,则需要重新分解控件 Size & Location 值。

ScaleByDPI 函数的用法:

在任何 Form 构造函数的末尾添加下一个命令: ScaleByDPI (this) ;

也是在动态地将任何控件添加到调用 ScaleByDPI ([ ControlName ])的 Form 中时。

  1. 在构造函数结束后动态设置任何控件的 Size 或 Location 时,创建并使用下一个函数以获得 Size 或 Location 的缩放值: ScaleByDPI _ X ScaleByDPI _ Y ScaleByDPI _ Size ScaleByDPI _ Point

  2. 为了将应用程序标记为 DPI 感知的,请将 dpiAware 元素添加到应用程序的程序集清单中。

  3. 设置所有控件的图形单元。字体到系统。绘图。图形单元。点

  4. 在所有容器的 * . Designer.cs 文件中,将 AutoScaleMode 值设置为 System.Windows.Forms.AutoScaleMode

  5. in controls like ComboBox & TextBox, changing Control.Size.Hieght have no affect. In this case changing Control.Font.Size will fix control's height.

  6. 如果表单 StartPosition 值为 FormStartposition. CenterScreen,则需要重新计算窗口的位置。

看起来这是 Windows 的一个问题。删除这两条线就可以解决所有问题。

this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;

这就是我得到解决办法的地方:

注意: 这不会修复控件移动,当 dpi 改变时。这只会修复模糊的文本。


How to fix blurry Windows Forms in high-dpi settings:

  1. Go the the Forms designer, then select your Form (by clicking at 标题栏)
  2. 按 F4打开“属性”窗口,
  3. 然后定位 AutoScaleMode属性
  4. 把它从 字体(默认)改成 DPI

Now, go to Program.cs (or the file where your Main method is located) and change it to look like:

namespace myApplication
{
static class Program
{
[STAThread]
static void Main()
{
// ***this line is added***
if (Environment.OSVersion.Version.Major >= 6)
SetProcessDPIAware();


Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}


// ***also dllimport of that function***
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern bool SetProcessDPIAware();
}
}

保存并编译。现在窗体应该看起来清晰了。


source: Http://crsouza.com/2015/04/13/how-to-fix-blurry-windows-forms-windows-in-high-dpi-settings/

I struggled with this for a while eventually I found a super simple solution for windows 10 and potentially other systems.

在 WinFormsApp.config文件中粘贴以下内容:

<System.Windows.Forms.ApplicationConfigurationSection>
<add key="DpiAwareness" value="PerMonitorV2" />
</System.Windows.Forms.ApplicationConfigurationSection>

然后创建一个 app.manifest文件,粘贴或注释到这一行:

<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />

在做了以上这些之后,我能够在我的4k 屏幕上得到很好的 DPI 结果。

查看 这个这个了解更多信息。