禁用 WPF 应用程序的 DPI 感知

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13858665/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 23:37:17  来源:igfitidea点击:

Disable DPI awareness for WPF application

wpfscalingdpi

提问by Johan Verbelen

Good day!

再会!

I've been working on a WPF app for some time now (as a learning experience and oh boy it was a learning experience) and it's finally ready for release. Release means installing it on my HTPC where it will be used to browse my movie collection.

我已经在 WPF 应用程序上工作了一段时间(作为一种学习体验,哦,男孩,这是一种学习体验),它终于准备好发布了。发布意味着将它安装在我的 HTPC 上,用于浏览我的电影收藏。

I designed it on my PC which runs 1920*1080 but at the normal DPI setting, while the HTPC/TV is running at the same resolution but a higher DPI setting for obvious reasons.

我在我的 PC 上设计它,它运行 1920*1080 但在正常 DPI 设置下,而 HTPC/TV 运行在相同的分辨率下,但出于显而易见的原因,DPI 设置更高。

The problem is my app goes bonkers on the HTPC, messing up pretty much everything as far as visuals go. I know this is due to bad design (mea culpa), but since it's an application that will only be used by me I'm looking for an quick fix, not a full redesign. I read it would be possible to stop the app from being DPI aware by adding the following to AssemblyInfo.cs:

问题是我的应用程序在 HTPC 上变得疯狂,就视觉效果而言几乎把所有东西都搞砸了。我知道这是由于糟糕的设计(mea culpa),但由于它是一个仅供我使用的应用程序,我正在寻找快速修复,而不是完全重新设计。我读到可以通过将以下内容添加到 AssemblyInfo.cs 来阻止应用程序感知 DPI:

[assembly: System.Windows.Media.DisableDpiAwareness]

However, it doesn't seem to have any effect and the behavior of the app remains the same.

但是,它似乎没有任何效果,应用程序的行为保持不变。

Could anyone point me in the right direction?

有人能指出我正确的方向吗?

Thank you, Johan

谢谢你,约翰

回答by Colin Smith

DPIAwareness

DPIA意识

Just some ideas (not tried):

只是一些想法(未尝试):

Are you running on XP? That option might not work on that platform.

你在XP上运行吗?该选项可能不适用于该平台。

What follows are probably just different ways to set the same DpiAwareness option:

以下可能只是设置相同 DpiAwareness 选项的不同方法:

  • look at the "compatibility mode" settings on your EXE...right click it's properties...and turn on "Disable display scaling"

  • create a manifest, and say you aren't aware

    <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" >
     ...
      <asmv3:application>
        <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
          <dpiAware>false</dpiAware>
        </asmv3:windowsSettings>
      </asmv3:application>
     ...
    </assembly>
    
  • call SetProcessDPIAware(think you have to call this early i.e. before Window is created)

    http://msdn.microsoft.com/en-gb/library/windows/desktop/ms633543(v=vs.85).aspx

  • 查看 EXE 上的“兼容模式”设置...右键单击它的属性...然后打开“禁用显示缩放”

  • 创建一个清单,并说你不知道

    <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" >
     ...
      <asmv3:application>
        <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
          <dpiAware>false</dpiAware>
        </asmv3:windowsSettings>
      </asmv3:application>
     ...
    </assembly>
    
  • 调用SetProcessDPIAware(认为​​您必须在创建 Window 之前尽早调用它)

    http://msdn.microsoft.com/en-gb/library/windows/desktop/ms633543(v=vs.85).aspx

You can call IsProcessDPIAwareto check if your apps process has had the setting applied to it or not.

您可以致电IsProcessDPIAware检查您的应用程序进程是否已应用该设置。

Finding out the DPI

找出 DPI

Here's how you find out what DPI you are currently using:

以下是您了解当前使用的 DPI 的方法:

Access CompositionTargetvia the PresentationSourceof your Windowto find out what DPI scaling it's using.....you can then use the values to do some scaling adjustments i.e. scale down your "stuff" (whose sizes/length/etc are specified in Device Independent Units), so that when its scaled up due to a higher DPI being in effect it doesn't explode the physical pixel usage (...this can be done in various ways e.g. ViewBox, or calculations on widths, etc on elements ).

CompositionTarget通过PresentationSource您的访问Window以找出它正在使用的 DPI 缩放......然后您可以使用这些值进行一些缩放调整,即缩小您的“东西”(其大小/长度/等在设备独立单位中指定) ,因此当它由于更高的 DPI 有效而放大时,它不会爆炸物理像素的使用(...这可以通过各种方式完成,例如ViewBox,或对元素的宽度等进行计算)。

double dpiX, double dpiY;
PresentationSource presentationsource = PresentationSource.FromVisual(mywindow);

if (presentationsource != null) // make sure it's connected
{
    dpiX = 96.0 * presentationsource.CompositionTarget.TransformToDevice.M11;
    dpiY = 96.0 * presentationsource.CompositionTarget.TransformToDevice.M22;
}

Do Scaling Adjustments

进行缩放调整

  • use the ViewBoxtrick

    See this answer I made before that allowed a Canvasto use positions that were interpreted as "native" pixel no matter what the DPI scaling.

    WPF for LCD screen Full HD

  • access the TransFormToDevicescaling matrix on the CompositionTarget, and from that calculate a new matrix that just undoes that scaling and use that in LayoutTransformor RenderTransform.

  • use a method (maybe even put it in a Converter) that modifies a DIP (Device Independent Position) position/length on an explicit basis....you might do that if you want your Window Size to match a particular pixel size.

  • 使用ViewBox技巧

    请参阅我之前所做的这个答案,Canvas无论 DPI 缩放如何,都可以使用被解释为“本机”像素的位置。

    液晶屏全高清WPF

  • 访问 上的TransFormToDevice缩放矩阵CompositionTarget,并从中计算出一个新矩阵,该矩阵只是撤消该缩放并在LayoutTransform或 中使用它RenderTransform

  • 使用一种在显式基础上修改 DIP(设备独立位置)位置/长度的方法(甚至可能将它放在转换器中)......如果您希望窗口大小与特定像素大小匹配,您可以这样做。

回答by ulatekh

This blog articleexplains how to use a Decoratorsubclass to do just that.

这篇博客文章解释了如何使用装饰器子类来做到这一点。

In short, create a class like this:

简而言之,创建一个这样的类:

namespace MyNamespace
{
    public class DpiDecorator : Decorator
    {
        public DpiDecorator()
        {
            this.Loaded += (s, e) =>
            {
                Matrix m = PresentationSource.FromVisual(this).CompositionTarget.TransformToDevice;
                ScaleTransform dpiTransform = new ScaleTransform(1 / m.M11, 1 / m.M22);
                if (dpiTransform.CanFreeze)
                    dpiTransform.Freeze();
                this.LayoutTransform = dpiTransform;
            };
        }
    };
};

Then add something like xmlns:custom="clr-namespace:MyNamespace"to your top-level window definition, then enclose your DPI-independent user-interface with <custom:DpiDecorator>...</custom:DpiDecorator>.

然后将类似的内容添加xmlns:custom="clr-namespace:MyNamespace"到您的顶级窗口定义中,然后将您的 DPI 独立用户界面用<custom:DpiDecorator>...括起来</custom:DpiDecorator>

回答by Calin Pirtea

None of the above truly disable the dpi scaling in WPF.

以上都没有真正禁用 WPF 中的 dpi 缩放。

This is how dpi scaling is calculated in .net 4.6 by WPF: 1) HwndTarget which is the CompositionTarget used by all visuals. 2) UIElement which is the base class for visuals and the place where dpi scaling calculation results are stored.

这是 WPF 在 .net 4.6 中计算 dpi 缩放的方式:1) HwndTarget,它是所有视觉效果使用的 CompositionTarget。2) UIElement,它是视觉对象的基类,也是存储dpi缩放计算结果的地方。

WPF makes sure the global scaling is calculated once when first visual is prepared. This is controlled by a boolean on HwndTarget {private static bool _setDpi = true}. After calculating the dpi, the _setDpi field is set to false and dpi aware methods are shortcut using code like this {if (!HwndTarget._setDpi) return;}

WPF 确保在准备第一个视觉对象时计算一次全局缩放。这由 HwndTarget {private static bool _setDpi = true} 上的布尔值控制。计算 dpi 后,_setDpi 字段设置为 false,dpi 感知方法是使用这样的代码的快捷方式 {if (!HwndTarget._setDpi) return;}

A similar thing happens for every UIElement, which has the same pattern using {private static bool _setDpi = true} to allow calculation first time.

每个 UIElement 都会发生类似的事情,它具有相同的模式,使用 {private static bool _setDpi = true} 允许第一次计算。

Next check comes from ProcessDpiAwareness which can be None, Process, or Monitor. In order to stop WPF from considering individual monitors you needs to set ProcessDpiAwareness to Process (int 1).

下一个检查来自 ProcessDpiAwareness,它可以是 None、Process 或 Monitor。为了阻止 WPF 考虑单个监视器,您需要将 ProcessDpiAwareness 设置为 Process (int 1)。

Finally when dpi is calculated the result is stored in 2 lists called DpiScaleXValues and DpiScaleYValues on UIElement. So you need to initialize these to correct values.

最后,当计算 dpi 时,结果存储在 UIElement 上名为 DpiScaleXValues 和 DpiScaleYValues 的 2 个列表中。因此,您需要将这些初始化为正确的值。

Here is a sample code I use for myself (only works for .net 4.6):

这是我自己使用的示例代码(仅适用于 .net 4.6):

        var setDpiHwnd = typeof(HwndTarget).GetField("_setDpi", BindingFlags.Static | BindingFlags.NonPublic);
        setDpiHwnd?.SetValue(null, false);

        var setProcessDpiAwareness = typeof(HwndTarget).GetProperty("ProcessDpiAwareness", BindingFlags.Static | BindingFlags.NonPublic);
        setProcessDpiAwareness?.SetValue(null, 1, null);

        var setDpi = typeof(UIElement).GetField("_setDpi", BindingFlags.Static | BindingFlags.NonPublic);

        setDpi?.SetValue(null, false);

        var setDpiXValues = (List<double>)typeof(UIElement).GetField("DpiScaleXValues", BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null);

        setDpiXValues?.Insert(0, 1);

        var setDpiYValues = (List<double>)typeof(UIElement).GetField("DpiScaleYValues", BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null);

        setDpiYValues?.Insert(0, 1);

回答by Joe White

A quick fix would be just to wrap your Window's contents in a Viewbox. If the Viewbox's immediate child has an explicit Width and Height, and if the aspect ratio is the same on both machines, then this should get you up and running pretty much immediately.

一个快速的解决方法是将 Window 的内容包装在Viewbox 中。如果 Viewbox 的直接子节点具有明确的 Width 和 Height,并且两台机器上的纵横比相同,那么这应该可以让您立即启动并运行。

回答by SilverbackNet

DpiAwarenessonly affects DPI Virtualization. In Windows it shows up as "Use Windows XP style scaling" in the Windows 7 & 8 custom resolution box, where checked means disabled and unchecked means enabled. (Remember to apply on the main font size screen if you change it! OK'ing the box is not enough.)

DpiAwareness仅影响 DPI 虚拟化。在 Windows 中,它在 Windows 7 和 8 自定义分辨率框中显示为“使用 Windows XP 样式缩放”,其中选中表示禁用,未选中表示启用。(如果更改,请记住在主字体大小屏幕上应用!确定框是不够的。)

If you have DPI Virtualization turned off, then it makes zero difference what your app tells the OS it supports, it will always use standard scaling. When it's on, it makes the difference between true scaling (aware) and just slapping a bilinear resize on the whole app (unaware).

如果您关闭了 DPI 虚拟化,那么您的应用程序告诉它支持的操作系统的内容为零,它将始终使用标准缩放。当它打开时,它会区分真正的缩放(有意识)和只是在整个应用程序上进行双线性调整大小(无意识)。

I just remembered another caveat, DPI virt won't work at all without Aero Glass working.

我只记得另一个警告,如果没有 Aero Glass 工作,DPI virt 将根本无法工作。

回答by Carlos Borau

For those using VB.NET, the way to access the app.manifest is:

对于那些使用 VB.NET 的人,访问 app.manifest 的方式是:

enter image description here

在此处输入图片说明

and then uncomment this part and set it to "false"

然后取消注释这部分并将其设置为“false”

enter image description here

在此处输入图片说明

回答by Siamca

Calin Pirtea's answer truly disable the DPI scaling.

Calin Pirtea 的回答真正禁用了 DPI 缩放。

For those who wants to stretch the application for a display scale more than 100% and accept blurry, Carlos Borau's answer works. Here's is a way for .NET 4.6 (C#):

对于那些想要将应用程序扩展到超过 100% 的显示比例并接受模糊的人来说,Carlos Borau 的回答有效。这是 .NET 4.6 (C#) 的一种方式:

1 Add a manifest file to your application project

1 将清单文件添加到您的应用程序项目

  1. right-click project, Add->New Item
  2. Choose General->Application Manifest File
  1. 右键单击项目,添加->新建项目
  2. 选择常规->应用程序清单文件

2 Uncomment following and set dpiAware = false:

2 取消注释并设置 dpiAware = false:

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

回答by Paul G

After updating to .NET 4.8 runtime on Windows 10, I was getting the following exception with Calin's approach for setting ProcessDpiAwareness

在 Windows 10 上更新到 .NET 4.8 运行时后,Calin 设置 ProcessDpiAwareness 的方法出现以下异常

System.ArgumentException: 'Object of type 'System.Int32' cannot be converted to type 'System.Nullable`1[MS.Win32.NativeMethods+PROCESS_DPI_AWARENESS]'.'

System.ArgumentException: 'System.Int32' 类型的对象无法转换为类型'System.Nullable`1[MS.Win32.NativeMethods+PROCESS_DPI_AWARENESS]'。

Changing code to below resolves the Exception, but I am not sure if it achieves the same result.

将代码更改为以下可以解决异常,但我不确定它是否能达到相同的结果。

var processDpiAwareness = typeof(HwndTarget).GetProperty("ProcessDpiAwareness", BindingFlags.Static | BindingFlags.NonPublic);

if (processDpiAwareness != null)
{
      var underlyingType = Nullable.GetUnderlyingType(processDpiAwareness.PropertyType);

      if (underlyingType != null)
      {
           var val = Enum.Parse(underlyingType, "PROCESS_SYSTEM_DPI_AWARE");
           processDpiAwareness.SetValue(null, val, null);
      }
 }