How can I make Windows 8.1 aware that my Delphi application wants to support Per Monitor DPI?
Asked Answered
F

3

22

I have tried to make Windows 8.1 recognize a Delphi XE6 application (a demo program) that I have been trying to build, and have it recognize my application is Per-Monitor DPI aware, purely by the Manifest technique. Delphi XE6 (and all other similarly up to date versions of Delphi) make adding a manifest easy to do, inside Project Options, and I have done so.

This is the .manifest content I have determined using MSDN resources. I suspect it could be slightly incorrect.

If you want to try this manifest, make an empty VCL application, use this content as your manifest, and add the code (code is currently attached to my answer to this question).

<?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>

Has anyone gotten this to work? What I find is that the above is not recognized. If I call SetProcessDPIAwareness(Process_Per_Monitor_DPI_Aware) first, then call GetProcessDPIAwareness(hProc,Awareness), I get back the necessary Awareness = Process_Per_Monitor_DPI_Aware, but I have read that there are potential drawbacks to that approach, and so I would prefer a working Manifest-only approach.

If I call GetProcessDPIAwareness(hProc,Awareness), I get back `Awareness = Process_DPI_Unaware'.

My other worry is that in the MSDN sources they specify adding an ADDITIONAL manifest. Whereas, I am using Delphi XE6's IDE's ability to link ONE and ONLY ONE manifest into my application. I have never noticed that adding any additional manifest versus having only one was ever a problem, other than that perhaps the .manifest management system in Visual Studio 2010 was lame, and that is why the tip existed, and so has no relevance to other IDEs/Languages.

In Visual Studio 2013, there is a checkbox right inside the project options, but I don't have Visual Studio 2013 so I can't examine a working .manifest.

update:

Here's another shot at a 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>

The above mini manifest changes the behavior of the application, but not exactly the way I wanted it to. With the above tiny manifest, the OLD Windows 8.0/Windows 7/Vista DPI awareness level is detected.

update 2:

Thanks Remy for the ideas. Interestingly the following appears to be valid enough to allow an application launch. However mixing the SMI/2005 syntax with the above caused a side-by-side launch error. You can see that Microsoft has been churning on their manifests rather a lot. Note that the following does not actually solve my problem, but it provides yet another "potential base form" that might be CLOSE to the real solution:

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

update 3:

CODE RED ALERT! DO NOT USE the following OS COMPATIBILITY flag in any Delphi VCL application: <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>. Microsoft has BROKEN MOUSE CAPTURE BEHAVIOUR, BROKEN WINDOW PAINTING in horrible ways that I have not even been able to guess at. Turning this flag on caused very subtle bugs in my application, including painting problems, inability to click on controls (mouse down messages not reaching controls, due to mouse capture being lost), and many other problems.

Filberto answered 10/11, 2014 at 20:10 Comment(5)
Additional manifests added e.g. through the mt.exe tool are just merged into a single manifest (which is allowed to be in the application), aren't they ? I believe you should just modify your app. manifest without trying to have more of them.Quad
Okay that's good to know. I suspect I have some obscure XML/Manifest metadata slightly wrong above.Filberto
I would suggest changing the order of the top-level XML elements. Make the <dependency> be first, then the <compatibility> next, then finally the <application> last. These are the order in which those feature were introduced in Windows. XML is not supposed to be order-sensitive, but can be if an XML schema is used. Windows' parser might be order-sensitive, it might need to see the Win8.1 <compatibility> element is present before it will process the <dpiAware> element.Corney
Also, even though the DPI-Aware documentation says the XML namespace for the <WindowsSettings> element is http://schemas.microsoft.com/SMI/2005/WindowsSettings, the Application Manifests documentation says the namespace is http://schemas.microsoft.com/SMI/2011/WindowsSettings instead, so try both.Corney
I added my code, so anyone who has Windows 8.1, and Delphi XE can give it a try. In your main application form, run this code: uses PerMonitorApi; ... SomeLabelControl.Visible := SystemCanSupportPerMonitorDpi( False);Filberto
B
12

It is documented on the MSDN topic Writing DPI-Aware Desktop and Win32 Applications:

Mark the application as per monitor-DPI aware by modifying the application manifest or by calling the SetProcessDpiAwarenessAPI. We recommend that you use the application manifest because that sets the DPI awareness level when the application is launched. Use the API only in the following cases:

  • Your code is in a dll that runs via rundll32.exe. This is a launch mechanism that does not support the application manifest.
  • You need to make complex run-time decisions based on OS version or other considerations. For example, if you need the application to be system-DPI aware on Windows 7 and dynamically aware on Windows 8.1, use the True/PM manifest setting.

If you use the SetProcessDpiAwareness method to set the DPI awareness level, you must call SetProcessDpiAwareness prior to any Win32API call that forces the system to begin virtualization.

DPI awareness manifest value, Description

False Sets the application to not DPI-aware.

True Sets the application to system DPI–aware.

Per-monitor On Windows 8.1, sets the application to per monitor-DPI aware. On Windows Vista through Windows 8, sets the application to not DPI–aware.

True/PM On Windows 8.1, sets the application to per monitor-DPI aware. On Windows Vista through Windows 8, sets the application to system-DPI aware.

You just use the standard DPI aware manifest but specify True/PM or Per-monitor instead of True.

The same topic gives the DPI aware manifest as follows:

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

So, replace True with your chosen value.

Britanybritches answered 10/11, 2014 at 23:1 Comment(13)
I am accepting this answer, because it contains the exact correct answer to my question. Anyone who wants sample code should also read my answer as I put that there so people can see how to import the new shcore.dll exports and invoke them. Thanks David, for being so much better at understanding MSDN than me. :-)Filberto
Thanks. I guess I'll have to get round to making my app per monitor aware. That will take some work on my image list class.....Britanybritches
I found that my application which has some classic single-system-DPI based form resizing logic, interacts BADLY with Windows 8.1, unless you add a manifest like the one I've got here, or you disable Per-Monitor-DPI-Awareness from the Display Settings page in Control Panel by checking "Let me choose one scaling level..". So you might find your existing non-per-monitor-DPI aware app gets some weird behaviours when it runs on Win 8.1. There are some extremely weird "backwards compatibility" hacks built into the Windows 8.1 top-level-client-window-manager code.Filberto
Currently you cannot make your applications fully Per-Monitor DPI aware in Delphi. It will work for people using single monitor setups or using same scaling across multiple monitors. Delphi forms do not handle WM_DPICHANGED messages when dragging forms from one monitor to another. See quality.embarcadero.com/browse/RSP-9679Debbee
@DalijaPrasnikar Currently you cannot even make your apps system DPI aware with vanilla Delphi. You have to support that yourself. Handling WM_DPICHANGED could be quite interesting. Going to involve scaling a form and its controls.Britanybritches
@DavidHeffernan Depends on what you mean by vanilla Delphi. In general you could have DPI aware application if you have supplied custom manifest and made sure that all controls scale well. Depending on controls you have used you could have quite easy job, or more complicated one. But with Windows 8.1 and per-monitor DPI you would have to hack Delphi core frameworks extensively to make it work.Debbee
@Dalija Scaling controls is the easy part. Scaled does that, albeit not very well. Toolbar glyphs won't scale in an image list. That's where you need to start getting messy.Britanybritches
@DavidHeffernan You have to supply multiple image lists for different sizes and assign proper one during run-time. You have to do same thing with other image resources. How many different sized resources you will use or will you have to resample images during run-time depends on your needs. I usually need only 1x and 2x images and then I downsample 2x images at run-time when needed.Debbee
I have resources with 16, 24, 32, 48 and 64 px icons in. I never resize these. If I need a 20 px icon I use the 16 px icon with a transparent border. Resampling small icons leaves artifacts. But this is the point. Extra work is needed that is tricky.Britanybritches
Have you tried the latest 10.1 Berlin changes? Are they useful to you in making per monitor DPI aware apps, or not really yet?Filberto
@Warren Not sure Berlin has much new. Big changes in Seattle. I'm not going per user yet. The thing i don't like is that the non client area, the caption bar, doesn't seem to scale.Britanybritches
Hmm. There are win32 APIs to force the nonclient region to be larger right?Filberto
Not to my knowledge. Have a look at the questions here. Specifically the one asked by Cody Gray here a few days ago.Britanybritches
F
6

This manifest works but with some warnings:

  • Note the various "metadata" differences about asmv1, and asm.v2 and asmv3. Probably not important.

  • As David pointed out probably its the True/PM value, instead of True that makes all the difference. Microsoft apparently DID document it. (grins)

  • Some SMI/2011 variants WILL launch WITHOUT dread SxS launch errors, but I haven't found an SMI/2011 variant that WORKS.

  • After using an application that both enabled Per Monitor API, and defined OS compatibility as Windows 8.1, I found some HORRIBLE regressions in my application. Microsoft has changed the mouse focus behaviour in Windows 8.1 level applications. I do NOT RECOMMEND THIS APPROACH. Opt in via CODE instead of via MANIFEST, and DO NOT USE the <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> example below!

Here is the XML. I am having some trouble with StackOverflow mangling this XML, so if it looks bad, you're seeing StackOverflow bugs.

<?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>

And you can has teh codez too:

Code sample:

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.
Filberto answered 10/11, 2014 at 22:24 Comment(0)
T
2

Either modify the manifest pointed to in Project | Options | Application or include an additional manifest using the $R directive in the .dpr file.

Also your asmv3:application section looks fine except that I think you need to spell "True" with a lower case t as in "true".

Tm answered 10/11, 2014 at 20:51 Comment(1)
It turns out it's True/PM. As usual DavidH seems to be all up on that stuff!Filberto

© 2022 - 2024 — McMap. All rights reserved.