// 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); } } }