Skip to content
Open
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
57 changes: 57 additions & 0 deletions Sample Applications/WPFGallery/Controls/ThemedMessageBox.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<Window
x:Class="WPFGallery.Controls.ThemedMessageBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
MinWidth="320"
MaxWidth="480"
Background="{DynamicResource MessageBoxBackground}"
Foreground="{DynamicResource MessageBoxForeground}"
ResizeMode="NoResize"
ShowInTaskbar="False"
SizeToContent="WidthAndHeight"
TextOptions.TextFormattingMode="Display"
WindowStartupLocation="CenterOwner"
mc:Ignorable="d">

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>

<Grid Grid.Row="0" Margin="24,24,24,24">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
x:Name="IconGlyph"
Grid.Column="0"
Margin="0,0,16,0"
VerticalAlignment="Top"
FontFamily="{DynamicResource SymbolThemeFontFamily}"
FontSize="28"
Visibility="Collapsed" />
<TextBlock
x:Name="MessageText"
Grid.Column="1"
VerticalAlignment="Center"
Foreground="{DynamicResource MessageBoxForeground}"
TextWrapping="Wrap" />
</Grid>

<Border
Grid.Row="1"
Padding="24,16"
Background="{DynamicResource MessageBoxTopOverlay}"
BorderBrush="{DynamicResource MessageBoxSeparatorBorderBrush}"
BorderThickness="0,1,0,0">
<StackPanel
x:Name="ButtonPanel"
HorizontalAlignment="Right"
Orientation="Horizontal" />
</Border>
</Grid>
</Window>
205 changes: 205 additions & 0 deletions Sample Applications/WPFGallery/Controls/ThemedMessageBox.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
using System.Linq;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Controls;
using System.Windows.Media;

namespace WPFGallery.Controls
{
/// <summary>
/// A Fluent-themed replacement for <see cref="System.Windows.MessageBox"/>. Because the native
/// Win32 message box does not honor the application's Fluent <c>ThemeMode</c>, it stays light in
/// dark mode and fails contrast requirements. This dialog is a WPF <see cref="Window"/>, so it
/// inherits the app theme (Light/Dark/High Contrast) and adapts automatically.
/// </summary>
public partial class ThemedMessageBox : Window
{
private MessageBoxResult _result = MessageBoxResult.None;
private MessageBoxResult _cancelResult = MessageBoxResult.None;

private ThemedMessageBox()
{
InitializeComponent();
}

public static MessageBoxResult Show(string messageBoxText)
=> Show(messageBoxText, string.Empty, MessageBoxButton.OK, MessageBoxImage.None, MessageBoxResult.None);

public static MessageBoxResult Show(string messageBoxText, string caption)
=> Show(messageBoxText, caption, MessageBoxButton.OK, MessageBoxImage.None, MessageBoxResult.None);

public static MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button)
=> Show(messageBoxText, caption, button, MessageBoxImage.None, MessageBoxResult.None);

public static MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon)
=> Show(messageBoxText, caption, button, icon, MessageBoxResult.None);

public static MessageBoxResult Show(string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, MessageBoxResult defaultResult)
{
var dialog = new ThemedMessageBox();
dialog.Build(messageBoxText, caption, button, icon, defaultResult);

Window? owner = Application.Current?.Windows
.OfType<Window>()
.FirstOrDefault(w => w.IsActive) ?? Application.Current?.MainWindow;

if (owner != null && owner != dialog && owner.IsLoaded)
{
dialog.Owner = owner;
}
else
{
dialog.WindowStartupLocation = WindowStartupLocation.CenterScreen;
}

dialog.ShowDialog();
return dialog._result;
}

private void Build(string messageBoxText, string caption, MessageBoxButton button, MessageBoxImage icon, MessageBoxResult defaultResult)
{
Title = caption ?? string.Empty;
MessageText.Text = messageBoxText ?? string.Empty;
AutomationProperties.SetName(this, string.IsNullOrEmpty(caption) ? "Message" : caption);

ApplyIcon(icon);
BuildButtons(button, defaultResult);
}

private void ApplyIcon(MessageBoxImage icon)
{
string glyph;
string brushKey;
string name;

switch (icon)
{
case MessageBoxImage.Error: // also Stop, Hand
glyph = "\uEA39";
brushKey = "SystemFillColorCriticalBrush";
name = "Error";
break;
case MessageBoxImage.Warning: // also Exclamation
glyph = "\uE7BA";
brushKey = "SystemFillColorCautionBrush";
name = "Warning";
break;
case MessageBoxImage.Information: // also Asterisk
glyph = "\uE946";
brushKey = "SystemFillColorAttentionBrush";
name = "Information";
break;
case MessageBoxImage.Question:
glyph = "\uE9CE";
brushKey = "SystemFillColorAttentionBrush";
name = "Question";
break;
default:
IconGlyph.Visibility = Visibility.Collapsed;
return;
}

IconGlyph.Text = glyph;
if (TryFindResource(brushKey) is Brush brush)
{
IconGlyph.Foreground = brush;
}

AutomationProperties.SetName(IconGlyph, name);
IconGlyph.Visibility = Visibility.Visible;
}

private void BuildButtons(MessageBoxButton button, MessageBoxResult defaultResult)
{
var buttons = button switch
{
MessageBoxButton.OKCancel => new[]
{
(MessageBoxResult.OK, "OK"),
(MessageBoxResult.Cancel, "Cancel"),
},
MessageBoxButton.YesNo => new[]
{
(MessageBoxResult.Yes, "Yes"),
(MessageBoxResult.No, "No"),
},
MessageBoxButton.YesNoCancel => new[]
{
(MessageBoxResult.Yes, "Yes"),
(MessageBoxResult.No, "No"),
(MessageBoxResult.Cancel, "Cancel"),
},
MessageBoxButton.AbortRetryIgnore => new[]
{
(MessageBoxResult.Abort, "Abort"),
(MessageBoxResult.Retry, "Retry"),
(MessageBoxResult.Ignore, "Ignore"),
},
MessageBoxButton.RetryCancel => new[]
{
(MessageBoxResult.Retry, "Retry"),
(MessageBoxResult.Cancel, "Cancel"),
},
MessageBoxButton.CancelTryContinue => new[]
{
(MessageBoxResult.Cancel, "Cancel"),
(MessageBoxResult.TryAgain, "Try Again"),
(MessageBoxResult.Continue, "Continue"),
},
_ => new[]
{
(MessageBoxResult.OK, "OK"),
},
};

// Closing via Esc or the title-bar close button returns the cancel-equivalent, mirroring
// the native message box: Cancel if present, otherwise No, otherwise the last button.
_cancelResult = buttons.Any(b => b.Item1 == MessageBoxResult.Cancel) ? MessageBoxResult.Cancel
: buttons.Any(b => b.Item1 == MessageBoxResult.No) ? MessageBoxResult.No
: buttons[buttons.Length - 1].Item1;

bool hasExplicitDefault = defaultResult != MessageBoxResult.None
&& buttons.Any(b => b.Item1 == defaultResult);

Button? defaultButton = null;

for (int i = 0; i < buttons.Length; i++)
{
(MessageBoxResult result, string label) = buttons[i];

var uiButton = new Button
{
Content = label,
MinWidth = 80,
Margin = new Thickness(i == 0 ? 0 : 8, 0, 0, 0),
IsDefault = hasExplicitDefault ? result == defaultResult : i == 0,
IsCancel = result == _cancelResult,
};
uiButton.Click += (_, _) =>
{
_result = result;
Close();
};

if (uiButton.IsDefault)
{
defaultButton = uiButton;
}

ButtonPanel.Children.Add(uiButton);
}

Loaded += (_, _) => defaultButton?.Focus();
}

protected override void OnClosed(System.EventArgs e)
{
if (_result == MessageBoxResult.None)
{
_result = _cancelResult;
}

base.OnClosed(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Windows.Navigation;
using System.Windows.Shapes;

using WPFGallery.Controls;
using WPFGallery.ViewModels;

namespace WPFGallery.Views
Expand All @@ -22,13 +23,13 @@ public MessageBoxPage(MessageBoxPageViewModel viewModel)

private void ShowDefaultMessageButton_Click(object sender, RoutedEventArgs e)
{
var result = MessageBox.Show("This is a simple message box!");
var result = ThemedMessageBox.Show("This is a simple message box!");
ViewModel.DefaultMessageResult = $"Result: {result}";
}

private void ShowCustomTitleButton_Click(object sender, RoutedEventArgs e)
{
var result = MessageBox.Show("This is a detailed description of what happened or what action is needed.", "Custom Title");
var result = ThemedMessageBox.Show("This is a detailed description of what happened or what action is needed.", "Custom Title");
ViewModel.CustomTitleResult = $"Result: {result}";
}

Expand Down Expand Up @@ -58,7 +59,7 @@ private void ShowButtonFromComboBox_Click(object sender, RoutedEventArgs e)
_ => "OK"
};

var result = MessageBox.Show($"This MessageBox has {buttonName} button(s).", $"{buttonName} Button(s)", buttonType);
var result = ThemedMessageBox.Show($"This MessageBox has {buttonName} button(s).", $"{buttonName} Button(s)", buttonType);
ViewModel.DifferentButtonsResult = $"Result: {result}";
}

Expand All @@ -84,33 +85,33 @@ private void ShowImageFromComboBox_Click(object sender, RoutedEventArgs e)
_ => "None"
};

var result = MessageBox.Show($"This MessageBox displays the {imageName} icon.", $"{imageName} Icon", MessageBoxButton.OK, imageType);
var result = ThemedMessageBox.Show($"This MessageBox displays the {imageName} icon.", $"{imageName} Icon", MessageBoxButton.OK, imageType);
ViewModel.DifferentImagesResult = $"Result: {result}";
}

// 6. Common Messages (Information, Error, Warning)
private void ShowCommonInformation_Click(object sender, RoutedEventArgs e)
{
var result = MessageBox.Show("The operation completed successfully.", "Information", MessageBoxButton.OK, MessageBoxImage.Information);
var result = ThemedMessageBox.Show("The operation completed successfully.", "Information", MessageBoxButton.OK, MessageBoxImage.Information);
ViewModel.CommonMessagesResult = $"Type: Information | Result: {result}";
}

private void ShowCommonError_Click(object sender, RoutedEventArgs e)
{
var result = MessageBox.Show("An error occurred! The operation could not be completed.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
var result = ThemedMessageBox.Show("An error occurred! The operation could not be completed.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
ViewModel.CommonMessagesResult = $"Type: Error | Result: {result}";
}

private void ShowCommonWarning_Click(object sender, RoutedEventArgs e)
{
var result = MessageBox.Show("This action cannot be undone! Do you want to continue?", "Warning", MessageBoxButton.OKCancel, MessageBoxImage.Warning);
var result = ThemedMessageBox.Show("This action cannot be undone! Do you want to continue?", "Warning", MessageBoxButton.OKCancel, MessageBoxImage.Warning);
ViewModel.CommonMessagesResult = $"Type: Warning | Result: {result}";
}

// 7. Custom Default Button
private void ShowCustomDefaultButton_Click(object sender, RoutedEventArgs e)
{
var result = MessageBox.Show("Do you want to save changes? Press Enter to select the default 'No' button.", "Save Changes", MessageBoxButton.YesNoCancel, MessageBoxImage.Question, MessageBoxResult.No);
var result = ThemedMessageBox.Show("Do you want to save changes? Press Enter to select the default 'No' button.", "Save Changes", MessageBoxButton.YesNoCancel, MessageBoxImage.Question, MessageBoxResult.No);
ViewModel.CustomDefaultResult = $"User selected: {result}";
}
}
Expand Down
Loading