WinForms:如何在高DPI、多显示器环境下(PerMonitorV2),修复表单的不正确缩放,特别是在不同分辨率/缩放比例的情况下。

5
在 Winforms 应用程序中,我遵循了如何编写自适应系统字体和 DPI 设置的 Winforms 代码.Net Framework 高 DPI 支持中的一般指导来启用 PerMonitorV2 DPI 缩放。我在一个安装了 Windows 10 Anniversary Update (1607) 之后的系统上使用 .NET Framework 4.8 来利用最新的高 DPI 支持功能。
如果应用程序在主屏幕或任何具有与主屏幕相同缩放比例的显示器上启动,则 DPI 缩放看起来很好,但如果任何窗体(应用程序的主窗体或一个辅助顶级窗体)首先显示在与主屏幕不同 DPI 的显示器上,则缩放会完全错误。例如,如果该应用程序在一个 4k/250% 缩放的笔记本电脑屏幕上启动(并有四个其他显示器的缩放比例为 1920x1080/100%),则这个窗体在 4k 屏幕上以 1:1 的缩放比例显示,并且只显示为一个小的 1 平方英寸(仅标题和菜单栏正确缩放):显示错误缩放比例的图像
该问题似乎是由于这个窗体的当前自动缩放维度在这些情况下未被正确设置所导致的。它们似乎被设置为主屏幕的当前“维度”,而不是窗体正在显示的屏幕。但是,如果该窗体首先显示在主屏幕上,然后移动到具有不同 DPI 的屏幕上,则当前自动缩放维度会被正确更新以反映目标屏幕的实际 DPI,并且该窗体会被正确缩放。因此,例如,如果我将主屏幕设置为 4k/250% 屏幕,然后在一个 1920x1080/100% 屏幕上启动应用程序,当前自动缩放维度(不正确地)设置为 4k/250% 屏幕的维度,导致该窗体被极度过度缩放。但是如果我在主要的 4k/250% 屏幕上启动该应用程序,那么当它首次显示时,它会被正确缩放,然后当它被拖动到其他显示器上(和回到主屏幕)时,它也会被正确缩放。总之,当窗体首次显示时,当前自动缩放维度似乎总是被设置为主监视器的尺寸而不是该窗体正在显示的屏幕的尺寸。 有人知道如何解决这种情况吗?

1
https://github.com/dotnet/winforms/issues/4854 - Hans Passant
谢谢@HansPassant!非常有启发性,感激不尽。虽然我还不是Git专家,但如果我正确阅读了那个链接,我描述的问题是一个已知的错误,已经得到了修正,但修复程序没有包含在.NET 6.01中。在这个修复程序发布之前,我将使用下面的解决方案,然后尝试过渡到.NET。非常感谢! - JohnC
2个回答

3
我想到的最佳解决方案就是强制应用程序总是在主屏幕上启动,无论它从哪个屏幕启动。这似乎可以解决所有与主表单的初始缩放和随后在多个显示器之间拖动时的缩放相关问题。

public Form1()
    {
    this.Font = SystemFonts.IconTitleFont;

    // Force the main Form of the app to always open on the primary screen
    // to get scaling to work.

    this.Location = Screen.PrimaryScreen.WorkingArea.Location;
    this.StartPosition = FormStartPosition.Manual;
    InitializeComponent();
    ...

对于应用程序生成的次要窗体,我发现如果先在主屏幕上显示它们,然后隐藏并重新在包含应用程序主窗体(我想要它们显示的地方)的屏幕上显示它们,则它们会被正确缩放。在某些情况下,我发现仅调用 f.PerformLayout() 而不实际在主屏幕上显示窗体就足够了,但并非所有情况都适用。

private void secondFormToolStripMenuItem_Click(object sender, EventArgs e)
    {
    FormExtra f = new FormExtra(this);

    // First display the Form on the primary screen to make scaling correct

    f.StartPosition = FormStartPosition.Manual;
    f.Location = Screen.PrimaryScreen.WorkingArea.Location;
    f.Show();

    // Then hide it and move it where we really want it

    f.Hide();
    f.StartPosition = FormStartPosition.CenterParent;
    f.ShowDialog();
    }

这种解决方案并不理想,因为表格会在另一个屏幕上闪烁,看起来相当笨拙。然而,无论应用程序在哪个显示器上启动,此解决方案似乎都能正确缩放应用程序中的所有表单。迄今为止,我已经在两个不同的多显示器配置上进行了测试。


我发布这篇文章的主要目的是为了分享这些信息,以防它对其他人有用,因为我在这个问题上找不到太多信息,并且花费了相当长的时间才想出这个笨拙的解决方案。希望有人能提供更好的解决方案! - JohnC
2
不错的自问自答问题。感谢您记录这个。加1给大家。 - Flydog57
如果我的主屏幕(也是唯一的屏幕)默认为125%缩放(这是我的笔记本HiDPI屏幕),我该如何使我的WinForms程序以100%缩放运行,并保持字体大小与在设置为100%缩放的监视器上运行时相同数量的像素。 - Felix An

0
我有同样的问题。我的主屏幕是4K,Windows缩放到175%。我的副屏幕是2560x1440,缩放为125%。当在与用户之前拖动到的相同位置打开一个表单时,如果该位置在副屏幕上,它会变得太大。只要用户稍微移动一下表单,屏幕就会根据缩放重新调整大小。
我所做的是在表单的加载子程序中添加: Me.Left = Me.Left + 1(vb.net,但我认为C#类似)
Private Sub frmSelectNewIx_Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load
            Me.Left = Me.Left + 1
            AddHandler LocationChanged, AddressOf SaveNewFormPosition ' In a module to save the forms new position using a timer.

......

这会强制屏幕移动,并且自动缩放会启动,而不会出现明显的视觉故障。

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