Skip to content

Commit 8822070

Browse files
committed
restore focus to the last focused element inside of the dialog
1 parent be0b22b commit 8822070

File tree

1 file changed

+49
-1
lines changed

1 file changed

+49
-1
lines changed

src/MaterialDesignThemes.Wpf/DialogHost.cs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ public class DialogHost : ContentControl
6464
private DialogClosingEventHandler? _attachedDialogClosingEventHandler;
6565
private DialogClosedEventHandler? _attachedDialogClosedEventHandler;
6666
private IInputElement? _restoreFocusDialogClose;
67+
private IInputElement? _lastFocusedDialogElement;
68+
private WindowState _previousWindowState;
6769
private Action? _currentSnackbarMessageQueueUnPauseAction;
6870

6971
static DialogHost()
@@ -370,6 +372,7 @@ private static void IsOpenPropertyChangedCallback(DependencyObject dependencyObj
370372

371373
dialogHost.CurrentSession = new DialogSession(dialogHost);
372374
var window = Window.GetWindow(dialogHost);
375+
dialogHost.ListenForWindowStateChanged(window);
373376
if (!dialogHost.IsRestoreFocusDisabled)
374377
{
375378
dialogHost._restoreFocusDialogClose = window != null ? FocusManager.GetFocusedElement(window) : null;
@@ -395,7 +398,8 @@ private static void IsOpenPropertyChangedCallback(DependencyObject dependencyObj
395398

396399
//https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit/issues/187
397400
//totally not happy about this, but on immediate validation we can get some weird looking stuff...give WPF a kick to refresh...
398-
Task.Delay(300).ContinueWith(t => dialogHost.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => {
401+
Task.Delay(300).ContinueWith(t => dialogHost.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
402+
{
399403
CommandManager.InvalidateRequerySuggested();
400404
//Delay focusing the popup until after the animation has some time, Issue #2912
401405
UIElement? child = dialogHost.FocusPopup();
@@ -405,6 +409,50 @@ private static void IsOpenPropertyChangedCallback(DependencyObject dependencyObj
405409
})));
406410
}
407411

412+
413+
private void ListenForWindowStateChanged(Window? window)
414+
{
415+
window ??= Window.GetWindow(this);
416+
417+
if (window is not null)
418+
{
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)
426+
{
427+
return;
428+
}
429+
430+
var windowState = window.WindowState;
431+
if (windowState == WindowState.Minimized)
432+
{
433+
_lastFocusedDialogElement = FocusManager.GetFocusedElement(window);
434+
_previousWindowState = windowState;
435+
return;
436+
}
437+
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() && IsLastFocusedDialogElementFocusable())
441+
{
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(() =>
444+
{
445+
_lastFocusedDialogElement!.Focus();
446+
})));
447+
}
448+
_previousWindowState = windowState;
449+
450+
bool IsWindowRestoredFromMinimized() => (windowState == WindowState.Normal || windowState == WindowState.Maximized) &&
451+
_previousWindowState == WindowState.Minimized;
452+
453+
bool IsLastFocusedDialogElementFocusable() => _lastFocusedDialogElement is UIElement { Focusable: true, IsVisible: true };
454+
}
455+
408456
/// <summary>
409457
/// Returns a DialogSession for the currently open dialog for managing it programmatically. If no dialog is open, CurrentSession will return null
410458
/// </summary>

0 commit comments

Comments
 (0)