【问题标题】:Disable DPI awareness for WPF application禁用 WPF 应用程序的 DPI 感知
【发布时间】:2012-12-01 06:33:19
【问题描述】:

美好的一天!

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

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

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

[assembly: System.Windows.Media.DisableDpiAwareness]

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

谁能指出我正确的方向?

谢谢你, 约翰

【问题讨论】:

    标签: wpf scaling dpi


    【解决方案1】:

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

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

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

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

    【讨论】:

      【解决方案2】:

      Calin Pirtea 的回答确实禁用了 DPI 缩放。

      对于那些想要将应用程序拉伸到 100% 以上并接受模糊的人来说,Carlos Borau 的回答很有效。这是 .NET 4.6 (C#) 的一种方式:

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

      1. 右键项目,添加->新建项目
      2. 选择常规->应用程序清单文件

      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>
      

      【讨论】:

        【解决方案3】:

        对于使用 VB.NET 的用户,访问 app.manifest 的方法是:

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

        【讨论】:

          【解决方案4】:

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

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

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

          的代码的快捷方式

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

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

          最后,当计算 dpi 时,结果存储在 UIElement 上称为 DpiScaleXValues 和 DpiScaleYValues 的 2 个列表中。所以你需要将它们初始化为正确的值。

          这是我自己使用的示例代码(仅适用于 .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);
          

          【讨论】:

          • 这个很吸引人,让你得到真正的1:1像素比例。但是,考虑到所有其他已经初始化但无法解决的事情有点可怕。我在 SystemParameters.VirtualScreenWidth 遇到了一个案例。这是我搜索这个问题时想要的,但我觉得我无法使用它。我希望 Microsoft 提供一种受支持的方式来设置基本比例因子。
          • 这在新的 Windows 10 更新 1903 之前一直运行良好,该应用程序现在始终使用每个显示器缩放。有什么想法可以处理它吗?
          【解决方案5】:

          This blog article 解释了如何使用 Decorator 子类来做到这一点。

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

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

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

          【讨论】:

            【解决方案6】:

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

            如果您关闭了 DPI 虚拟化,那么您的应用告诉它所支持的操作系统的内容为零差异,它将始终使用标准缩放。当它打开时,它会在真正的缩放(感知)和只是对整个应用程序(不感知)进行双线性调整大小之间产生差异。

            我刚刚想起另一个警告,如果 Aero Glass 不工作,DPI virt 根本无法工作。

            【讨论】:

              【解决方案7】:

              DPIA 意识

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

              您是否在 XP 上运行?该选项可能不适用于该平台。

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

              • 查看 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(认为您必须尽早调用,即在创建窗口之前)

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

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

              找出 DPI

              您可以通过以下方式了解当前使用的 DPI:

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

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

              进行缩放调整

              • 使用ViewBox 技巧

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

                WPF for LCD screen Full HD

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

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

              【讨论】:

              • 两台机器都在 Windows 7 上运行。我尝试了应用清单选项,但似乎没有用。我会尽快调查其他选项,谢谢您的意见。
              【解决方案8】:

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

              【讨论】:

              • 感谢您的意见!我试过这个,但由于某种原因我的电脑内存不足!起初,当我在 XAML 中添加 ViewBox 标记时,它会挂起很长时间,并且我看到 VS 使用的内存高达 4 Gb 以上。当我尝试编译时,内存不足(在 12 Gb 机器上),所以确实出了点问题,很可能是我在 ViewBox 中所做的事情。
              猜你喜欢
              • 1970-01-01
              • 2011-05-03
              • 1970-01-01
              • 1970-01-01
              • 2021-06-07
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多