Control.CreateHandle should specify dpiAwarenessContext (of current thread) in Application.ParkHandle
- Dominant language
- C#
- Stars
- 4.9k
- Forks
- 1.1k
- Avg merge
- 1d 13m
- Merged PRs (30d)
- 85
Description
* .NET Core Version: (e.g. 3.0 Preview1, or daily build number, use `dotnet --info`) 5.0-alpha1
* Have you experienced this same bug with .NET Framework?: N/A
**Problem description:**
I came across the problem when trying to add a UT for `System.Windows.Forms` that involves controls of different DPI-awareness. The code is like this
```c#
[WinFormsFact]
public void Control_Parent_DifferentDpi_ThrowsWin32Exception()
{
Control childControl = null, parentControl = null;
IntPtr? originalAwareness = null;
try
{
try
{
// DpiHelper.EnterDpiAwarenessScope will do nothing if current process is SYSTEM_UNAWARE or SYSTEM_AWARE
//
// We cannot use DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE,
// Somehow GetDpiAwarenessContextForWindow will return DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 for such window.
originalAwareness = CommonUnsafeNativeMethods.SetThreadDpiAwarenessContext(new IntPtr((int)DpiAwarenessContext.DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2));
Assert.Equal(0, Marshal.GetLastWin32Error());
childControl = new Control();
childControl.CreateControl();
Assert.Equal(DpiAwarenessContext.DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2,
CommonUnsafeNativeMethods.GetDpiAwarenessContextForWindow(childControl.Handle));
CommonUnsafeNativeMethods.SetThreadDpiAwarenessContext(new IntPtr((int)DpiAwarenessContext.DPI_AWARENESS_CONTEXT_SYSTEM_AWARE));
Assert.Equal(0, Marshal.GetLastWin32Error());
parentControl = new Control();
parentControl.CreateControl();
// [Below] Assertion failure: Expected: DPI_AWARENESS_CONTEXT_SYSTEM_AWARE; Actual: DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
Assert.Equal(DpiAwarenessContext.DPI_AWARENESS_CONTEXT_SYSTEM_AWARE,
CommonUnsafeNativeMethods.GetDpiAwarenessContextForWindow(parentControl.Handle));
}
finally
{
if (originalAwareness != null)
{
CommonUnsafeNativeMethods.SetThreadDpiAwarenessContext(originalAwareness.Value);
}
}
Assert.Throws(() => childControl.Parent = parentControl);
Assert.Null(childControl.Parent);
}
finally
{
childControl?.Dispose();
parentControl?.Dispose();
}
}
```
There are actually three problems I have came across during writing this unit test.
* `DpiHelper.EnterDpiAwarenessScope` will do nothing if the DPI-awareness of current process is SYSTEM_UNAWARE or SYSTEM_AWARE. I'm not sure if that's by design.
* `CommonUnsafeNativeMethods.GetDpiAwarenessContextForWindow` is returning `PER_MONITOR_AWARE_V2` for windows that is actually `PER_MONITOR_AWARE`. It seems that you will need to use [`AreDpiAwarenessContextsEqual`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-aredpiawarenesscontextsequal) and compare the target windows' DPI awareness context with `IntPtr(-3)` or `IntPtr(-4)` to determine whether it's `V2` or not.
* That's the blocking issue for adding UT for #2262 : `Control.CreateHandle` is calling `Application.ParkHandle` without specifying the second parameter, leaving its `dpiAwarenessContext` set to `DPI_AWARENESS_CONTEXT_UNSPECIFIED`.
https://github.com/dotnet/winforms/blob/2392f418ed9a87e121e92688cd77cba3f5e19e03/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs#L5022-L5027
This will cause the parked control having the same DPI awareness as the parking window for `DPI_AWARENESS_CONTEXT_UNSPECIFIED`, that is, actually the thread DPI awareness when creating the parking window.
https://github.com/dotnet/winforms/blob/2392f418ed9a87e121e92688cd77cba3f5e19e03/src/System.Windows.Forms/src/System/Windows/Forms/Application.ThreadContext.cs#L285-L288
In this case, I think we should retrieve the DPI awareness context for the current thread, then pass it as the `dpiAwarenessContext` argument when calling `Application.ParkHandle`, so the created control will have the same DPI awareness context as the current thread.
**Actual behavior:**
**Expected behavior:**
**Minimal repro:**
Contributor guide
Assessment
This issue has not been assessed yet.