【问题标题】:How can I make Windows 8.1 aware that my Delphi application wants to support Per Monitor DPI?如何让 Windows 8.1 知道我的 Delphi 应用程序想要支持 Per Monitor DPI?
【发布时间】:2015-01-07 05:59:33
【问题描述】:

我试图让 Windows 8.1 识别我一直在尝试构建的 Delphi XE6 应用程序(一个演示程序),并让它识别我的应用程序是 Per-Monitor DPI 感知的,纯粹是通过 Manifest 技术。 Delphi XE6(以及所有其他类似的 Delphi 最新版本)使添加清单变得容易,在项目选项中,我已经这样做了。

这是我使用 MSDN 资源确定的 .manifest 内容。我怀疑它可能有点不正确。

如果您想尝试此清单,请创建一个空的 VCL 应用程序,将此内容用作您的清单,然后添加代码(代码目前附在我对这个问题的回答中)。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" >
  <!-- Per Monitor DPI Awareness in Windows 8.1 uses asmv3:application + asmv3:windowsSettings -->
  <asmv3:application>
    <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
      <dpiAware>True</dpiAware>
    </asmv3:windowsSettings>
  </asmv3:application>

  <!-- Dear Microsoft, Don't Lie to Me About What Version of Windows I am On -->
  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
    <application>
      <!-- Windows Vista and Windows Server 2008 -->
      <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
      <!-- Windows 7 and Windows Server 2008 R2 -->
      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
      <!-- Windows 8 and Windows Server 2012 -->
      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
      <!-- Windows 8.1 and Windows Server 2012 R2 -->
      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
    </application>
  </compatibility>
  <dependency>
    <dependentAssembly>
      <assemblyIdentity
        type="win32"
        name="Microsoft.Windows.Common-Controls"
        version="6.0.0.0"
        processorArchitecture="*"
        publicKeyToken="6595b64144ccf1df"
        language="*"
        />
    </dependentAssembly>
  </dependency>
</assembly>

有人让这个工作吗?我发现以上内容无法识别。如果我先调用SetProcessDPIAwareness(Process_Per_Monitor_DPI_Aware),然后再调用GetProcessDPIAwareness(hProc,Awareness),我会得到必要的Awareness = Process_Per_Monitor_DPI_Aware,但我已经读到这种方法有潜在的缺点,所以我更喜欢一种有效的Manifest-only方法。

如果我打电话给GetProcessDPIAwareness(hProc,Awareness),我会返回“Awareness = Process_DPI_Unaware”。

我的另一个担心是,在 MSDN 来源中,他们指定添加附加清单。然而,我正在使用 Delphi XE6 的 IDE 将一个和唯一一个清单链接到我的应用程序中的能力。我从来没有注意到添加任何额外的清单而不是只有一个清单是一个问题,除了可能 Visual Studio 2010 中的 .manifest 管理系统是蹩脚的,这就是为什么存在提示,因此与其他 IDE 无关/语言。

在 Visual Studio 2013 中,项目选项中有一个复选框,但我没有 Visual Studio 2013,因此无法检查有效的 .manifest。

更新:

这是清单的另一个镜头:

<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>true</dpiAware>
    </asmv3:windowsSettings>
  </asmv3:application>
</assembly>

上面的迷你清单改变了应用程序的行为,但不完全是我想要的方式。通过上述微型清单,可以检测到旧的 Windows 8.0/Windows 7/Vista DPI 感知级别。

更新 2:

感谢雷米的想法。有趣的是,以下内容似乎足以允许应用程序启动。但是,将 SMI/2005 语法与上述混合会导致并行启动错误。你可以看到微软一直在大量修改他们的清单。请注意,以下内容实际上并不能解决我的问题,但它提供了另一种可能接近实际解决方案的“潜在基本形式”:

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

更新 3:

CODE RED ALERT! 不要在任何 Delphi VCL 应用程序中使用以下 OS COMPATIBILITY 标志: &lt;supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/&gt;。微软以我什至无法猜测的可怕方式破坏了鼠标捕获行为,破坏了窗口绘画。打开此标志会在我的应用程序中引起非常细微的错误,包括绘制问题、无法单击控件(由于鼠标捕获丢失,鼠标按下消息未到达控件)以及许多其他问题。

【问题讨论】:

  • 添加了其他清单,例如通过 mt.exe 工具只是合并到一个清单中(允许在应用程序中),不是吗?我相信你应该只修改你的应用程序。表现出来而不试图拥有更多。
  • 好的,很高兴知道。我怀疑我上面有一些模糊的 XML/Manifest 元数据。
  • 我建议更改顶级 XML 元素的顺序。让&lt;dependency&gt; 成为第一个,然后是&lt;compatibility&gt;,最后是&lt;application&gt;。这些是在 Windows 中引入这些功能的顺序。 XML 不应该是顺序敏感的,但如果使用 XML 模式则可以。 Windows 的解析器可能对顺序敏感,它可能需要先查看 Win8.1 的 &lt;compatibility&gt; 元素,然后才能处理 &lt;dpiAware&gt; 元素。
  • 另外,即使DPI-Aware documentation 表示&lt;WindowsSettings&gt; 元素的XML 命名空间是http://schemas.microsoft.com/SMI/2005/WindowsSettingsApplication Manifests documentation 却表示命名空间是http://schemas.microsoft.com/SMI/2011/WindowsSettings,所以请尝试两者。跨度>
  • 我添加了我的代码,所以任何拥有 Windows 8.1 和 Delphi XE 的人都可以尝试一下。在您的主应用程序表单中,运行以下代码:uses PerMonitorApi; ... SomeLabelControl.Visible := SystemCanSupportPerMonitorDpi( False);

标签: windows delphi windows-8.1 dpi delphi-xe6


【解决方案1】:

要么修改 Project | 中指向的清单。选项 |使用 .dpr 文件中的 $R 指令应用或包含附加清单。

你的 asmv3:application 部分看起来也不错,只是我认为你需要用小写 t 拼写“True”,就像“true”一样。

【讨论】:

  • 原来是True/PM。像往常一样,DavidH 似乎对这些东西很感兴趣!
【解决方案2】:

此清单有效,但有一些警告:

  • 请注意关于 asmv1、asm.v2 和 asmv3 的各种“元数据”差异。可能不重要。

  • 正如 David 指出的那样,可能是 True/PM 值,而不是 True,这一切都不同。微软显然没有记录它。 (笑)

  • 一些 SMI/2011 变体将在没有可怕的 SxS 启动错误的情况下启动,但我还没有找到有效的 SMI/2011 变体。

  • 在使用启用 Per Monitor API 并将操作系统兼容性定义为 Windows 8.1 的应用程序后,我在我的应用程序中发现了一些可怕的回归。 Microsoft 已更改 Windows 8.1 级别应用程序中的鼠标焦点行为。我不推荐这种方法。通过 CODE 而不是通过 MANIFEST 选择加入,并且不要使用下面的 &lt;supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/&gt; 示例!

这里是 XML。我在 StackOverflow 处理这个 XML 时遇到了一些问题,所以如果它看起来很糟糕,那么您会看到 StackOverflow 错误。

<?xml version="1.0" encoding="utf-8" ?>
<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
  <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
    <security>
      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
        <requestedExecutionLevel level="asInvoker" uiAccess="false" />
      </requestedPrivileges>
    </security>
  </trustInfo>

  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
    <application>
      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
    </application>
  </compatibility>

  <asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
    <asmv3:windowsSettings
         xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
      <dpiAware>True/PM</dpiAware>
    </asmv3:windowsSettings>
  </asmv3:application>
</asmv1:assembly>

你也可以拥有codez:

代码示例:

unit PerMonitorApi;

interface

const
   Process_DPI_Unaware = 0;
   Process_System_DPI_Aware = 1;    // Old windows 8.0
   Process_Per_Monitor_DPI_Aware = 2; // Windows 8.1

function SystemCanSupportPerMonitorDpi(AutoEnable: Boolean): Boolean; // New Windows 8.1 dpi awareness available?

function SystemCanSupportOldDpiAwareness(AutoEnable: Boolean): Boolean; // Windows Vista/ Windows 7 Global System DPI functional level.

var
   _RequestedLevelOfAwareness:LongInt;
   _ProcessDpiAwarenessValue:LongInt;

implementation

uses
   System.SysUtils,
   WinApi.Windows;

type
   TGetProcessDPIAwarenessProc = function(const hprocess: THandle; var ProcessDpiAwareness: LongInt): HRESULT; stdcall;
   TSetProcessDPIAwarenessProc = function(const ProcessDpiAwareness: LongInt): HRESULT; stdcall;

const
   E_ACCESSDENIED = $80070005;



function _GetProcessDpiAwareness(AutoEnable: Boolean): LongInt;
var
   hprocess: THandle;
   HRESULT: DWORD;
   BAwareness: Integer;
   GetProcessDPIAwareness: TGetProcessDPIAwarenessProc;
   LibHandle: THandle;
   PID: DWORD;

   function ManifestOverride: Boolean;
   var
      HRESULT: DWORD;
      SetProcessDPIAwareness: TSetProcessDPIAwarenessProc;
   begin
      Result := False;
      SetProcessDPIAwareness := TSetProcessDPIAwarenessProc(GetProcAddress(LibHandle, 'SetProcessDpiAwareness'));
      if Assigned(SetProcessDPIAwareness) and (_RequestedLevelOfAwareness>=0) then
      begin
         HRESULT := SetProcessDPIAwareness(_RequestedLevelOfAwareness ); // If we do this we don't need the manifest change.
         Result := (HRESULT = 0) or (HRESULT = E_ACCESSDENIED)
         // if Result = 80070005 then ACESS IS DENIED, means already set.
      end
   end;

begin
   Result := _ProcessDpiAwarenessValue;
   if (Result = -1) then
   begin
      BAwareness := 3;
      LibHandle := LoadLibrary('shcore.dll');
      if LibHandle <> 0 then
      begin
         if (not AutoEnable) or ManifestOverride then
         begin
            // This supercedes the Vista era IsProcessDPIAware api, and is available in Windows 8.0 and 8.1,although only
            // windows 8.1 and later will return a per-monitor-dpi-aware result.
            GetProcessDPIAwareness := TGetProcessDPIAwarenessProc(GetProcAddress(LibHandle, 'GetProcessDpiAwareness'));
            if Assigned(GetProcessDPIAwareness) then
            begin
               PID := WinApi.Windows.GetCurrentProcessId;
               hprocess := OpenProcess(PROCESS_ALL_ACCESS, False, PID);
               if hprocess > 0 then
               begin
                  HRESULT := GetProcessDPIAwareness(hprocess, BAwareness);
                  if HRESULT = 0 then
                     Result := BAwareness;
               end;
            end;
         end;
      end;
   end;
end;

// If this returns true, this is a windows 8.1 system that has Per Monitor DPI Awareness enabled
// at a system level.
function SystemCanSupportPerMonitorDpi(AutoEnable: Boolean): Boolean;
begin
   if AutoEnable then
   begin
    _RequestedLevelOfAwareness := Process_Per_Monitor_DPI_Aware;
    _ProcessDpiAwarenessValue := -1;
   end;
   Result := _GetProcessDpiAwareness(AutoEnable) = Process_Per_Monitor_DPI_Aware;
end;


// If this returns true, This is either a Windows 7 machine, or a Windows 8 machine, or a
// Windows 8.1 machine where the Per-DPI Monitor Awareness feature has been disabled.
function SystemCanSupportOldDpiAwareness(AutoEnable: Boolean): Boolean;
begin
   if AutoEnable then
   begin
     _RequestedLevelOfAwareness := Process_Per_Monitor_DPI_Aware;
     _ProcessDpiAwarenessValue := -1;
   end;

   Result := _GetProcessDpiAwareness(AutoEnable) = Process_System_DPI_Aware;
end;


initialization
   _ProcessDpiAwarenessValue := -1;// not yet determined.
   _RequestedLevelOfAwareness := -1;

end.

【讨论】:

    【解决方案3】:

    MSDN 主题Writing DPI-Aware Desktop and Win32 Applications

    通过修改 应用程序清单或通过调用 SetProcessDpiAwarenessAPI。我们 建议您使用应用程序清单,因为它设置 启动应用程序时的 DPI 感知级别。仅使用 API 在以下情况下:

    • 您的代码位于通过 rundll32.exe 运行的 dll 中。这是一种不支持应用清单的启动机制。
    • 您需要根据操作系统版本或其他考虑因素做出复杂的运行时决策。例如,如果您需要将应用程序 Windows 7 上的系统 DPI 感知和 Windows 8.1 上的动态感知, 使用 True/PM 清单设置。

    如果使用 SetProcessDpiAwareness 方法设置 DPI 感知 级别,您必须在任何 Win32API 调用之前调用 SetProcessDpiAwareness 这会强制系统开始虚拟化。

    DPI 感知清单值,说明

    False 将应用程序设置为不支持 DPI。

    True 将应用程序设置为系统 DPI 感知。

    每显示器 在 Windows 8.1 上,将应用程序设置为每显示器 DPI 感知。在 Windows Vista 到 Windows 8 上,设置 应用程序不支持 DPI。

    True/PM 在 Windows 8.1 上,将应用程序设置为每个监视器 DPI 感知。在 Windows Vista 到 Windows 8 上,将应用程序设置为 系统 DPI 感知。

    您只需使用标准 DPI 感知清单,但指定 True/PMPer-monitor 而不是 True

    同一主题给出了 DPI 感知清单如下:

    <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>true</dpiAware>
        </asmv3:windowsSettings>
      </asmv3:application>
    </assembly>
    

    所以,用您选择的值替换 True

    【讨论】:

    • 我接受这个答案,因为它包含对我的问题的确切正确答案。任何想要示例代码的人也应该阅读我的答案,这样人们就可以看到如何导入新的shcore.dll 导出并调用它们。感谢 David,因为他比我更了解 MSDN。 :-)
    • 谢谢。我想我必须设法让每个显示器都知道我的应用程序。这将需要对我的图像列表类进行一些工作.....
    • 我发现我的应用程序具有一些经典的基于单系统 DPI 的表单大小调整逻辑,与 Windows 8.1 的交互非常糟糕,除非您添加像我在这里得到的那样的清单,或者您禁用通过选中“让我选择一个缩放级别..”,从控制面板中的“显示设置”页面中的每个显示器-DPI-Awareness。因此,您可能会发现您现有的不支持每个显示器的 DPI 应用程序在 Win 8.1 上运行时会出现一些奇怪的行为。 Windows 8.1 顶级客户端窗口管理器代码中内置了一些非常奇怪的“向后兼容性”技巧。
    • 目前您无法让您的应用程序在 Delphi 中完全支持 Per-Monitor DPI。它适用于使用单个显示器设置或跨多个显示器使用相同缩放的人。将表单从一个监视器拖到另一个监视器时,Delphi 表单不处理 WM_DPICHANGED 消息。见quality.embarcadero.com/browse/RSP-9679
    • @DalijaPrasnikar 目前,您甚至无法使用 vanilla Delphi 让您的应用程序系统 DPI 感知。你必须自己支持。处理WM_DPICHANGED 可能会很有趣。将涉及缩放表单及其控件。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-18
    • 1970-01-01
    • 1970-01-01
    • 2015-11-12
    • 2019-06-04
    相关资源
    最近更新 更多