创建可识别DPI的应用程序


70

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

有人有主意吗?


你检查检查这个博客对此事还有,我认为它提供了关于该主题良好的信息:telerik.com/blogs/...
checho

FWIW,在最新的Windows 10上,我做的更好,相反:确保我的旧Windows Forms应用程序是DPI的UNAWARE。这迫使Windows 10对winforms使用其默认缩放比例,效果很好。要从代码进行测试,请使用SetProcessDpiAwareness(0)。0 =在某些枚举中不知道-谷歌了解详细信息,需要“ shcore.dll”中的DllImport。建议在app.manifest中完成;可以肯定,我只是提到代码作为测试。
ToolmakerSteve

我在C#Winforms应用程序中遇到问题,该应用程序窗口使用的DPI将从屏幕设置从96 dpi(比例因子125%)更改为120dpi(比例因子100%),但仅在运行可执行文件时出现-问题在VS2013 IDE中运行时不会发生。附加调试器以查找发生更改的地方将产生不可复制的结果。通过调用SetProcessDpiAwareness(0)进行上述修复。
SimonKravis

Answers:


111

编辑:从.NET 4.7开始,Windows窗体改进了对High DPI的支持。在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使用容器字体设置来缩放其内容,并使用不同字体大小的面板比它包含的形式肯定会产生问题。如果表单和表单上的所有容器使用相同的字体大小,则可能会起作用,但是我还没有尝试过。
  • 使用具有更高DPI设置的另一台计算机或虚拟Windows安装(VMware,Virtual PC,VirtualBox)立即测试您的设计。只需从DEV计算机上的/ bin / Debug文件夹中运行已编译的.exe文件即可。

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

编辑:此链接具有很多好的信息,尤其是如果您选择使用AutoScaleMode.DPI:链接到相关的stackoverflow问题


我已经有了FlowLayoutPanel,是否要在每个面板上写上“ this.AutoScaleDimensions = new System.Drawing.SizeF(6F,13F);”?我在哪里写?
$$$

2
我没有用FlowLayoutPanel尝试过此操作,但是在您的每个窗体或usercontrols .Designer.cs文件(由Visual Studio设计器生成的局部类文件)中,都需要设置AutoScaleDimensions和AutoScaleMode。这适用于“标准”形式,在其中您可以使用锚点放置控件,例如。您对它的位置有特定的x和y坐标
Trygve

3
谢谢,非常有用的帖子。我在一个不得不追赶别人的项目中遇到了这个问题。只需从所有* .Designer.cs文件中删除所有AutoScale *行,即可立即使用。
Alexey Yakovenko 2013年

3
我们根据您上面的建议进行的实验表明这是一个很好的建议。**自3年前发布以来,您是否了解到要遵循的其他准则?**我们已经发现一对夫妇别人...张贴在这里: stackoverflow.com/questions/22735174/...
布赖恩·肯尼迪

2
@MuratfromDaminionSoftware:是的,它有一些警告。在发布时(5年前),高DPI屏幕并不是很常见,但是这种情况已经迅速改变。在较高DPI中进行开发的问题是,所有.Designer生成的代码都特定于较高DPI,并且在构建后使所有内容缩放错误都返回到较低DPI。不过,在构建后将其提升到更高的DPI效果很好。
Trygve

22

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


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

  1. 进入表单设计器,然后选择您的表单(通过单击其标题栏)
  2. 按F4打开“属性”窗口,
  3. 然后找到AutoScaleMode属性
  4. 将其从Font(默认)更改为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上非常有效!谢谢
Andrea

如果您查看源链接中的屏幕图像,它们将完全不同。尽管使用DPI模式缩放的方法可能会解决模糊问题,但控件的大小会发生变化,从而影响布局。因此,这是一种易碎的解决方案,必须仔细应用。
亚兹莫夫

在vs 2019(resulion 1920 x 1080)上工作得很好,但是控件的大小有不同的大小,但是我可以接受
Ebrahim ElSayed

16

我终于找到了解决屏幕方向和DPI处理问题的方法。
Microsoft已经提供了一份说明文件,但有一点缺陷,它将完全杀死DPI处理。只需遵循以下文档中“为每个方向创建单独的布局代码”下提供的解决方案即可: http://msdn.microsoft.com/zh-cn/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应用程序。
Neolisk


3

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

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


3

根据经验:

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

我遇到一种情况,将AutoScaleMode设置为字体模式会导致该应用程序崩溃,因为我尝试以中级(125%或120ppi)而不是通常的“ Smaller”(96ppi)运行Windows。正如您所说,我已将AutoScaleMode设置为None,到目前为止一切似乎都很好...
Dan W

1
  1. 如果您希望WinForms应用程序是支持DPI的应用程序,除了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个可能值之一,而control.Anchor可能具有16个可能值之一。

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

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

以上(d。)bool变量的更多信息:

*有时需要一组控件(可能是一个按钮)在同一垂直线上一个接一个地放置,其“锚点”值包括“右”而不是“左”,或者它们需要在一个水平线上一个接一个地放置,锚点值包括“底部”而不是“顶部”,在这种情况下,您需要重新计算控件的“位置”值。

*如果控件的“锚点”包含“顶部和底部”和/或“左侧”和“右侧”,则需要重新分解控件的“大小和位置”值。

ScaleByDPI函数的用途:

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

b。同样,在将任何控件动态添加到Form时,对ScaleByDPI([ControlName])的调用也是如此。

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

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

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

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

  5. 在ComboBox和TextBox等控件中,更改Control.Size.Hieght无效。在这种情况下,更改Control.Font.Size将固定控件的高度。

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


AutoScaleMode.Font是否不是大多数应用程序的首选,而AutoScaleMode.DPI仅对试图占据屏幕一定百分比的应用程序有用?
乔恩·库姆斯

-1

由于Winform应用程序表单可能包含内容控件和图像,因此允许系统调整窗口大小不是解决方案,但是,如果您能够按DPI分辨率管理一个表单,并具有正确缩放的图像……那不是一个好主意,因为随着屏幕尺寸的增加,字体大小会减小。

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

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

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

最后但并非最不重要的:

请勿以不同于原始分辨率的其他分辨率在Visual Studio中打开您的应用程序,否则当您打开每个表单时,“所有控件”都会移动并调整其大小,并且无法退回...

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


您是否尝试过使用AutoScaleMode.None而不是将表单的DPI设置回96 。
Neolisk 2014年

5
这将一直有效,直到您的用户拥有高清监视器为止。然后,您将获得不可读的内容。这种方法实质上禁用了高清支持。
史蒂夫·谢尔顿
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.