Skip to content

Commit 34082b4

Browse files
committed
Refactor DialogHost to use HwndSourceHook for focus
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.
1 parent 3c6ea51 commit 34082b4

File tree

1 file changed

+36
-33
lines changed

1 file changed

+36
-33
lines changed

src/MaterialDesignThemes.Wpf/DialogHost.cs

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public class DialogHost : ContentControl
6565
private DialogClosedEventHandler? _attachedDialogClosedEventHandler;
6666
private IInputElement? _restoreFocusDialogClose;
6767
private IInputElement? _lastFocusedDialogElement;
68-
private WindowState _previousWindowState;
68+
private HwndSourceHook? _hook;
6969
private Action? _currentSnackbarMessageQueueUnPauseAction;
7070

7171
static DialogHost()
@@ -412,48 +412,51 @@ private static void IsOpenPropertyChangedCallback(DependencyObject dependencyObj
412412

413413
private void ListenForWindowStateChanged(Window? window)
414414
{
415-
416-
if (window is not null)
417-
{
418-
window.StateChanged -= Window_StateChanged;
419-
window.StateChanged += Window_StateChanged;
420-
}
421-
}
422-
423-
private void Window_StateChanged(object? sender, EventArgs e)
424-
{
425-
if (sender is not Window window)
415+
if (window is null)
426416
{
427417
return;
428418
}
429419

430-
var windowState = window.WindowState;
431-
if (windowState == WindowState.Minimized)
420+
if (PresentationSource.FromVisual(window) is HwndSource source)
432421
{
433-
_lastFocusedDialogElement = FocusManager.GetFocusedElement(window);
434-
_previousWindowState = windowState;
435-
return;
422+
_hook = Hook;
423+
source.RemoveHook(_hook);
424+
source.AddHook(_hook);
436425
}
437426

438-
// We only need to focus anything manually if the window changes state from Minimized --> (Normal or Maximized)
439-
// Going from Normal --> Maximized (and vice versa) is fine since the focus is already kept correctly
440-
if (IsWindowRestoredFromMinimized())
427+
IntPtr Hook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
441428
{
442-
// Kinda hacky, but without a delay the focus doesn't always get set correctly because the Focus() method fires too early
443-
Task.Delay(50).ContinueWith(_ => this.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
429+
//https://learn.microsoft.com/en-us/windows/win32/menurc/wm-syscommand
430+
const int WM_SYSCOMMAND = 0x0112;
431+
const int SC_MINIMIZE = 0xf020;
432+
const int SC_RESTORE = 0xF120;
433+
434+
long wParamLong = wParam.ToInt64();
435+
switch (msg)
444436
{
445-
if (IsLastFocusedDialogElementFocusable())
446-
{
447-
_lastFocusedDialogElement!.Focus();
448-
}
449-
})));
437+
case WM_SYSCOMMAND:
438+
if (wParamLong == SC_MINIMIZE && //Minimize
439+
_popupContentControl?.IsKeyboardFocusWithin == true) //Only persistent the one with keyboard focus
440+
{
441+
var element = Keyboard.FocusedElement;
442+
_lastFocusedDialogElement = element;
443+
}
444+
else if (wParamLong == SC_RESTORE) //Restore
445+
{
446+
// Kinda hacky, but without a delay the focus doesn't always get set correctly because the Focus() method fires too early
447+
Task.Delay(50).ContinueWith(_ => this.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
448+
{
449+
if (_lastFocusedDialogElement is UIElement { Focusable: true, IsVisible: true })
450+
{
451+
_lastFocusedDialogElement.Focus();
452+
_lastFocusedDialogElement = null;
453+
}
454+
})));
455+
}
456+
break;
457+
}
458+
return IntPtr.Zero;
450459
}
451-
_previousWindowState = windowState;
452-
453-
bool IsWindowRestoredFromMinimized() => (windowState == WindowState.Normal || windowState == WindowState.Maximized) &&
454-
_previousWindowState == WindowState.Minimized;
455-
456-
bool IsLastFocusedDialogElementFocusable() => _lastFocusedDialogElement is UIElement { Focusable: true, IsVisible: true };
457460
}
458461

459462
/// <summary>

0 commit comments

Comments
 (0)