From ddb4b21c6315faddfdbb22c419de0083c106107c Mon Sep 17 00:00:00 2001
From: Corvin <43533385+corvinsz@users.noreply.github.com>
Date: Sun, 8 Nov 2026 13:38:08 +0100
Subject: [PATCH 1/2] fix(DialogHost): restore focus inside dialog after window
is minimized and restored
---
src/MaterialDesignThemes.Wpf/DialogHost.cs | 48 ++++++++++
.../DialogHost/WithMultipleTextBoxes.xaml | 27 ++++++
.../DialogHost/WithMultipleTextBoxes.xaml.cs | 16 ++++
.../WPF/DialogHosts/DialogHostTests.cs | 89 +++++++++++++++++++
4 files changed, 180 insertions(+)
create mode 100644 tests/MaterialDesignThemes.UITests/Samples/DialogHost/WithMultipleTextBoxes.xaml
create mode 100644 tests/MaterialDesignThemes.UITests/Samples/DialogHost/WithMultipleTextBoxes.xaml.cs
diff --git a/src/MaterialDesignThemes.Wpf/DialogHost.cs b/src/MaterialDesignThemes.Wpf/DialogHost.cs
index f0b39e45b9..ed300694ea 100644
--- a/src/MaterialDesignThemes.Wpf/DialogHost.cs
+++ b/src/MaterialDesignThemes.Wpf/DialogHost.cs
@@ -75,6 +75,8 @@ public class DialogHost : ContentControl
private DialogClosingEventHandler? _attachedDialogClosingEventHandler;
private DialogClosedEventHandler? _attachedDialogClosedEventHandler;
private IInputElement? _restoreFocusDialogClose;
+ private Window? _window;
+ private UIElement? _lastFocusedPopupElement;
private Action? _currentSnackbarMessageQueueUnPauseAction;
static DialogHost()
@@ -1010,8 +1012,50 @@ private void CloseDialogHandler(object sender, ExecutedRoutedEventArgs executedR
private string GetStateName()
=> IsOpen ? OpenStateName : ClosedStateName;
+ protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
+ {
+ if (e.NewFocus is UIElement element &&
+ _popup?.Child is { } child &&
+ child.IsAncestorOf(element))
+ {
+ _lastFocusedPopupElement = element;
+ }
+ base.OnGotKeyboardFocus(e);
+ }
+
+ protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
+ {
+ // Minimizing moves focus to null; only forget the element when the user focuses something else in this window
+ if (e.NewFocus is DependencyObject newFocus &&
+ _popup?.Child is { } child &&
+ !child.IsAncestorOf(newFocus) &&
+ Window.GetWindow(newFocus) == _window)
+ {
+ _lastFocusedPopupElement = null;
+ }
+ base.OnLostKeyboardFocus(e);
+ }
+
+ private void WindowOnActivated(object? sender, EventArgs e)
+ {
+ // The popup has its own window handle, so WPF does not restore focus into it when the window is re-activated (issue #3434)
+ Dispatcher.BeginInvoke(() =>
+ {
+ if (IsOpen &&
+ _popup is { IsOpen: true, Child: { IsKeyboardFocusWithin: false } child } &&
+ _lastFocusedPopupElement is { IsVisible: true } element &&
+ child.IsAncestorOf(element))
+ {
+ element.Focus();
+ }
+ }, DispatcherPriority.Input);
+ }
+
private void OnUnloaded(object sender, RoutedEventArgs routedEventArgs)
{
+ _window?.Activated -= WindowOnActivated;
+ _window = null;
+
foreach (var weakRef in LoadedInstances.ToList())
{
if (!weakRef.TryGetTarget(out DialogHost? dialogHost) || ReferenceEquals(dialogHost, this))
@@ -1024,6 +1068,10 @@ private void OnUnloaded(object sender, RoutedEventArgs routedEventArgs)
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
+ _window?.Activated -= WindowOnActivated;
+ _window = Window.GetWindow(this);
+ _window?.Activated += WindowOnActivated;
+
foreach (var weakRef in LoadedInstances.ToList())
{
if (weakRef.TryGetTarget(out DialogHost? dialogHost) && ReferenceEquals(dialogHost, this))
diff --git a/tests/MaterialDesignThemes.UITests/Samples/DialogHost/WithMultipleTextBoxes.xaml b/tests/MaterialDesignThemes.UITests/Samples/DialogHost/WithMultipleTextBoxes.xaml
new file mode 100644
index 0000000000..6206c001ea
--- /dev/null
+++ b/tests/MaterialDesignThemes.UITests/Samples/DialogHost/WithMultipleTextBoxes.xaml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/MaterialDesignThemes.UITests/Samples/DialogHost/WithMultipleTextBoxes.xaml.cs b/tests/MaterialDesignThemes.UITests/Samples/DialogHost/WithMultipleTextBoxes.xaml.cs
new file mode 100644
index 0000000000..0911fa7f73
--- /dev/null
+++ b/tests/MaterialDesignThemes.UITests/Samples/DialogHost/WithMultipleTextBoxes.xaml.cs
@@ -0,0 +1,16 @@
+namespace MaterialDesignThemes.UITests.Samples.DialogHost;
+
+///
+/// Interaction logic for WithMultipleTextBoxes.xaml
+///
+public partial class WithMultipleTextBoxes : UserControl
+{
+ public WithMultipleTextBoxes()
+ {
+ InitializeComponent();
+ }
+ private void DialogHost_Loaded(object sender, RoutedEventArgs e)
+ {
+ SampleDialogHost.IsOpen = true;
+ }
+}
diff --git a/tests/MaterialDesignThemes.UITests/WPF/DialogHosts/DialogHostTests.cs b/tests/MaterialDesignThemes.UITests/WPF/DialogHosts/DialogHostTests.cs
index fa84783942..78336475f8 100644
--- a/tests/MaterialDesignThemes.UITests/WPF/DialogHosts/DialogHostTests.cs
+++ b/tests/MaterialDesignThemes.UITests/WPF/DialogHosts/DialogHostTests.cs
@@ -665,6 +665,95 @@ public async Task DialogHost_FocusMovedInsideDialogWhileOpening_IsKept(string di
}
}
+ [Test]
+ [Description("Issue 3434")]
+ [Arguments(WindowState.Minimized, WindowState.Maximized, null)]
+ [Arguments(WindowState.Minimized, WindowState.Normal, null)]
+ [Arguments(WindowState.Minimized, WindowState.Maximized, "MaterialDesignEmbeddedDialogHost")]
+ [Arguments(WindowState.Minimized, WindowState.Normal, "MaterialDesignEmbeddedDialogHost")]
+ public async Task DialogHost_WhenWindowStateChanges_FocusedElementStaysFocused(WindowState firstWindowState,
+ WindowState secondWindowState,
+ string? styleName)
+ {
+ await using var recorder = new TestRecorder(App);
+
+ var dialogHost = (await LoadUserControl()).As();
+ if (styleName is not null)
+ {
+ await dialogHost.RemoteExecute(SetDialogHostStyle, styleName);
+ }
+ await Task.Delay(400);
+
+ // Select the second TextBox
+ var tbTwo = await dialogHost.GetElement("TextBoxTwo");
+ await tbTwo.MoveKeyboardFocus();
+ await Assert.That(await tbTwo.GetIsFocused()).IsTrue();
+
+ // First state
+ await dialogHost.RemoteExecute(SetStateOfParentWindow, firstWindowState);
+ await Task.Delay(400);
+ // Second state
+ await dialogHost.RemoteExecute(SetStateOfParentWindow, secondWindowState);
+ await Task.Delay(400);
+
+ // After changing state of the window the previously focused element should be focused again
+ await Assert.That(await tbTwo.GetIsFocused()).IsTrue();
+ recorder.Success();
+ }
+
+ [Test]
+ [Description("Issue 3434")]
+ //[Arguments(null)]
+ [Arguments("MaterialDesignEmbeddedDialogHost")]
+ public async Task DialogHost_WhenWindowStateChanges_FocusOutsideDialogStaysOutside(string? styleName)
+ {
+ await using var recorder = new TestRecorder(App);
+
+ var grid = await LoadXaml("""
+
+
+
+
+
+
+
+
+
+
+
+
+ """);
+ var dialogHost = await grid.GetElement("DialogHost");
+ if (styleName is not null)
+ {
+ await dialogHost.RemoteExecute(SetDialogHostStyle, styleName);
+ }
+ await Task.Delay(400);
+
+ var insideTextBox = await dialogHost.GetElement("InsideTextBox");
+ var outsideTextBox = await grid.GetElement("OutsideTextBox");
+ await insideTextBox.MoveKeyboardFocus();
+ await Assert.That(await insideTextBox.GetIsFocused()).IsTrue();
+ await outsideTextBox.MoveKeyboardFocus();
+ await Assert.That(await outsideTextBox.GetIsFocused()).IsTrue();
+
+ await dialogHost.RemoteExecute(SetStateOfParentWindow, WindowState.Minimized);
+ await Task.Delay(400);
+ await dialogHost.RemoteExecute(SetStateOfParentWindow, WindowState.Normal);
+ await Task.Delay(400);
+
+ // Focus was outside the dialog before minimizing, so it must not be moved into the dialog
+ await Assert.That(await outsideTextBox.GetIsFocused()).IsTrue();
+ recorder.Success();
+ }
+
+ private static object SetStateOfParentWindow(DialogHost dialogHost, WindowState state)
+ {
+ var window = Window.GetWindow(dialogHost);
+ window.WindowState = state;
+ return null!;
+ }
+
private static object SetDialogHostStyle(DialogHost dialogHost, string styleName)
{
var style = (Style)dialogHost.FindResource(styleName);
From 6f339ebc89e4cfa839f9ace3546acfc458cc0a0b Mon Sep 17 00:00:00 2001
From: Corvin <43533385+corvinsz@users.noreply.github.com>
Date: Sun, 8 Nov 2026 13:49:04 +0100
Subject: [PATCH 2/2] comment in test case
---
.../WPF/DialogHosts/DialogHostTests.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/MaterialDesignThemes.UITests/WPF/DialogHosts/DialogHostTests.cs b/tests/MaterialDesignThemes.UITests/WPF/DialogHosts/DialogHostTests.cs
index 78336475f8..e376fd7d83 100644
--- a/tests/MaterialDesignThemes.UITests/WPF/DialogHosts/DialogHostTests.cs
+++ b/tests/MaterialDesignThemes.UITests/WPF/DialogHosts/DialogHostTests.cs
@@ -703,7 +703,7 @@ public async Task DialogHost_WhenWindowStateChanges_FocusedElementStaysFocused(W
[Test]
[Description("Issue 3434")]
- //[Arguments(null)]
+ [Arguments(null)]
[Arguments("MaterialDesignEmbeddedDialogHost")]
public async Task DialogHost_WhenWindowStateChanges_FocusOutsideDialogStaysOutside(string? styleName)
{