dotnet / wpf

WPF is a .NET Core UI framework for building Windows desktop applications.
MIT License
7.03k stars 1.16k forks source link

WPF HwndSource ignores DPIContextAwareness in a hosted environment. #9420

Open vsfeedback opened 1 month ago

vsfeedback commented 1 month ago

This issue has been moved from a ticket on Developer Community.


[severity:It’s more difficult to complete my work]
[severity:it's a BUG]

My WPF needs to handle Multimonitor DPI inside VSTO Office addin.

Since my WPF is hosted, I can’t control the manifest of the host app and access is denied when I try to set the ProcessDpiAwareness in code.

So what I need to do:
set the Process/Thread DPIAwarenessContext.
set a HwndSourceHook, catch the WM_DPICHANGED and forward dpi and size to WPF.
set SetRootDPI on my Window.
set LayoutTransform to Window
set LayoutTransform To Window’s child.

It’s cumbersome but it works, EXCEPT for Popups and Tooltips that end up in the wrong location (or monitor) and in the wrong scale.

What happens:
In hosted WPF the HwndSource is ‘stuck’ to SystemDPI. The layout transforms have to bridge the delta between actual dpi and system dpi. But Popups and Tooltips don’t fall within the Window’s visual tree and require workrounds. For generic Tooltips I can bind to the ancestor Window’s LayoutTransform. But some will be defined in resources used for a DataGrid and cannot locate the ancestor Window.

Also Window location/sizes (including Min/Max Height/Width) are in SystemDPI and require manipulation. Not hard, but contrary to WPF design philosophy.

From a layman’s perspective you’d think all this is needlessly difficult.
There should be no need for hooking, setrootdpi, layouttransforms and the Popup problems/workarounds.

The solution?
When I set the DpiContextAwareness the HwndSource SHOULD handle DpiChanges itself. Afaik for HwndSource the (windows 8.1) ProcessAwareness is dominant, and in a hosted environment the (win10) AwarenessContext is ignored. If HwndSource knows the correct DPI then PopupRoot will know as well.

Microsoft please fix this!
I have a demo project. It’s currently VB, but easily changed to C#. And it also has a good workaround for the Modeless WPF input focus issue. Another WPF bug/design feature that never gets addressed.

Kind regards,
Jurgen (aka keepITcool)


Original Comments

Feedback Bot on 7/17/2024, 07:30 AM:

(private comment, text removed)


Original Solutions

(no solutions)

harshit7962 commented 1 month ago

Can you please share a minimal repro for the same.

xlsupport commented 1 month ago

@harshit7962
The demo. 1 shared library, 1 wpf app to call it. 1 vsto addin to call it. Questions? dont hesitate!

Jurgen aka keepITcool

DpiDemo.zip

xlsupport commented 1 month ago

How does this work? After 4 days there's 1 thumb from @lindexi. Nothing else. After all the work I put into this, I'm not impressed tbh.

lindexi commented 1 month ago

@xlsupport I am looking your code, but I do not know why ...

xlsupport commented 1 month ago

@lindexi Can you please elaborate. Why what? There appear to be some static variables in HwndSource (set from code in utils I believe). I also believe those are set using manifest's (win8.1) ProcessAwareness. And they seems to have priority over laters calls to ContextAwarenss. I'm a vb hack, c# is not my mothertongue ;-)

lindexi commented 1 month ago

@xlsupport Sorry, this problem is too complicated for me...

xlsupport commented 1 month ago

@harshit7962 Sorry to be pushing, but can this be assigned to someone to investigate?

xlsupport commented 1 month ago

Let me make it easier for you by pointing at (one of) the culprits...

HwndTarget.cs line 296

public override Visual RootVisual
{
    [SecurityCritical]
    [UIPermission(SecurityAction.LinkDemand, Window = UIPermissionWindow.AllWindows)]
    set
    {
        base.RootVisual = value;
        if (value != null)
        {
            if (IsProcessPerMonitorDpiAware == true)
            {
                DpiFlags dpiFlags = DpiUtil.UpdateDpiScalesAndGetIndex(_currentDpiScale.PixelsPerInchX, _currentDpiScale.PixelsPerInchY);
                DpiScale newDpiScale = new DpiScale(UIElement.DpiScaleXValues[dpiFlags.Index], UIElement.DpiScaleYValues[dpiFlags.Index]);
                RootVisual.RecursiveSetDpiScaleVisualFlags(new DpiRecursiveChangeArgs(dpiFlags, RootVisual.GetDpi(), newDpiScale));
            }
            UnsafeNativeMethods.NotifyWinEvent(1879048191, _hWnd.MakeHandleRef(this), 0, 0);
        }
    }
}

Calls this from HwndTarget.cs line 246

internal static bool? IsProcessPerMonitorDpiAware
{
    get
    {
        if (ProcessDpiAwareness.HasValue)
        {
            return ProcessDpiAwareness.Value == MS.Win32.NativeMethods.PROCESS_DPI_AWARENESS.PROCESS_PER_MONITOR_DPI_AWARE;
        }
        return null;
    }
}

But that will return False when Wpf is hosted in a VSTO controlled domain., where the Process is PROCESS_SYSTEM_AWARE, and the dev only has control over the AwarenessContext.

Why this does not call GetDpiAwarenessContextForProcess (available since build 1803 / 10.0.17134 !!!) is beyond me. It just doesn't make sense... And if that breaks compatibility then what about a method to modify the Matrices in HwndSource and HwndTarget. Similar to SetRootDpi which updates the UIElement, but at the Hwnd level... call it SetHwndDpi?

lindexi commented 1 month ago

@xlsupport Could you format the code?

pchaurasia14 commented 1 month ago

@xlsupport - Due to other high priority work that the team is involved in, we won't be able to look into this issue right away. Appreciate your patience on this.

harshit7962 commented 1 month ago

@xlsupport - apologies for not keeping a close eye on it. As mentioned above we are a bit occupied with some other priority tasks and would take it up as we get the width to do so. Regarding the tag "waiting-author-feedback" not getting removed, we have it automated and due the mismatch in username it did not get out automatically.

Would like to thank you for your detailed investigation and would appreciate your patience on this. Will mark it as investigate as of now.

xlsupport commented 1 month ago

@pchaurasia14 please check your own SLA. I hope you appreciate my patience is wearing thin.

lindexi commented 1 month ago

@xlsupport The IsProcessPerMonitorDpiAware property will use the value of ProcessDpiAwareness. And the value of ProcessDpiAwareness is from GetProcessDpiAwareness method, see

https://github.com/dotnet/wpf/blob/d417620109d4a097e6ee9be3d1b50e39d96a99e4/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/InterOp/HwndTarget.cs#L340-L345

The GetProcessDpiAwareness method will call the GetProcessDpiAwareness, which from Shcore.dll.

But I do not know what is the difference between GetDpiAwarenessContextForProcess and GetProcessDpiAwareness.

Can you sure the GetDpiAwarenessContextForProcess can return the correct value?

xlsupport commented 1 month ago

@lindexi As i said in my 1st post... GetProcessDpiAwareness is win81 old hat. See https://learn.microsoft.com/en-us/windows/win32/hidpi/dpi-awareness-context . DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 introduced the whole concept of a Thread DpiAwarenessContext. And when my DpiHelper can catch the DPIChanged events, there's zero reasons why Hwndsource can't.

lindexi commented 1 month ago

Maybe we can try call the GetDpiAwarenessContextForProcess in SafeNativeMethods.GetProcessDpiAwareness, see

https://github.com/dotnet/wpf/blob/d417620109d4a097e6ee9be3d1b50e39d96a99e4/src/Microsoft.DotNet.Wpf/src/Shared/MS/Win32/SafeNativeMethodsCLR.cs#L399

xlsupport commented 1 month ago

@lindexi You stated you do not know the difference between ProcessAwareness and ProcessAwarenessContext, but you are on the right track! But HwndTarget.cs and HwndSource are pretty fundamental classes... so can you please help get this assigned to someone with the knowledge and overview this requires?

I also noted that DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED, which has been been defined in windef.h since October 2018 (win10 version 1809) is missing in src.

Can't find it in src/Microsoft.DotNet.Wpf/src/Shared/MS/Win32/NativeMethodsCLR.cs Can't find it in src/Microsoft.DotNet.Wpf/src/Shared/MS/Utility/DpiAwarenessContext/DpiAwarenessContextValue.cs (Last modfied 5 years ago)

It's not relevant to my scenario, but just a reminder people don't change these lightly. And there must be fallbacks for earlier environments.

HTH, and waiting for my issue to be assigned! Jurgen.

lindexi commented 1 month ago

so can you please help get this assigned to someone with the knowledge and overview this requires?

Waiting Harshit

xlsupport commented 1 month ago

@harshit & @pchaurasia14 bump!

pchaurasia14 commented 1 month ago

@xlsupport - Sorry, still no progress. Unfortunately, we won't be able to prioritize this right now due to ongoing .NET 9 release commitments.

xlsupport commented 6 days ago

@HARSHIT & @pchaurasia14 One month later...