Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Context Menus are sometimes not shown in High-DPI applications #2097

Merged
merged 1 commit into from
Oct 25, 2019

Conversation

vatsan-madhavan
Copy link
Member

Addresses #2088

As part of a previous fix (that shipped originally as part of .NET 4.8 in), a change was made to Popup that involved the destruction and recreation of the underlying HWND. This was done to ensure that the HWND was always created with the correct monitor (~=DPI) affinity.

In order to acheive this, DestroyWindow() and BuildWindow() - private methods in Popup - were used.

We are finding that DestroyWindow() has side-effects that can lead to incorrect behavior. The incorrect beahvior works as follows:

  • The call into Popup.CreateWindow is usually a consequence Popup.IsOpenChanged (false -> true)
  • Within Popup.CreateWindow, DestroyWindow() is called (when high-dpi mode is detected).
  • DestroyWindow() destroys the underlying HWND, releases the capture, raises the OnClosed event and clears the placement-target.
    • At the end of DestroyWindow(), we get IsOpen == false.
  • After DestroyWindow(), we call into BuildWindow() and CreateNewPopupRoot() etc., which go on to build the Popup again (and also instnatiate a new HWND).
    • Unfortunately, there is no mechanism here for resetting IsOpen back to true (without also leading to an undesirable infinite-recursion that calls back into CreateWindow).

If these calls to show the context-menu arise from ContextMenu.OnIsOpenChanged, and flow through ContextMenu.HookupParentPopup -> Popup.CreateRootPopup, then IsOpen gets reset (there is a direct call into SetBinding(IsOpenProperty), in Popup.CreateRootPopupInternal) and the popup is shown correctly. Until then, the context-menu is "stuck" not being able to be shown.

The solution is to stop using DestroyWindow() as-is, which does more than what we need for it to accomplish. Our original intent in calling DestroyWindow() was simply destroy and recreate the HWND. This fix refactors DestroyWindow() to suit this need and uses the newly introduced DestroyWindowImpl() to destroy the HWND, and then recreate just that. The rest of the state is retained intact, and the Popup/ContextMenu continues to function well as before.

…8 in), a change was made to `Popup` that involved the destruction and recreation of the underlying `HWND`. This was done to ensure that the `HWND` was always created with the correct monitor (~=DPI) affinity.

In order to acheive this, `DestroyWindow()` and `BuildWindow()` - private methods in `Popup` - were used.

We are finding that `DestroyWindow()` has side-effects that can lead to incorrect behavior. The incorrect beahvior works as follows:

  - The call into `Popup.CreateWindow` is usually a consequence `Popup.IsOpenChanged` (`false -> true`)
  - Within `Popup.CreateWindow`, `DestroyWindow()` is called (when high-dpi mode is detected).
  - `DestroyWindow()` destroys the underlying `HWND`, releases the capture, raises the *OnClosed* event and clears the placement-target.
    - At the end of `DestroyWindow()`, we get `IsOpen == false`.
  - After `DestroyWindow()`, we call into `BuildWindow()` and `CreateNewPopupRoot()` etc., which go on to build the `Popup` again (and also instnatiate a new `HWND`).
    - Unfortunately, there is no mechanism here for resetting `IsOpen` back to `true` (without also leading to an undesirable infinite-recursion that calls back into `CreateWindow`).

If these calls to show the context-menu arise from `ContextMenu.OnIsOpenChanged`, and flow through `ContextMenu.HookupParentPopup -> Popup.CreateRootPopup`, then `IsOpen` gets reset (there is a direct call into `SetBinding(IsOpenProperty)`, in `Popup.CreateRootPopupInternal`) and the popup is shown correctly. Until then, the context-menu is "stuck" not being able to be shown.

The solution is to stop using `DestroyWindow()` as-is, which does more than what we need for it to accomplish. Our original intent in calling `DestroyWindow()` was simply destroy and recreate the `HWND`. This fix refactors `DestroyWindow()` to suit this need and uses the newly introduced `DestroyWindowImpl()` to destroy the `HWND`, and then recreate just that. The rest of the state is retained intact, and the `Popup/ContextMenu` continues to function well as before.
@ghost ghost requested a review from rladuca October 23, 2019 17:20
@ghost ghost added the PR metadata: Label to tag PRs, to facilitate with triage label Oct 23, 2019
@ghost ghost requested a review from SamBent October 23, 2019 17:20
@arpitmathur arpitmathur self-requested a review October 24, 2019 19:58
@vatsan-madhavan vatsan-madhavan merged commit 01547d9 into dotnet:master Oct 25, 2019
@vatsan-madhavan vatsan-madhavan deleted the contextmenu_fixup branch October 25, 2019 05:45
@ghost ghost locked as resolved and limited conversation to collaborators Apr 14, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
PR metadata: Label to tag PRs, to facilitate with triage
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants