创建 DPI 感知应用程序

Posted

技术标签:

【中文标题】创建 DPI 感知应用程序【英文标题】:Creating a DPI-Aware Application 【发布时间】:2011-05-03 19:29:58 【问题描述】:

我有一个 C# 中的表单应用程序。当我更改显示器的 DPI 时,所有控件都会移动。 我使用了代码this.AutoScaleMode = AutoScaleMode.Dpi,但并没有避免问题。

有人有想法吗?

【问题讨论】:

你也检查一下这个博客,我认为它提供了关于这个主题的很好的信息:telerik.com/blogs/… FWIW,在最新的 Windows 10 上,我在相反的情况下取得了更好的成功:确保我的旧 Windows 窗体应用程序不知道 DPI。这迫使 Windows 10 对 winforms 使用其默认缩放,效果很好。从代码中测试,SetProcessDpiAwareness(0)。 0 = 在某些枚举中不知道 - 谷歌了解详细信息,需要来自“shcore.dll”的 DllImport。建议在 app.manifest 中完成此操作;可以肯定的是,我只是提到代码作为测试。 我在 C# Winforms 应用程序中遇到问题,其中应用程序窗口使用的 DPI 将从屏幕设置 96 dpi(比例因子 125%)更改为 120dpi(比例因子 100%),但是仅在运行可执行文件时 - 在 VS2013 IDE 中运行时不会出现问题。附加调试器以找出发生更改的位置会产生不可重现的结果。通过调用 SetProcessDpiAwareness(0) 修复如上。 【参考方案1】:

编辑:从 .NET 4.7 开始,Windows 窗体改进了对高 DPI 的支持。 Read more about it on docs.microsoft.com 不过它只适用于 Win 10 Creators Update 及更高版本,因此根据您的用户群,使用它可能还不可行。


困难,但并非不可能。当然,最好的选择是迁移到 WPF,但这可能不可行。

我在这个问题上花了很多时间。以下是一些规则/指南,可以在没有 FlowLayoutPanel 或 TableLayoutPanel 的情况下使其正常工作:

始终以默认 96 DPI (100%) 编辑/设计您的应用程序。如果您使用 120DPI (125% f.ex) 进行设计,那么当您稍后回到 96 DPI 使用它时会变得非常糟糕。 我已经成功使用了 AutoScaleMode.Font,但我没有太多尝试 AutoScaleMode.DPI。 确保在所有容器(表单、面板、标签页、用户控件等)上使用默认字体大小。 8,25 像素。最好不要在 .Designer.cs 文件中为所有容器设置它,以便它使用容器类的默认字体。 所有容器必须使用相同的AutoScaleMode 确保所有容器都在 Designer.cs 文件中设置了以下行:

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

如果您需要在标签/文本框等上设置不同的字体大小,请为每个控件设置它们,而不是在容器类上设置字体,因为 winforms 使用容器字体设置来缩放其内容并让 f.ex 面板带有保证与包含表单不同的字体大小会产生问题。如果表单和表单上的所有容器使用相同的字体大小,它可能会起作用,但我没有尝试过。 使用具有更高 DPI 设置的另一台计算机或虚拟 Windows 安装(VMware、Virtual PC、VirtualBox)立即测试您的设计。只需从 DEV 机器上的 /bin/Debug 文件夹中运行已编译的 .exe 文件即可。

我保证,如果您遵循这些准则,您会没事的,即使您已经放置了带有特定锚点的控件并且不使用流程面板。我们有一个以这种方式构建的应用程序部署在数百台具有不同 DPI 设置的机器上,我们不再有任何抱怨。所有表单/容器/网格/按钮/文本字段等大小都像字体一样正确缩放。图像也可以,但在高 DPI 下它们往往会有点像素化。

编辑:这个链接有很多很好的信息,特别是如果你选择使用 AutoScaleMode.DPI:link to related *** question

【讨论】:

我已经有了 FlowLayoutPanel,我要不要在每个面板上写“this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);”?我在哪里写? 我没有用 FlowLayoutPanel 尝试过这个,但是在你的每个表单或用户控件 .Designer.cs 文件(由 Visual Studio 设计器生成的部分类文件)中,你需要设置 AutoScaleDimensions 和 AutoScaleMode。这适用于“正常”表单,您可以在其中放置带有锚点的控件,例如。你有一个特定的 x 和 y 坐标作为它的位置 谢谢,非常有用的帖子。我在一个项目中遇到了这个问题,我不得不在别人之后解决这个问题。只需从所有 *.Designer.cs 文件中删除所有 AutoScale* 行,现在就可以使用了。 我们对您上述建议的实验表明这是一个很好的建议。 ** 自从您在 3 年前发布此内容以来,您是否学到了任何其他要遵循的准则? ** 我们找到了其他几个...发布在这里:***.com/questions/22735174/… @MuratfromDaminionSoftware:是的,它有一些注意事项。在发布时(5 年前),高 DPI 屏幕并不常见,但这种情况发生了迅速变化。使用更高 DPI 进行开发的问题在于,所有 .Designer 生成的代码都特定于更高的 DPI,并且在构建后返回到更低的 DPI 会导致一切缩放错误。不过,在构建后提升到更高的 DPI 效果很好。【参考方案2】:

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


如何在高 dpi 设置中修复模糊的 Windows 窗体:

    转到表单设计器,然后选择您的表单(通过单击 它的标题栏) 按 F4 打开“属性”窗口, 然后找到 AutoScaleMode 属性 将其从 字体(默认) 更改为 Dpi

现在,转到 Program.cs(或 Main 方法所在的文件)并将其更改为:

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

保存并编译。现在您的表单应该又看起来酥脆了。


来源: http://crsouza.com/2015/04/13/how-to-fix-blurry-windows-forms-windows-in-high-dpi-settings/

【讨论】:

这在带有 VS2017 的 win10 上运行良好!谢谢 如果您查看源链接中的屏幕图像,它们是完全不同的。虽然使用 DPI 模式缩放的方法可能会修复模糊,但控件的大小会发生变化,从而影响布局。因此,这是一种脆弱的解决方案,必须小心应用。 在 vs 2019 (resulion 1920 x 1080) 上效果很好,但是控件的大小有不同的大小,但我可以接受【参考方案3】:

我终于找到了解决屏幕方向和 DPI 处理问题的方法。 微软已经提供了一个文档来解释它,但有一个小缺陷会完全破坏 DPI 处理。 只需按照以下文档中“为每个方向创建单独的布局代码”下提供的解决方案 http://msdn.microsoft.com/en-us/library/ms838174.aspx

然后是重要的部分! 在 Landscape() 和 Portrait() 方法的代码中,在每个方法的最后添加以下几行:

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

对我来说就像魅力一样。

【讨论】:

Creating Separate Layout Code for Each Orientation 部分与原始问题无关,一个winform应用程序。【参考方案4】:

看起来这是 Windows 的问题。取出这两条线可以解决所有问题。

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

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

Anchor not work during changing text size to 125% @ Telerik forums

【讨论】:

【参考方案5】:

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

对于简单的应用程序,它可以在合理的时间内完成,但对于大型应用程序,它确实需要大量的工作。

【讨论】:

【参考方案6】:

经验:

除非关键,否则不要对 Windows 窗体使用 DPI 感知 为此始终在您的应用中的所有表单和用户控件上将AutoScaleMode 属性设置为None 结果:DPI 设置更改时所见即所得的界面类型

【讨论】:

我遇到这样一种情况,即当我尝试在中等(125% 或 120ppi)而不是通常的“小”(96ppi)中运行 Windows 时,将 AutoScaleMode 设置为字体模式会导致应用程序崩溃.正如你所说,我将 AutoScaleMode 设置为 None 到目前为止一切都很好......【参考方案7】:

我为此苦苦挣扎了一段时间,最终我找到了适用于 Windows 10 和可能的其他系统的超级简单的解决方案。

在你的 WinForms App.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 结果。

查看this 和this 了解更多信息。

【讨论】:

【参考方案8】:
    如果您希望您的 WinForms 应用程序是 DPI-Aware 应用程序,除了 Trygve 好的答案,如果您有大项目,您可能希望自动缩放您的表单及其内容,您可以通过创建 ScaleByDPI 函数来做到这一点:

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

ScaleByDPI函数特别说明:

一个。对于所有具有默认字体大小的控件,您需要将其 Font.Size 设置为 8.25。

b.您可以通过 (control.CreateGraphics().DpiX / 96) 和 (control.CreateGraphics().DpiY / 96) 获取 devicePixelRatioX 和 devicePixelRatioY 值。

c。您将需要通过基于 control.Dock 和 control.Anchor 值的算法来缩放 Control.Size 和 Control.Location。请注意,control.Dock 可能有 6 个可能值中的 1 个,而 control.Anchor 可能有 16 个可能值中的 1 个。

d。该算法需要为下一个布尔变量 isDoSizeWidth、isDoSizeHeight、isDoLocationX、isDoLocationY、isDoRefactorSizeWidth、isDoRefactorSizeHeight、isDoRefactorLocationX、isDoRefactorLocationY、isDoClacLocationXBasedOnRight、isDoClacLocationYBasedOnBottom 设置值。

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

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

*有时需要将一组控件(可能是按钮)一个接一个地放在同一垂直线上,并且它们的Anchor值包含Right但不包含Left,或者需要一个接一个地放在同一水平线上,并且它们的Anchor值包括Bottom但不包括Top,这种情况下需要重新计算控件的Location值。

*如果 Anchor 包含 Top & Bottom 和\或 Left & Right 的控件,您将需要重新考虑控件的 Size & Location 值。

ScaleByDPI函数的使用:

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

b.此外,当动态添加任何控件到对 ScaleByDPI([ControlName]) 的 Form 调用时。

    当您在构造函数结束后动态设置任何控件的大小或位置时,创建并使用以下函数之一以获取大小或位置的缩放值:ScaleByDPI_X \ ScaleByDPI_Y \ ScaleByDPI_Size \ ScaleByDPI_Point

    要将您的应用程序标记为可识别 DPI,请将 dpiAware 元素添加到应用程序的程序集清单中。

    将所有Control.Font的GraphicsUnit设置为System.Drawing.GraphicsUnit.Point

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

    在 ComboBox 和 TextBox 等控件中,更改 Control.Size.Hieght 没有任何影响。在这种情况下,更改 Control.Font.Size 将修复控件的高度。

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

【讨论】:

AutoScaleMode.Font 不是大多数应用程序的首选,AutoScaleMode.DPI 仅对试图占据一定比例屏幕的应用程序有用吗? 我们是否必须编辑 Designer.cs 文件? Winforms 抱怨用户编辑该文件,因为当您在 VS 设计器本身中创建 GUI 时,它应该由 VS 自动创建。将它添加到 Form1_Load 事件中还不够吗?【参考方案9】:

由于 Winform 应用程序表单可能包含控件和图像,因此允许系统调整您的窗口大小不是一个解决方案,但如果您可以设法让每个 DPI 分辨率的表单具有适当缩放的图像...好主意,因为随着屏幕大小的增加,字体大小会减小。

当使用不同的 DPI 分辨率时,系统会强制您的表单重新定义其控件的大小、位置和字体,但不是图像,解决方案是在加载时在运行时更改表单的 DPI,以便一切恢复到原始大小和位置。

这是一个可能的解决方案,我已经用一个纸牌游戏应用程序对其进行了测试,其中我有大约 80 个图像按钮、TabControls 等。

在每个表单form_Load事件中,添加这段代码sn -p:

  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.startup 事件中设置一个 DPIFactor 全局变量。

最后但并非最不重要的一点:

不要在 Visual Studio 中以不同于原始分辨率的分辨率打开您的应用程序,否则您的所有控件将在您打开每个表单时移动和调整大小,并且无法返回...

希望对您有所帮助,祝您编程愉快。

【讨论】:

您是否尝试过使用AutoScaleMode.None,而不是将表单的 DPI 设置回 96? 这在您的用户拥有高清显示器之前有效。然后你会得到不可读的内容。这种方法本质上是禁用高清支持。

以上是关于创建 DPI 感知应用程序的主要内容,如果未能解决你的问题,请参考以下文章

SetWindowPos()跨进程DPI感知

创建后更改窗口的 dpi 感知

从外部窗口的 GetWindowRect 获取 DPI 感知正确的 RECT

在 win32 应用程序中动态设置 DPI 感知级别

稍后更改组合框的项目高度(用于 DPI 感知)

设置进程 DPI 感知,以便系统补偿缩放因子