diff --git a/src/Wpf.Ui.Gallery/ViewModels/Pages/DialogsAndFlyouts/MessageBoxViewModel.cs b/src/Wpf.Ui.Gallery/ViewModels/Pages/DialogsAndFlyouts/MessageBoxViewModel.cs
index 940d50429..314653bec 100644
--- a/src/Wpf.Ui.Gallery/ViewModels/Pages/DialogsAndFlyouts/MessageBoxViewModel.cs
+++ b/src/Wpf.Ui.Gallery/ViewModels/Pages/DialogsAndFlyouts/MessageBoxViewModel.cs
@@ -29,4 +29,98 @@ private async Task OnOpenCustomMessageBox(object sender)
_ = await uiMessageBox.ShowDialogAsync();
}
+
+ [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "relay command")]
+ [RelayCommand]
+ private async Task OnOpenThreeButtonMessageBox(object sender)
+ {
+ var uiMessageBox = new Wpf.Ui.Controls.MessageBox
+ {
+ Title = "Confirmation",
+ Content = "Do you want to save your changes before closing?",
+ PrimaryButtonText = "Save",
+ SecondaryButtonText = "Don't Save",
+ CloseButtonText = "Cancel",
+ DefaultFocusedButton = Wpf.Ui.Controls.MessageBoxButton.Secondary,
+ };
+
+ var result = await uiMessageBox.ShowDialogAsync();
+ _ = MessageBox.Show($"You selected: {result}", "Result");
+ }
+
+ [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "relay command")]
+ [RelayCommand]
+ private async Task OnOpenMessageBoxWithFocusableContent(object sender)
+ {
+ var textBox = new System.Windows.Controls.TextBox
+ {
+ Text = "Type something here...",
+ Margin = new System.Windows.Thickness(0, 8, 0, 0),
+ };
+
+ var uiMessageBox = new Wpf.Ui.Controls.MessageBox
+ {
+ Title = "Input Required",
+ Content = new System.Windows.Controls.StackPanel
+ {
+ Children =
+ {
+ new System.Windows.Controls.TextBlock
+ {
+ Text = "Please enter your name:",
+ TextWrapping = System.Windows.TextWrapping.Wrap,
+ },
+ textBox,
+ },
+ },
+ PrimaryButtonText = "OK",
+ SecondaryButtonText = "Cancel",
+ DefaultFocusedButton = Wpf.Ui.Controls.MessageBoxButton.Primary,
+ };
+
+ var result = await uiMessageBox.ShowDialogAsync();
+ if (result == Wpf.Ui.Controls.MessageBoxResult.Primary)
+ {
+ _ = MessageBox.Show($"Hello, {textBox.Text}!", "Greeting");
+ }
+ }
+
+ [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "relay command")]
+ [RelayCommand]
+ private async Task OnOpenMessageBoxWithIcons(object sender)
+ {
+ var uiMessageBox = new Wpf.Ui.Controls.MessageBox
+ {
+ Title = "Warning",
+ Content = "This action cannot be undone. Are you sure you want to continue?",
+ PrimaryButtonText = "Yes, Continue",
+ SecondaryButtonText = "No, Cancel",
+ PrimaryButtonIcon = new Wpf.Ui.Controls.SymbolIcon
+ {
+ Symbol = Wpf.Ui.Controls.SymbolRegular.Warning24,
+ },
+ DefaultFocusedButton = Wpf.Ui.Controls.MessageBoxButton.Secondary,
+ };
+
+ _ = await uiMessageBox.ShowDialogAsync();
+ }
+
+ [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "relay command")]
+ [RelayCommand]
+ private async Task OnOpenMessageBoxWithAutoFocus(object sender)
+ {
+ // DefaultFocusedButton is not set, so focus will automatically be set to primaryButton
+ // when no button has focus and no non-button control has focus
+ var uiMessageBox = new Wpf.Ui.Controls.MessageBox
+ {
+ Title = "Auto Focus",
+ Content = "This MessageBox will automatically set focus to the primary button when no button or non-button control has focus.",
+ PrimaryButtonText = "OK",
+ SecondaryButtonText = "Cancel",
+ // DefaultFocusedButton is intentionally not set
+ };
+
+ var result = await uiMessageBox.ShowDialogAsync();
+ _ = MessageBox.Show($"You selected: {result}", "Result");
+ }
}
diff --git a/src/Wpf.Ui.Gallery/Views/Pages/DialogsAndFlyouts/MessageBoxPage.xaml b/src/Wpf.Ui.Gallery/Views/Pages/DialogsAndFlyouts/MessageBoxPage.xaml
index 577f8f3a8..7c1c23f32 100644
--- a/src/Wpf.Ui.Gallery/Views/Pages/DialogsAndFlyouts/MessageBoxPage.xaml
+++ b/src/Wpf.Ui.Gallery/Views/Pages/DialogsAndFlyouts/MessageBoxPage.xaml
@@ -36,5 +36,33 @@
CommandParameter="{Binding RelativeSource={RelativeSource Self}, Mode=OneWay}"
Content="Open custom MessageBox" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Wpf.Ui/Controls/MessageBox/MessageBox.cs b/src/Wpf.Ui/Controls/MessageBox/MessageBox.cs
index 48b59cfb3..f68fe3d1d 100644
--- a/src/Wpf.Ui/Controls/MessageBox/MessageBox.cs
+++ b/src/Wpf.Ui/Controls/MessageBox/MessageBox.cs
@@ -3,6 +3,7 @@
// Copyright (C) Leszek Pomianowski and WPF UI Contributors.
// All Rights Reserved.
+using System.Linq;
using System.Reflection;
using Wpf.Ui.Input;
using Wpf.Ui.Interop;
@@ -131,6 +132,14 @@ public class MessageBox : System.Windows.Window
new PropertyMetadata(null)
);
+ /// Identifies the dependency property.
+ public static readonly DependencyProperty DefaultFocusedButtonProperty = DependencyProperty.Register(
+ nameof(DefaultFocusedButton),
+ typeof(MessageBoxButton?),
+ typeof(MessageBox),
+ new PropertyMetadata(null)
+ );
+
///
/// Gets or sets a value indicating whether to show the in .
///
@@ -253,6 +262,16 @@ public bool IsPrimaryButtonEnabled
///
public IRelayCommand TemplateButtonCommand => (IRelayCommand)GetValue(TemplateButtonCommandProperty);
+ ///
+ /// Gets or sets the button that should receive focus when the MessageBox is displayed.
+ /// If null, focus will be set to the first available button (Primary > Secondary > Close).
+ ///
+ public MessageBoxButton? DefaultFocusedButton
+ {
+ get => (MessageBoxButton?)GetValue(DefaultFocusedButtonProperty);
+ set => SetValue(DefaultFocusedButtonProperty, value);
+ }
+
#if !NET8_0_OR_GREATER
private static readonly PropertyInfo CanCenterOverWPFOwnerPropertyInfo = typeof(Window).GetProperty(
"CanCenterOverWPFOwner",
@@ -275,6 +294,13 @@ public MessageBox()
var self = (MessageBox)sender;
self.OnLoaded();
};
+
+ ContentRendered += static (sender, _) =>
+ {
+ var self = (MessageBox)sender;
+ self.OnContentRendered();
+ };
+
}
protected TaskCompletionSource? Tcs { get; set; }
@@ -317,6 +343,27 @@ public async Task ShowDialogAsync(
{
RemoveTitleBarAndApplyMica();
+ // If Owner is not set, try to set it to the active window
+ if (Owner == null)
+ {
+ var activeWindow = System.Windows.Application.Current?.Windows
+ .OfType()
+ .FirstOrDefault(w => w.IsActive);
+
+ if (activeWindow != null)
+ {
+ Owner = activeWindow;
+ }
+ }
+
+ // Set WindowStartupLocation to CenterOwner if not explicitly set
+ if (WindowStartupLocation == WindowStartupLocation.Manual)
+ {
+ WindowStartupLocation = Owner != null
+ ? WindowStartupLocation.CenterOwner
+ : WindowStartupLocation.CenterScreen;
+ }
+
if (showAsDialog)
{
base.ShowDialog();
@@ -354,7 +401,7 @@ protected virtual void OnLoaded()
CenterWindowOnScreen();
break;
case WindowStartupLocation.CenterOwner:
- if (!CanCenterOverWPFOwner() || Owner.WindowState is WindowState.Minimized)
+ if (Owner == null || !CanCenterOverWPFOwner() || Owner.WindowState is WindowState.Minimized)
{
CenterWindowOnScreen();
}
@@ -367,8 +414,15 @@ protected virtual void OnLoaded()
default:
throw new InvalidOperationException();
}
+
+ // Set focus to the first available button after the visual tree is fully loaded
+ // Loaded event provides the appropriate timing for setting focus, so we call it directly
+ // without delay to avoid interfering with other developers' focus logic
+ // Skip IsFocused check at this point as the window may not have focus yet
+ SetFocusToFirstAvailableButton(checkIsFocused: false);
}
+
// CanCenterOverWPFOwner property see https://source.dot.net/#PresentationFramework/System/Windows/Window.cs,e679e433777b21b8
private bool CanCenterOverWPFOwner()
{
@@ -402,6 +456,27 @@ protected virtual void ResizeToContentSize(UIElement rootElement)
ResizeHeight(rootElement);
}
+ public override void OnApplyTemplate()
+ {
+ base.OnApplyTemplate();
+
+ // After template is applied, try to set focus
+ // OnLoaded will also try, but this ensures we try as early as possible
+ // Skip IsFocused check at this point as the window may not have focus yet
+ SetFocusToFirstAvailableButton(checkIsFocused: false);
+ }
+
+ ///
+ /// Occurs after ContentRendered event
+ ///
+ protected virtual void OnContentRendered()
+ {
+ // Set focus after content is rendered
+ // This ensures the window is fully displayed before setting focus
+ // At this point, check IsFocused to avoid overriding developer's focus logic
+ SetFocusToFirstAvailableButton(checkIsFocused: true);
+ }
+
protected override void OnClosing(CancelEventArgs e)
{
base.OnClosing(e);
@@ -490,4 +565,141 @@ private void ResizeHeight(UIElement element)
SetCurrentValue(MaxWidthProperty, Width);
}
}
+
+ ///
+ /// Checks if a button is available and enabled for focus.
+ ///
+ private static bool IsButtonAvailableForFocus(Button? button, bool isEnabled)
+ {
+ return button != null
+ && isEnabled
+ && button.IsEnabled
+ && button.IsVisible
+ && button.Focusable;
+ }
+
+ ///
+ /// Attempts to set focus to the specified button using multiple methods.
+ ///
+ private static bool TrySetFocusToButton(Button button)
+ {
+ // Method 1: Keyboard.Focus (most reliable for modal dialogs)
+ if (System.Windows.Input.Keyboard.Focus(button) == button)
+ {
+ return true;
+ }
+
+ // Method 2: Focus() method
+ if (button.Focus())
+ {
+ return true;
+ }
+
+ // Method 3: MoveFocus
+ var request = new System.Windows.Input.TraversalRequest(System.Windows.Input.FocusNavigationDirection.First);
+ return button.MoveFocus(request);
+ }
+
+ ///
+ /// Sets focus to the first available button in the MessageBox.
+ /// Focus will be set to the first available button (Primary > Secondary > Close).
+ ///
+ /// If true, check IsFocused before setting focus. If false, skip the check.
+ /// True if focus was successfully set, false otherwise.
+ private bool SetFocusToFirstAvailableButton(bool checkIsFocused = true)
+ {
+ // Get buttons directly from template using GetTemplateChild for efficiency
+ Button? primaryButton = GetTemplateChild("PART_MessageBoxPrimaryButton") as Button;
+ Button? secondaryButton = GetTemplateChild("PART_MessageBoxSecondaryButton") as Button;
+ Button? closeButton = GetTemplateChild("PART_MessageBoxCloseButton") as Button;
+
+ // If template is not applied yet, buttons will be null
+ if (primaryButton == null && secondaryButton == null && closeButton == null)
+ {
+ return false;
+ }
+
+ // Check if focus is already set to a button or to a non-button control
+ // If checkIsFocused is true, we need to verify that:
+ // 1. Focus is not already on any button (if so, don't override)
+ // 2. Focus is not on a non-button control (e.g., TextBox) - developer's focus logic is active
+ // 3. If focus is on MessageBox itself or nowhere, we should set focus to primaryButton
+ if (checkIsFocused)
+ {
+ var currentFocusedElement = System.Windows.Input.Keyboard.FocusedElement;
+
+ // Check if focus is already on any button
+ if (currentFocusedElement == primaryButton ||
+ currentFocusedElement == secondaryButton ||
+ currentFocusedElement == closeButton)
+ {
+ // Focus is already on a button, don't override
+ return false;
+ }
+
+ // Check if focus is on a non-button control within MessageBox
+ // If IsFocused is false and currentFocusedElement is not the MessageBox itself,
+ // it means an internal control (e.g., TextBox) has focus
+ if (!IsFocused && currentFocusedElement != this)
+ {
+ // An internal control (not the MessageBox itself) has focus, avoid overriding
+ return false;
+ }
+
+ // If we reach here, focus is either on MessageBox itself or nowhere
+ // In this case, we should set focus to primaryButton
+ }
+
+ // Ensure the window is active and can receive focus
+ if (!IsActive)
+ {
+ Activate();
+ }
+
+ // Update layout once for the window
+ UpdateLayout();
+
+ // If DefaultFocusedButton is specified, try to set focus to that button
+ if (DefaultFocusedButton.HasValue)
+ {
+ Button? targetButton = DefaultFocusedButton.Value switch
+ {
+ MessageBoxButton.Primary => primaryButton,
+ MessageBoxButton.Secondary => secondaryButton,
+ MessageBoxButton.Close => closeButton,
+ _ => null,
+ };
+
+ bool isButtonEnabled = DefaultFocusedButton.Value switch
+ {
+ MessageBoxButton.Primary => IsPrimaryButtonEnabled,
+ MessageBoxButton.Secondary => IsSecondaryButtonEnabled,
+ MessageBoxButton.Close => IsCloseButtonEnabled,
+ _ => false,
+ };
+
+ if (IsButtonAvailableForFocus(targetButton, isButtonEnabled))
+ {
+ return TrySetFocusToButton(targetButton!);
+ }
+ }
+
+ // Fallback to automatic selection: Set focus to the first available button
+ if (IsButtonAvailableForFocus(primaryButton, IsPrimaryButtonEnabled))
+ {
+ return TrySetFocusToButton(primaryButton!);
+ }
+
+ if (IsButtonAvailableForFocus(secondaryButton, IsSecondaryButtonEnabled))
+ {
+ return TrySetFocusToButton(secondaryButton!);
+ }
+
+ if (IsButtonAvailableForFocus(closeButton, IsCloseButtonEnabled))
+ {
+ return TrySetFocusToButton(closeButton!);
+ }
+
+ return false;
+ }
}
diff --git a/src/Wpf.Ui/Controls/MessageBox/MessageBox.xaml b/src/Wpf.Ui/Controls/MessageBox/MessageBox.xaml
index b3537d189..577c860f1 100644
--- a/src/Wpf.Ui/Controls/MessageBox/MessageBox.xaml
+++ b/src/Wpf.Ui/Controls/MessageBox/MessageBox.xaml
@@ -48,15 +48,15 @@
-
-
-
+
+
+
-
+
+ TextWrapping="Wrap"
+ Focusable="False" />
+ ShowMinimize="False"
+ Focusable="False" />
+ ContentTemplateSelector="{TemplateBinding ContentTemplateSelector}"
+ Focusable="False">