如何让Windows 8.1意识到我的Delphi应用程序想要支持Per Monitor DPI?

War*_* P 22 windows delphi dpi windows-8.1 delphi-xe6

我试图让Windows 8.1识别我一直在尝试构建的Delphi XE6应用程序(一个演示程序),让它认识到我的应用程序是Per-Monitor DPI识别的,纯粹是通过Manifest技术.Delphi XE6(以及所有其他类似的最新版本的Delphi)在Project Options中添加了一个易于操作的清单,我已经这样做了.

这是我使用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>
Run Code Online (Sandbox Code Playgroud)

有没有人得到这个工作?我发现上述内容未得到承认.如果我SetProcessDPIAwareness(Process_Per_Monitor_DPI_Aware)先打电话,然后打电话GetProcessDPIAwareness(hProc,Awareness),我会回复必要的Awareness = Process_Per_Monitor_DPI_Aware,但我已经读到这种方法存在潜在的缺点,所以我更喜欢一种有效的Manifest方法.

如果我打电话GetProcessDPIAwareness(hProc,Awareness),我会回到'Awareness = Process_DPI_Unaware'.

我担心的另一个问题是,在MSDN源代码中,他们指定添加一个ADDITIONAL清单.然而,我正在使用Delphi XE6的IDE将ONE和ONLY ONE清单链接到我的应用程序中.我从来没有注意到添加任何额外的清单而只有一个是一个问题,除了可能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>
Run Code Online (Sandbox Code Playgroud)

上面的迷你清单改变了应用程序的行为,但不完全是我想要的方式.使用上面的小清单,可以检测到OLD 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>
Run Code Online (Sandbox Code Playgroud)

更新3:

代码红色警告! 请勿在任何Delphi VCL应用程序中使用以下OS兼容性标志: <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>.微软以可怕的方式破坏了鼠标捕捉行为,破碎的窗口绘画,我甚至无法猜测.打开此标志会在我的应用程序中引起非常微妙的错误,包括绘制问题,无法单击控件(鼠标按下消息不能到达控件,由于鼠标捕获丢失)以及许多其他问题.

Dav*_*nan 10

它在MSDN主题编写DPI-Aware Desktop和Win32应用程序中有记录:

通过修改应用程序清单或调用SetProcessDpiAwarenessAPI,根据monitor-DPI标记应用程序.我们建议您使用应用程序清单,因为它在启动应用程序时设置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>
Run Code Online (Sandbox Code Playgroud)

因此,将True替换为您选择的值.


War*_* P 6

这个清单有效,但有一些警告:

  • 请注意有关asmv1,asm.v2和asmv3的各种"元数据"差异.可能不重要.

  • 正如大卫所指出的可能是它的True/PM价值,而不是True那样会产生重大影响.微软显然是DID记录它.(笑容)

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

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

这是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>
Run Code Online (Sandbox Code Playgroud)

你也可以有代码:

代码示例:

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.
Run Code Online (Sandbox Code Playgroud)