Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 22 additions & 8 deletions src/MaterialDesignThemes.Wpf/DialogHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -431,17 +431,28 @@ private static void IsOpenPropertyChangedCallback(DependencyObject dependencyObj
dialogHost.DialogOpenedCallback?.Invoke(dialogHost, dialogOpenedEventArgs);
dialogHost._asyncShowOpenedEventHandler?.Invoke(dialogHost, dialogOpenedEventArgs);

//https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit/issues/187
//totally not happy about this, but on immediate validation we can get some weird looking stuff...give WPF a kick to refresh...
Task.Delay(300).ContinueWith(t => dialogHost.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
if (TransitionAssist.GetDisableTransitions(dialogHost))
{
CommandManager.InvalidateRequerySuggested();
//Without a transition there is nothing to wait for: the dialog is complete on the next frame,
//and waiting would send the first keys typed into it to the controls behind the dialog
dialogHost.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(OnDialogShown));
}
else
{
//https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit/issues/187
//totally not happy about this, but on immediate validation we can get some weird looking stuff...give WPF a kick to refresh...
//Delay focusing the popup until after the animation has some time, Issue #2912
UIElement? child = dialogHost.FocusPopup();
Task.Delay(300).ContinueWith(t => dialogHost.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(OnDialogShown)));
}

child?.InvalidateVisual();
void OnDialogShown()
{
CommandManager.InvalidateRequerySuggested();
//If the content has already moved the focus into the dialog (for example when it loaded), leave it there
UIElement? child = dialogHost.FocusPopup(keepFocusWithin: true);

})));
child?.InvalidateVisual();
}
}

/// <summary>
Expand Down Expand Up @@ -891,13 +902,16 @@ internal void InternalClose(object? parameter)
/// <summary>
/// Attempts to focus the content of a popup.
/// </summary>
/// <param name="keepFocusWithin">When true, the focus is left alone if it is already inside the popup content.</param>
/// <returns>The popup content.</returns>
internal UIElement? FocusPopup()
internal UIElement? FocusPopup(bool keepFocusWithin = false)
{
var child = _popup?.Child ?? _popupContentControl;
if (child is null) return null;

CommandManager.InvalidateRequerySuggested();
if (keepFocusWithin && child.IsKeyboardFocusWithin) return child;

var focusable = child.VisualDepthFirstTraversal().OfType<UIElement>().FirstOrDefault(ui => ui.Focusable);
if (focusable is null) return null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -574,12 +574,99 @@ public async Task DialogHost_TrapsFocusInsidePopup_WhenTabbing(string dialogHost
await Wait.For(async () => await Assert.That(await textBoxOne.GetIsFocused()).IsTrue());

recorder.Success();
}

[Test]
[Description("Issue 4097")]
[Arguments("")]
[Arguments("MaterialDesignEmbeddedDialogHost")]
public async Task DialogHost_WithTransitionsDisabled_FocusesDialogWithoutWaitingForTransition(string dialogHostStyle)
{
await using var recorder = new TestRecorder(App);

var dialogHost = await LoadXaml<DialogHost>("""
<materialDesign:DialogHost materialDesign:TransitionAssist.DisableTransitions="True">
<materialDesign:DialogHost.DialogContent>
<TextBox x:Name="DialogTextBox" Width="200" />
</materialDesign:DialogHost.DialogContent>
</materialDesign:DialogHost>
""");

if (!string.IsNullOrEmpty(dialogHostStyle))
{
await dialogHost.RemoteExecute(SetDialogHostStyle, dialogHostStyle);
}

string? focusedElementName = await dialogHost.RemoteExecute(OpenAndGetFocusedElementName);

await Assert.That(focusedElementName).IsEqualTo("DialogTextBox");

recorder.Success();

static async Task<string?> OpenAndGetFocusedElementName(DialogHost dialogHost)
{
// Opened and closed once first, so the check below does not include loading the templates
dialogHost.IsOpen = true;
await Task.Delay(500);
dialogHost.IsOpen = false;
await Task.Delay(500);

dialogHost.IsOpen = true;
// Well within the 300 ms the dialog host waits for when there is a transition
await Task.Delay(100);
return (Keyboard.FocusedElement as FrameworkElement)?.Name;
}
}

[Test]
[Description("Issue 4097")]
[Arguments("")]
[Arguments("MaterialDesignEmbeddedDialogHost")]
public async Task DialogHost_FocusMovedInsideDialogWhileOpening_IsKept(string dialogHostStyle)
{
await using var recorder = new TestRecorder(App);

static object SetDialogHostStyle(DialogHost dialogHost, string styleName)
var dialogHost = await LoadXaml<DialogHost>("""
<materialDesign:DialogHost>
<materialDesign:DialogHost.DialogContent>
<StackPanel Width="300">
<TextBox x:Name="TextBoxOne" />
<TextBox x:Name="TextBoxTwo" />
</StackPanel>
</materialDesign:DialogHost.DialogContent>
</materialDesign:DialogHost>
""");

if (!string.IsNullOrEmpty(dialogHostStyle))
{
var style = (Style)dialogHost.FindResource(styleName);
dialogHost.Style = style;
return null!;
await dialogHost.RemoteExecute(SetDialogHostStyle, dialogHostStyle);
}

string? focusedElementName = await dialogHost.RemoteExecute(OpenFocusSecondTextBoxAndWait);

await Assert.That(focusedElementName).IsEqualTo("TextBoxTwo");

recorder.Success();

static async Task<string?> OpenFocusSecondTextBoxAndWait(DialogHost dialogHost)
{
dialogHost.IsOpen = true;
await Task.Delay(50);

// As when the user clicks the second TextBox right after the dialog appears
var content = (Panel)dialogHost.DialogContent!;
Keyboard.Focus(content.Children[1]);

// Past the delay after which the dialog host moves the focus into the dialog
await Task.Delay(800);
return (Keyboard.FocusedElement as FrameworkElement)?.Name;
}
}

private static object SetDialogHostStyle(DialogHost dialogHost, string styleName)
{
var style = (Style)dialogHost.FindResource(styleName);
dialogHost.Style = style;
return null!;
}
}
Loading