禁用WPF应用程序的DPI感知

时间:2012-12-13 11:15:10

标签: wpf scaling dpi

美好的一天!

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

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

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

[assembly: System.Windows.Media.DisableDpiAwareness]

然而,它似乎没有任何影响,应用程序的行为保持不变。

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

谢谢你, 约翰

8 个答案:

答案 0 :(得分:32)

DPIAwareness

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

你在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(认为您必须尽早致电,即在创建Window之前)

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

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

找出DPI

以下是您如何找到目前使用的DPI的方法:

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

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

    看到我之前做出的这个答案允许ViewBox使用被解释为“原生”像素的位置,无论什么DPI缩放。

    WPF for LCD screen Full HD

  • 访问Canvas上的TransFormToDevice缩放矩阵,并从中计算出一个新矩阵,该矩阵只撤消该缩放并在CompositionTargetLayoutTransform中使用该矩阵

  • 使用一种方法(甚至可以把它放在转换器中)明确修改DIP(设备独立位置)位置/长度....如果你希望你的窗口大小匹配你可能会这样做特定的像素大小。

答案 1 :(得分:6)

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

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

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"之类的内容,然后用<custom:DpiDecorator> ... </custom:DpiDecorator>包含与DPI无关的用户界面。

答案 2 :(得分:3)

以上都没有真正禁用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的列表中。因此,您需要将这些值初始化为正确的值。

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

答案 3 :(得分:2)

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

答案 4 :(得分:2)

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

如果您关闭了DPI虚拟化,那么它会使您的应用程序告诉它支持的操作系统没有区别,它将始终使用标准缩放。当它打开时,它会区分真正的缩放(感知)和在整个应用程序上调整双线性调整大小(不知道)。

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

答案 5 :(得分:0)

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

enter image description here

然后取消注释此部分并将其设置为&#34; false&#34;

enter image description here

答案 6 :(得分:0)

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>

答案 7 :(得分:0)

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