Skip to content

Conversation

corvinsz
Copy link
Member

@corvinsz corvinsz commented Sep 13, 2025

fixes #3434

The fix is basically to listen for the StateChanged event of the parent Window.
If the window is restored from the minimized state, we focus the previously focused element, which got stored in a field _lastFocusedDialogElement when the window got minimized.

I want to point out the artificial delay I added, that we have in other places in the DialogHost class too. I just couldn't get it to work without the (50ms) delay, as described in my comment above the "hacky" code.

...
// Kinda hacky, but without a delay the focus doesn't always get set correctly because the Focus() method fires too early
Task.Delay(50).ContinueWith(_ => this.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
...

@corvinsz corvinsz closed this Sep 13, 2025
@corvinsz corvinsz reopened this Sep 13, 2025
@corvinsz
Copy link
Member Author

It seems like there is a problem with running the tests via github actions. This PR just had another failing test (SplitButton_WithButtonInPopup_CanBeInvoked). When reran, the test passed.
I'm not sure if this is a TUnit or github actions problem?

I had the same issue in #3915

@corvinsz corvinsz mentioned this pull request Sep 16, 2025
Copy link
Member Author

@corvinsz corvinsz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussion on how to get the last focused element

var windowState = window.WindowState;
if (windowState == WindowState.Minimized)
{
_lastFocusedDialogElement = FocusManager.GetFocusedElement(window);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw your comment about this line in your past twitch broadcast (I think you forgot to submit it?):
image

I honestly didn't think of that edge case.
I guess instead of getting the focused element of the Window you'd rather get it from the current dialog instance like so?

_lastFocusedDialogElement = FocusManager.GetFocusedElement(this);

FocusManager.GetFocusedElement(this) returns null if the default dialoghost style is used. This is some kind of problem with the PopupEx I assume. I can't think of an easy solution and I'm not sure if this is worth looking into.

I may be biased, but I don't like the current default style for the dialoghost anyway (that could be a me-problem though).

@corvinsz
Copy link
Member Author

I also adjusted the test to test against both DialogHost styles. I would say this problem is worth testing for both styles.

@Keboo
Copy link
Member

Keboo commented Oct 3, 2025

So the issue with the test should be resolved if you rebase on the latest main. I believe I fixed that test last week.

On the note of focus, I think we want to use the Keyboard Focused element rather than the logically focused element. The problem with the current events, is that Windows will have removed the keyboard focused element by the time those events are raised. After a little testing I think that we can watch one of the WND_PROC messages to know when a minimize is about to occur, and capture the element with keyboard focus then.

private void ListenForWindowStateChanged(Window? window)
{
    if (window is not null)
    {
        if (PresentationSource.FromVisual(window) is HwndSource source)
        {
            HwndSourceHook hook = Hook;
            source.AddHook(hook);
            //TODO: Need to unregister hooks as well, and ensure we don't double register
        }

        IntPtr Hook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            //https://learn.microsoft.com/en-us/windows/win32/menurc/wm-syscommand
            const int WM_SYSCOMMAND = 0x0112;
            const int SC_MINIMIZE = 0xf020;
            switch (msg)
            {
                case WM_SYSCOMMAND:
                    if (wParam.ToInt64() == SC_MINIMIZE && //Minimize
                        _popupContentControl?.IsKeyboardFocusWithin == true) //Only persistent the one with keyboard focus
                    {
                        var element = Keyboard.FocusedElement;
                        //TODO: Perist keyboard focused element
                    }
                    break;
            }
            return IntPtr.Zero;
        }
    }
}

This code still has the TODOs that would need to be addressed, but this way we can rely on looking at keyboard focus to see which element we care about.

Replaced the Window.StateChanged event-based approach with an
HwndSourceHook-based implementation to handle window state changes
(minimize/restore) more effectively. Removed `_previousWindowState`
and introduced `_hook` for managing system commands (`WM_SYSCOMMAND`).

Added a `Hook` method to handle `SC_MINIMIZE` and `SC_RESTORE`
commands, ensuring proper focus management. Refactored focus
restoration logic to use asynchronous behavior (`Task.Delay` and
`Dispatcher.BeginInvoke`) for improved reliability. Removed redundant
state-checking methods and improved code clarity with constants for
system commands.
@corvinsz
Copy link
Member Author

corvinsz commented Oct 5, 2025

I implemented the changes as you suggested. From my manual testing it works fine, however the UI test I added is failing.
I haven't yet figured out why it fails.
grafik

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

DialogHost accessibility issue
2 participants