Delphi Host App - WPF Visual Plugin - high DPI

Hello,

we have a Host Delphi App, which is compiled with Delphi 10.3.3.
In the manifest we have defined our app to be dpi aware: ‘Per Monitor V2’.
I examine the process using Task Manager, our app has the DPI-Awarness set to ‘Per-Monitor (v2)’.

Also in Delphi, when we move our app to a monitor that uses a different DPI scale the Dpi, the WM_DPICHANGED event is triggered.

WPF also offers a similar event: Window.DpiChanged. If I create a WPF window from within a Hydra .NET plugin and subscribe to this event, the event is never fired. We target .NET 4.8.

Also it seems that the Window/VisualPlugin are always using the DPI of the primary monitor.

For example my primary monitor is scaled at 150%. When I move the plugin to a monitor scaled at 100% (96 dpi), the plugin/window is too big. It remains scaled at 1.5.

We do not call any of the SetProcessDPIAware api’s. We just rely on the manifest.

Do you have any guidance on how to deal with high dpi scenarios for WPF Visual Plugins?

Is there DpiChanged event supposed to be triggered?

Thank you!

.NET Fx documentation is a bit vague (to say the least) on use cases like the one used in Hydra.

How exactly does your manifest look like? Do you load your WPF plugin in the default app domain or in a custom one?

@antonk
Here is the relevant part of our manifest:

 <asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
    <asmv3:windowsSettings
         xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
      <dpiAware>true/PM</dpiAware>
      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor, System</dpiAwareness>
    </asmv3:windowsSettings>
  </asmv3:application>

We load our plugin in the default app domain.

Also we did test a standalone wpf with our manifest, and it worked as expected. That is why I don’t think that the manifest would have anything to do with it.

Thank you!

Hello

We are investigating is it possible to propagate manifest settings to a child-process like hosted .NET AppDomain.

However there are entries like these ones in the .NET Framework 4.8 Release Notes so it can be even a bug in the .NET Framework itself:

  • Fixed WPF issue of not creating correctly-size rendertarget for mixed-mode child windows. Under certain circumstances, per-monitor DPI aware applications that host WPF based controls or plugins were not rendering the WPF window fully [646801, wpfgfx_v0400.dll, Bug, Build:3646
    *Fixed the issue of WPF Windows not scaling correctly. WPF windows that are parented under native HWND’s, including Windows Forms (using ElementHost), will correctly react to DPI changes and scale themselves appropriately when the dpiAwareness element in the application manifest is updated to “PerMonitorV2” value. [478267, PresentationCore.dll,wpfgfx_v0400.dll, Feature, Build:3673]]
  • When WPF is run in Per-Monitor Aware mode, controls hosted within HwndHost are not sized correctly after DPI changes (for e.g., when moving applications from one monitor to another). This fix ensures that controls hosted so are sized appropriately. On .NET Framework Versions 4.7.2 and older, applications must opt in to enable the fix by setting the AppContext switch ““Switch.System.Windows.DoNotUsePresentationDpiCapabilityTier2OrGreater”” to ““false””. This switch can be set either in the registry or in the application - see the documentation for AppContext (https://msdn.microsoft.com/en-us/library/system.appcontext(v=vs.110).aspx). [646805, PresentationFramework.dll;PresentationCore.dll;WindowsBase.dll, Bug, Build:3673]

Hello @antonk,

have you been able to make some progress?
Would you mind sharing your findings so far?

Thank you!

Hello @antonk, hello @Santiago_Burbano,
did you found a solution for this? At the moment I have to turn off DIP awareness because it doesn’t work on multiple screens with different dpi scaling. However, this makes the application look very blurry.

many thanks!!

Could you try to load the WPF plugin into the default app domain to see if it makes a difference?

@sebastian.may
The issue is still open for us.

Same here, loading in a different app domain doesn’t make any difference for us. We are thinking re-implementing things natively in Delphi and geting rid of the .NET plugin at this stage. :frowning:

Did you try to call the following Win API method in plugin (this is Elements syntax)?

  NativeMethods = static class
  public
    [System.Runtime.InteropServices.DllImport("SHCore.dll", SetLastError := true)]
    class method SetProcessDpiAwareness(awareness: PROCESS_DPI_AWARENESS): Boolean; external;
  end;


  PROCESS_DPI_AWARENESS = enum(
    Process_DPI_Unaware = 0,
    Process_System_DPI_Aware = 1,
    Process_Per_Monitor_DPI_Aware = 2
  );

@antonk

@lober found the following, from https://github.com/microsoft/WPF-Samples/tree/master/PerMonitorDPI

WindowsForms Apps (NOT SUPPORTED)

The scenario where a WindowsForms app hosts WPF via ElementHost does not currently support Per Monitor DPI. We’ll consider doing work in the future here, but it likely will require additional features from the Windows team.

I do not think it is very likely that Per Monitor DPI support will be added to WindowsForms apps hosting WPF user controls via ElementHost.

The other option, is that you provide a ‘native’ WPF Visual Plugin implementation that does not requiere the use of WinFroms + ElementHost.

Then it should work fine, and it will solve so many problems.

I understand it is convenient to use one implementation to support both WinFrorms and WPF, but unfortunately that prevents WPF user controls from being Per Monitor Dpi Aware.

Please let me know what your thoughts are.

Thank you!

There is one blocker issue here:
WPF controls are not ‘real’ controls. For WinAPI they are nothing more than a pictures drawn by WPF.
This essentially means that they have no Handle. And no Handle means that they cannot be embedded into a host window.
Thus a wrapper element is required.

@antonk

That is too bad. But I do understand the limitation.

fyi,

Thank you!

Hello,

I have uploaded a modified version of the Visualizer Sample Project.

You will find a Visual Studio solution and a Delphi Project.

The Delphi Project generates the Delphi Host App.
This App hosts a WPF Visual Plugin and also a non visual plugin.

The Visual Studio Solution consists of two projects:

  1. A class library (Visualizer.dll) which contains the Visual and Non-Visual Plugins as well as a dialog (WndTest) for testing the Window.DpiChanged event.

  2. A simple WPF Application that references the Visualizer class library. This application only has a button
    monitor.
    This works as expected.

WpfHostApp

However if you run the Delphi Host App [VisualizerHost.exe] (which references the same Visualizer.dll) and display the Dpi Changed Test Window, dragging the dialog to another monitor does not trigger the DpiChanged event.
We do not understand why.
This also happens if the WPF Dialog is created from within a non-visual plugin.

The process awarness for the hydra plugins was set to: Process_Per_Monitor_DPI_Aware.

Delphi WPF.zip (1.2 MB)

Thanks for the testcase.
Checks in the WPF dialogs confirm that its parent process is running in the PerMonitor_DPI_Aware mode. Despite this, the WM_DPICHANGED WinAPI event is not properly raised for them. So all DPI awareness code is not run.

@antonk
Thanks for the feedback.
But I am pretty much in the dark. Is this something you will continue looking into?
Just a guess, maybe having Hydra Binaries that target .NET 4.8 could help?

We need to develop a few WPF plugins for our Delphi Host App, but at the moment this issue is preventing us from moving forward.

Check the runtime used by your plugin at runtime. It will be the latest varsion of .NET Framework installed on your host. Thanks to the architecture used by .NET Framework Hydra plugins always use the latest available .NET versions.

Yes. However at the moment we are kind of lost too. This requires way deeper investigation in areas that are not even covered with proper docs. F.e. how exactly WPF application handles the DPI change message sent to it by Windows?
Note that when plugin asks WinAPI about its DPI support it reports that PerMonitor DPI support is enabled. But somehow this support is not propagated to WPF display system.