WinForms 4K和1080p缩放/高DPI?

17

所以,这是场景:我试图使我所有的窗体(它们是Winforms)在4K和1080p也就是高DPI或“dpi-aware”下看起来好看。为了本问题的目的,我有三个窗体:frmCompanyMasterEdit 继承自frmBaseEdit,后者继承自frmBase,后者继承自System.Windows.Forms.Form

我尝试了旧方法,通过在清单中使我的应用程序具有DPI感知性:

  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
    </windowsSettings>
  </application>

除了我可以修复的少量锚定问题之外,该表单在4K下看起来完美,并且看起来与那个版本非常相似,但在1080p下会略微模糊一些。这是4K版本:旧版和新版都为4K

无论如何,我放弃了那种方法,尝试使用.NET 4.7中描述的新方法,针对4.7框架并添加以下代码:

到 app.config

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

到 app.manifest 文件

<!-- Windows 10 compatibility -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
同时,我还将旧代码从app.manifest中删除,以免覆盖新的.NET 4.7方式。我确保将代码放在适当的位置。 所以,在4K下表单看起来很好,就像上面的图片一样,但现在在1080p下它非常缩放,如图所示: 1080p新方式 因此,在4k下,表单看起来很棒,除了一些锚定问题外。无论是旧方式还是正确的大小,但在1080p下有点模糊或者在1080p下不会模糊,但是实际上被缩放了。 我还必须更改所有designer.vb文件中的这两行,如下所示:
Me.AutoScaleDimensions = New System.Drawing.SizeF(96.0!, 96.0!)
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi

我不知道为什么在1080p下,我的内容看起来就是不合适。像我说的,我正在针对4.7 .NET 框架进行开发。我正在使用 Windows 10 的适当版本(版本号为1709/Creator's edition)。在1080p下,缩放比例为100%。此外,我们没有升级到 WPF 的资源。


1
你可能需要升级到.NET Framework 4.7.2预览版 https://blogs.msdn.microsoft.com/dotnet/2018/02/05/announcing-net-framework-4-7-2-early-access-build-3052/,并再次尝试,因为在4.7.1发布时,Rich Lander提到了更多的高DPI区域补丁,https://blogs.msdn.microsoft.com/dotnet/2017/10/17/announcing-the-net-framework-4-7-1/。 - Lex Li
2个回答

17

我已经成功为我的两个针对.NET 4.5和4.7的Winforms应用程序添加了DPI支持。

过去,我曾尝试通过清单文件添加支持,但没有成功。幸运的是,我找到了以下解决方案:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WinformsApp
{
    static class Program
    {
        [DllImport("Shcore.dll")]
        static extern int SetProcessDpiAwareness(int PROCESS_DPI_AWARENESS);

        // According to https://msdn.microsoft.com/en-us/library/windows/desktop/dn280512(v=vs.85).aspx
        private enum DpiAwareness
        {
            None = 0,
            SystemAware = 1,
            PerMonitorAware = 2
        }

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            SetProcessDpiAwareness((int)DpiAwareness.PerMonitorAware);

            Application.Run(new MainForm());
        }
    }
}

上面的代码是您的Program.cs文件应该看起来像的样子。当然,您将不得不将其移植到VB,但这应该很容易做到。

这在Windows 10中完美运行,无需进行任何其他修改。

在我渲染字符串时,我在两个Winforms应用程序中使用像素坐标通过 Graphics 类,导致我的字符串偏移。修复非常简单:

private void DrawString(Graphics g, string text, int x, int y)
{
    using (var font = new Font("Arial", 12))
    using (var brush = new SolidBrush(Color.White))
        g.DrawString(text, font, brush, LogicalToDeviceUnits(x), LogicalToDeviceUnits(y));
}

基本上,我需要使用Control.LogicalToDeviceUnits(int value)来获取像素坐标以进行缩放。

除此之外,我完全不需要修改我的代码。


3
除非是ClickOnce部署的应用程序,否则使用pinvoke没有特别的优势。 只需在清单中使用True / PM即可获得相同的结果。 发现比例因子是使用CreateGraphics()的几个好理由之一。 - Hans Passant
谢谢,我会去查看的。但是我的应用程序没有Main()方法,我们有一个启动窗体。我在考虑将上述代码放在启动窗体的构造函数或其加载事件中? - Chris K
@ChrisK。我已经测试过了,它在构造函数中也能正常工作。 - Nolonar
1
谢谢,我会尝试一下。但是@HansPassant,那不是旧的方法(Per Monitor V1)吗?微软在.NET Framework 4.7中添加了per monitor version 2,你可以在app.config中添加它。 - Chris K
1
经过一番调查,我终于发现为什么我使用 P/Invoke 而不是清单。我的应用程序是一个屏幕保护程序,而清单只在调试期间起作用,但在启动屏幕保护程序时却无效。@HansPassant - Nolonar

11

在我的情况下,按照建议设置进程DPI感知会遇到太多问题:最小窗体大小不正确,移动窗体跨越具有不同DPI的监视器时会随机出现问题,窗体无缘无故裁剪,Segoe UI字体无法正确缩放,在分隔容器内锚定控件将导致所有内容错位浮动等。

还有一个问题是图标变小了,你需要更新你的代码来有选择地加载更高分辨率的按钮、ListView、TreeView等等。

我最终使用的替代方案是使用Windows 10中的"用"GDI缩放"覆盖缩放行为"的设置。 这可以在应用程序的清单中声明。

这里是设置,您可以先尝试并查看它是否符合您的需求:

enter image description here

通过修改项目中的app.manifest文件并添加以下条目,可以自动设置此设置:

<asmv3:application>
  <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">
    <gdiScaling>true</gdiScaling>
  </asmv3:windowsSettings>
</asmv3:application>

代码可能会给您一个错误,指出“asmv3”未被识别。若要添加asmv3名称空间,请修改您的app.manifest文件以包含它:

修改前:


Before:

<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">

之后:

<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接