// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
// Copyright (C) Leszek Pomianowski and WPF UI Contributors.
// All Rights Reserved.
using System.Reflection;
using WPFluent.Input;
using WPFluent.Interop;
using Size = System.Windows.Size;
#if NET8_0_OR_GREATER
using mscoSystem.RuntimepilerServices;
#endif
// ReSharper disable once CheckNamespace
namespace WPFluent.Controls;
///
/// Customized window for notifications.
///
public class MessageWindow : System.Windows.Window
{
/// Identifies the dependency property.
public static readonly DependencyProperty ShowTitleProperty = DependencyProperty.Register(
nameof(ShowTitle),
typeof(bool),
typeof(MessageWindow),
new PropertyMetadata(true)
);
/// Identifies the dependency property.
public static readonly DependencyProperty PrimaryButtonTextProperty = DependencyProperty.Register(
nameof(PrimaryButtonText),
typeof(string),
typeof(MessageWindow),
new PropertyMetadata(string.Empty)
);
/// Identifies the dependency property.
public static readonly DependencyProperty SecondaryButtonTextProperty = DependencyProperty.Register(
nameof(SecondaryButtonText),
typeof(string),
typeof(MessageWindow),
new PropertyMetadata(string.Empty)
);
/// Identifies the dependency property.
public static readonly DependencyProperty CloseButtonTextProperty = DependencyProperty.Register(
nameof(CloseButtonText),
typeof(string),
typeof(MessageWindow),
new PropertyMetadata("Close")
);
/// Identifies the dependency property.
public static readonly DependencyProperty PrimaryButtonIconProperty = DependencyProperty.Register(
nameof(PrimaryButtonIcon),
typeof(IconElement),
typeof(MessageWindow),
new PropertyMetadata(null)
);
/// Identifies the dependency property.
public static readonly DependencyProperty SecondaryButtonIconProperty = DependencyProperty.Register(
nameof(SecondaryButtonIcon),
typeof(IconElement),
typeof(MessageWindow),
new PropertyMetadata(null)
);
/// Identifies the dependency property.
public static readonly DependencyProperty CloseButtonIconProperty = DependencyProperty.Register(
nameof(CloseButtonIcon),
typeof(IconElement),
typeof(MessageWindow),
new PropertyMetadata(null)
);
/// Identifies the dependency property.
public static readonly DependencyProperty PrimaryButtonAppearanceProperty = DependencyProperty.Register(
nameof(PrimaryButtonAppearance),
typeof(ControlAppearance),
typeof(MessageWindow),
new PropertyMetadata(ControlAppearance.Primary)
);
/// Identifies the dependency property.
public static readonly DependencyProperty SecondaryButtonAppearanceProperty = DependencyProperty.Register(
nameof(SecondaryButtonAppearance),
typeof(ControlAppearance),
typeof(MessageWindow),
new PropertyMetadata(ControlAppearance.Secondary)
);
/// Identifies the dependency property.
public static readonly DependencyProperty CloseButtonAppearanceProperty = DependencyProperty.Register(
nameof(CloseButtonAppearance),
typeof(ControlAppearance),
typeof(MessageWindow),
new PropertyMetadata(ControlAppearance.Secondary)
);
/// Identifies the dependency property.
public static readonly DependencyProperty IsPrimaryButtonEnabledProperty = DependencyProperty.Register(
nameof(IsPrimaryButtonEnabled),
typeof(bool),
typeof(MessageWindow),
new PropertyMetadata(true)
);
/// Identifies the dependency property.
public static readonly DependencyProperty IsSecondaryButtonEnabledProperty = DependencyProperty.Register(
nameof(IsSecondaryButtonEnabled),
typeof(bool),
typeof(MessageWindow),
new PropertyMetadata(true)
);
/// Identifies the dependency property.
public static readonly DependencyProperty TemplateButtonCommandProperty = DependencyProperty.Register(
nameof(TemplateButtonCommand),
typeof(IRelayCommand),
typeof(MessageWindow),
new PropertyMetadata(null)
);
///
/// Gets or sets a value indicating whether to show the in .
///
public bool ShowTitle
{
get => (bool)GetValue(ShowTitleProperty);
set => SetValue(ShowTitleProperty, value);
}
///
/// Gets or sets the text to display on the primary button.
///
public string PrimaryButtonText
{
get => (string)GetValue(PrimaryButtonTextProperty);
set => SetValue(PrimaryButtonTextProperty, value);
}
///
/// Gets or sets the text to be displayed on the secondary button.
///
public string SecondaryButtonText
{
get => (string)GetValue(SecondaryButtonTextProperty);
set => SetValue(SecondaryButtonTextProperty, value);
}
///
/// Gets or sets the text to display on the close button.
///
public string CloseButtonText
{
get => (string)GetValue(CloseButtonTextProperty);
set => SetValue(CloseButtonTextProperty, value);
}
///
/// Gets or sets the on the primary button
///
public IconElement? PrimaryButtonIcon
{
get => (IconElement?)GetValue(PrimaryButtonIconProperty);
set => SetValue(PrimaryButtonIconProperty, value);
}
///
/// Gets or sets the on the secondary button
///
public IconElement? SecondaryButtonIcon
{
get => (IconElement?)GetValue(SecondaryButtonIconProperty);
set => SetValue(SecondaryButtonIconProperty, value);
}
///
/// Gets or sets the on the close button
///
public IconElement? CloseButtonIcon
{
get => (IconElement?)GetValue(CloseButtonIconProperty);
set => SetValue(CloseButtonIconProperty, value);
}
///
/// Gets or sets the on the primary button
///
public ControlAppearance PrimaryButtonAppearance
{
get => (ControlAppearance)GetValue(PrimaryButtonAppearanceProperty);
set => SetValue(PrimaryButtonAppearanceProperty, value);
}
///
/// Gets or sets the on the secondary button
///
public ControlAppearance SecondaryButtonAppearance
{
get => (ControlAppearance)GetValue(SecondaryButtonAppearanceProperty);
set => SetValue(SecondaryButtonAppearanceProperty, value);
}
///
/// Gets or sets the on the close button
///
public ControlAppearance CloseButtonAppearance
{
get => (ControlAppearance)GetValue(CloseButtonAppearanceProperty);
set => SetValue(CloseButtonAppearanceProperty, value);
}
///
/// Gets or sets a value indicating whether the primary button is enabled.
///
public bool IsSecondaryButtonEnabled
{
get => (bool)GetValue(IsSecondaryButtonEnabledProperty);
set => SetValue(IsSecondaryButtonEnabledProperty, value);
}
///
/// Gets or sets a value indicating whether the secondary button is enabled.
///
public bool IsPrimaryButtonEnabled
{
get => (bool)GetValue(IsPrimaryButtonEnabledProperty);
set => SetValue(IsPrimaryButtonEnabledProperty, value);
}
///
/// Gets the command triggered after clicking the button on the Footer.
///
internal IRelayCommand TemplateButtonCommand => (IRelayCommand)GetValue(TemplateButtonCommandProperty);
#if !NET8_0_OR_GREATER
private static readonly PropertyInfo CanCenterOverWPFOwnerPropertyInfo = typeof(Window).GetProperty(
"CanCenterOverWPFOwner",
BindingFlags.NonPublic | BindingFlags.Instance
)!;
#endif
///
/// Initializes a new instance of the class.
///
public MessageWindow()
{
Topmost = true;
SetValue(TemplateButtonCommandProperty, new RelayCommand(OnButtonClick));
PreviewMouseDoubleClick += static (_, args) => args.Handled = true;
Loaded += static (sender, _) =>
{
var self = (MessageWindow)sender;
self.OnLoaded();
};
}
protected TaskCompletionSource? Tcs { get; set; }
[Obsolete($"Use {nameof(ShowDialogAsync)} instead")]
public new void Show()
{
throw new InvalidOperationException($"Use {nameof(ShowDialogAsync)} instead");
}
[Obsolete($"Use {nameof(ShowDialogAsync)} instead")]
public new bool? ShowDialog()
{
throw new InvalidOperationException($"Use {nameof(ShowDialogAsync)} instead");
}
[Obsolete($"Use {nameof(Close)} with MessageBoxResult instead")]
public new void Close()
{
throw new InvalidOperationException($"Use {nameof(Close)} with MessageBoxResult instead");
}
///
/// Displays a message box
///
///
/// Thrown if the operation is canceled.
public async Task ShowDialogAsync(
bool showAsDialog = true,
CancellationToken cancellationToken = default
)
{
Tcs = new TaskCompletionSource();
CancellationTokenRegistration tokenRegistration = cancellationToken.Register(
o => Tcs.TrySetCanceled((CancellationToken)o!),
cancellationToken
);
try
{
//RemoveTitleBarAndApplyMica();
if (showAsDialog)
{
base.ShowDialog();
}
else
{
base.Show();
}
return await Tcs.Task;
}
finally
{
#if NET6_0_OR_GREATER
await tokenRegistration.DisposeAsync();
#else
tokenRegistration.Dispose();
#endif
}
}
///
/// Occurs after Loading event
///
protected virtual void OnLoaded()
{
var rootElement = (UIElement)GetVisualChild(0)!;
ResizeToContentSize(rootElement);
switch (WindowStartupLocation)
{
case WindowStartupLocation.Manual:
case WindowStartupLocation.CenterScreen:
CenterWindowOnScreen();
break;
case WindowStartupLocation.CenterOwner:
if (
!CanCenterOverWPFOwner()
|| Owner.WindowState is WindowState.Maximized or WindowState.Minimized
)
{
CenterWindowOnScreen();
}
else
{
CenterWindowOnOwner();
}
break;
default:
throw new InvalidOperationException();
}
}
// CanCenterOverWPFOwner property see https://source.dot.net/#PresentationFramework/System/Windows/Window.cs,e679e433777b21b8
private bool CanCenterOverWPFOwner()
{
#if NET8_0_OR_GREATER
return CanCenterOverWPFOwnerAccessor(this);
#else
return (bool)CanCenterOverWPFOwnerPropertyInfo.GetValue(this)!;
#endif
}
#if NET8_0_OR_GREATER
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "get_CanCenterOverWPFOwner")]
private static extern bool CanCenterOverWPFOwnerAccessor(Window w);
#endif
///
/// Resizes the MessageWindow to fit the content's size, including margins.
///
/// The root element of the MessageWindow
protected virtual void ResizeToContentSize(UIElement rootElement)
{
Size desiredSize = rootElement.DesiredSize;
// left and right margin
const double margin = 12.0 * 2;
SetCurrentValue(WidthProperty, desiredSize.Width + margin);
SetCurrentValue(HeightProperty, desiredSize.Height);
ResizeWidth(rootElement);
ResizeHeight(rootElement);
}
protected override void OnClosing(CancelEventArgs e)
{
base.OnClosing(e);
if (e.Cancel)
{
return;
}
Tcs?.TrySetResult(MessageBoxResult.None);
}
protected virtual void CenterWindowOnScreen()
{
double screenWidth = SystemParameters.PrimaryScreenWidth;
double screenHeight = SystemParameters.PrimaryScreenHeight;
SetCurrentValue(LeftProperty, (screenWidth / 2) - (Width / 2));
SetCurrentValue(TopProperty, (screenHeight / 2) - (Height / 2));
}
private void CenterWindowOnOwner()
{
double left = Owner.Left + ((Owner.Width - Width) / 2);
double top = Owner.Top + ((Owner.Height - Height) / 2);
SetCurrentValue(LeftProperty, left);
SetCurrentValue(TopProperty, top);
}
///
/// Occurs after the is clicked
///
/// The MessageWindow button
protected virtual void OnButtonClick(MessageBoxButton button)
{
MessageBoxResult result = button switch
{
MessageBoxButton.Primary => MessageBoxResult.Primary,
MessageBoxButton.Secondary => MessageBoxResult.Secondary,
_ => MessageBoxResult.None,
};
Tcs?.TrySetResult(result);
base.Close();
}
//private void RemoveTitleBarAndApplyMica()
//{
// //UnsafeNativeMethods.RemoveWindowTitlebarContents(this);
// WindowBackdrop.ApplyBackdrop(this, WindowBackdropType.Mica);
//}
private void ResizeWidth(UIElement element)
{
if (Width <= MaxWidth)
{
return;
}
SetCurrentValue(WidthProperty, MaxWidth);
element.UpdateLayout();
SetCurrentValue(HeightProperty, element.DesiredSize.Height);
if (Height > MaxHeight)
{
SetCurrentValue(MaxHeightProperty, Height);
}
}
private void ResizeHeight(UIElement element)
{
if (Height <= MaxHeight)
{
return;
}
SetCurrentValue(HeightProperty, MaxHeight);
element.UpdateLayout();
SetCurrentValue(WidthProperty, element.DesiredSize.Width);
if (Width > MaxWidth)
{
SetCurrentValue(MaxWidthProperty, Width);
}
}
}