using System; using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Threading; using HandyControl.Data; using HandyControl.Interactivity; using HandyControl.Properties.Langs; using HandyControl.Tools; using HandyControl.Tools.Extension; namespace HandyControl.Controls; /// /// 消息提醒 /// [TemplatePart(Name = ElementPanelMore, Type = typeof(Panel))] [TemplatePart(Name = ElementGridMain, Type = typeof(Grid))] [TemplatePart(Name = ElementButtonClose, Type = typeof(Button))] public class Growl : Control { private const string ElementPanelMore = "PART_PanelMore"; private const string ElementGridMain = "PART_GridMain"; private const string ElementButtonClose = "PART_ButtonClose"; private const int MinWaitTime = 2; public static readonly DependencyProperty GrowlParentProperty = DependencyProperty.RegisterAttached( "GrowlParent", typeof(bool), typeof(Growl), new PropertyMetadata(ValueBoxes.FalseBox, (o, args) => { if ((bool) args.NewValue && o is Panel panel) { SetGrowlPanel(panel); } })); public static readonly DependencyProperty ShowModeProperty = DependencyProperty.RegisterAttached( "ShowMode", typeof(GrowlShowMode), typeof(Growl), new FrameworkPropertyMetadata(default(GrowlShowMode), FrameworkPropertyMetadataOptions.Inherits)); public static readonly DependencyProperty ShowDateTimeProperty = DependencyProperty.Register( nameof(ShowDateTime), typeof(bool), typeof(Growl), new PropertyMetadata(ValueBoxes.TrueBox)); public static readonly DependencyProperty MessageProperty = DependencyProperty.Register( nameof(Message), typeof(string), typeof(Growl), new PropertyMetadata(default(string))); public static readonly DependencyProperty TimeProperty = DependencyProperty.Register( nameof(Time), typeof(DateTime), typeof(Growl), new PropertyMetadata(default(DateTime))); public static readonly DependencyProperty IconProperty = DependencyProperty.Register( nameof(Icon), typeof(Geometry), typeof(Growl), new PropertyMetadata(default(Geometry))); public static readonly DependencyProperty IconBrushProperty = DependencyProperty.Register( nameof(IconBrush), typeof(Brush), typeof(Growl), new PropertyMetadata(default(Brush))); public static readonly DependencyProperty TypeProperty = DependencyProperty.Register( nameof(Type), typeof(InfoType), typeof(Growl), new PropertyMetadata(default(InfoType))); public static readonly DependencyProperty TokenProperty = DependencyProperty.RegisterAttached( "Token", typeof(string), typeof(Growl), new PropertyMetadata(default(string), OnTokenChanged)); internal static readonly DependencyProperty CancelStrProperty = DependencyProperty.Register( nameof(CancelStr), typeof(string), typeof(Growl), new PropertyMetadata(default(string))); internal static readonly DependencyProperty ConfirmStrProperty = DependencyProperty.Register( nameof(ConfirmStr), typeof(string), typeof(Growl), new PropertyMetadata(default(string))); private static readonly DependencyProperty IsCreatedAutomaticallyProperty = DependencyProperty.RegisterAttached( "IsCreatedAutomatically", typeof(bool), typeof(Growl), new PropertyMetadata(ValueBoxes.FalseBox)); private static GrowlWindow GrowlWindow; private static readonly Dictionary PanelDic = new(); private Panel _panelMore; private Grid _gridMain; private Button _buttonClose; private bool _showCloseButton; private bool _staysOpen; private int _waitTime = 6; /// /// 计数 /// private int _tickCount; /// /// 关闭计时器 /// private DispatcherTimer _timerClose; /// /// 消息容器 /// public static Panel GrowlPanel { get; set; } public InfoType Type { get => (InfoType) GetValue(TypeProperty); set => SetValue(TypeProperty, value); } public bool ShowDateTime { get => (bool) GetValue(ShowDateTimeProperty); set => SetValue(ShowDateTimeProperty, ValueBoxes.BooleanBox(value)); } public string Message { get => (string) GetValue(MessageProperty); set => SetValue(MessageProperty, value); } public DateTime Time { get => (DateTime) GetValue(TimeProperty); set => SetValue(TimeProperty, value); } public Geometry Icon { get => (Geometry) GetValue(IconProperty); set => SetValue(IconProperty, value); } public Brush IconBrush { get => (Brush) GetValue(IconBrushProperty); set => SetValue(IconBrushProperty, value); } internal string CancelStr { get => (string) GetValue(CancelStrProperty); set => SetValue(CancelStrProperty, value); } internal string ConfirmStr { get => (string) GetValue(ConfirmStrProperty); set => SetValue(ConfirmStrProperty, value); } private Func ActionBeforeClose { get; set; } public Growl() { CommandBindings.Add(new CommandBinding(ControlCommands.Close, ButtonClose_OnClick)); CommandBindings.Add(new CommandBinding(ControlCommands.Cancel, ButtonCancel_OnClick)); CommandBindings.Add(new CommandBinding(ControlCommands.Confirm, ButtonOk_OnClick)); } public static void Register(string token, Panel panel) { if (string.IsNullOrEmpty(token) || panel == null) return; PanelDic[token] = panel; InitGrowlPanel(panel); } public static void Unregister(string token, Panel panel) { if (string.IsNullOrEmpty(token) || panel == null) return; if (PanelDic.ContainsKey(token)) { if (ReferenceEquals(PanelDic[token], panel)) { PanelDic.Remove(token); panel.ContextMenu = null; panel.SetCurrentValue(PanelElement.FluidMoveBehaviorProperty, DependencyProperty.UnsetValue); } } } public static void Unregister(Panel panel) { if (panel == null) return; var first = PanelDic.FirstOrDefault(item => ReferenceEquals(panel, item.Value)); if (!string.IsNullOrEmpty(first.Key)) { PanelDic.Remove(first.Key); panel.ContextMenu = null; panel.SetCurrentValue(PanelElement.FluidMoveBehaviorProperty, DependencyProperty.UnsetValue); } } public static void Unregister(string token) { if (string.IsNullOrEmpty(token)) return; if (PanelDic.ContainsKey(token)) { var panel = PanelDic[token]; PanelDic.Remove(token); panel.ContextMenu = null; panel.SetCurrentValue(PanelElement.FluidMoveBehaviorProperty, DependencyProperty.UnsetValue); } } protected override void OnMouseEnter(MouseEventArgs e) { base.OnMouseEnter(e); _buttonClose.Show(_showCloseButton); } protected override void OnMouseLeave(MouseEventArgs e) { base.OnMouseLeave(e); _buttonClose.Collapse(); } public override void OnApplyTemplate() { base.OnApplyTemplate(); _panelMore = GetTemplateChild(ElementPanelMore) as Panel; _gridMain = GetTemplateChild(ElementGridMain) as Grid; _buttonClose = GetTemplateChild(ElementButtonClose) as Button; CheckNull(); Update(); } private void CheckNull() { if (_panelMore == null || _gridMain == null || _buttonClose == null) throw new Exception(); } private static void OnTokenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is Panel panel) { if (e.NewValue == null) { Unregister(panel); } else { Register(e.NewValue.ToString(), panel); } } } public static void SetToken(DependencyObject element, string value) => element.SetValue(TokenProperty, value); public static string GetToken(DependencyObject element) => (string) element.GetValue(TokenProperty); public static void SetShowMode(DependencyObject element, GrowlShowMode value) => element.SetValue(ShowModeProperty, value); public static GrowlShowMode GetShowMode(DependencyObject element) => (GrowlShowMode) element.GetValue(ShowModeProperty); public static void SetGrowlParent(DependencyObject element, bool value) => element.SetValue(GrowlParentProperty, ValueBoxes.BooleanBox(value)); public static bool GetGrowlParent(DependencyObject element) => (bool) element.GetValue(GrowlParentProperty); private static void SetIsCreatedAutomatically(DependencyObject element, bool value) => element.SetValue(IsCreatedAutomaticallyProperty, ValueBoxes.BooleanBox(value)); private static bool GetIsCreatedAutomatically(DependencyObject element) => (bool) element.GetValue(IsCreatedAutomaticallyProperty); /// /// 开始计时器 /// private void StartTimer() { _timerClose = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; _timerClose.Tick += delegate { if (IsMouseOver) { _tickCount = 0; return; } _tickCount++; if (_tickCount >= _waitTime) { Close(true); } }; _timerClose.Start(); } /// /// 消息容器 /// /// private static void SetGrowlPanel(Panel panel) { GrowlPanel = panel; InitGrowlPanel(panel); } private static void InitGrowlPanel(Panel panel) { if (panel == null) return; var menuItem = new MenuItem(); LangProvider.SetLang(menuItem, HeaderedItemsControl.HeaderProperty, LangKeys.Clear); menuItem.Click += (s, e) => { foreach (var item in panel.Children.OfType()) { item.Close(false); } }; panel.ContextMenu = new ContextMenu { Items = { menuItem } }; PanelElement.SetFluidMoveBehavior(panel, ResourceHelper.GetResourceInternal(ResourceToken.BehaviorXY400)); } private void Update() { if (DesignerHelper.IsInDesignMode) return; if (Type == InfoType.Ask) { _panelMore.IsEnabled = true; _panelMore.Show(); } var transform = new TranslateTransform { X = FlowDirection == FlowDirection.LeftToRight ? MaxWidth : -MaxWidth }; _gridMain.RenderTransform = transform; transform.BeginAnimation(TranslateTransform.XProperty, AnimationHelper.CreateAnimation(0)); if (!_staysOpen) StartTimer(); } private static void ShowInternal(Panel panel, UIElement growl) { if (panel is null) { return; } if (GetShowMode(panel) == GrowlShowMode.Prepend) { panel.Children.Insert(0, growl); } else { panel.Children.Add(growl); } } private static void ShowGlobal(GrowlInfo growlInfo) { Application.Current.Dispatcher?.Invoke( #if NET40 new Action( #endif () => { if (GrowlWindow == null) { GrowlWindow = new GrowlWindow(); GrowlWindow.Show(); InitGrowlPanel(GrowlWindow.GrowlPanel); GrowlWindow.Init(); } GrowlWindow.Show(true); var ctl = new Growl { Message = growlInfo.Message, Time = DateTime.Now, Icon = ResourceHelper.GetResource(growlInfo.IconKey) ?? growlInfo.Icon, IconBrush = ResourceHelper.GetResource(growlInfo.IconBrushKey) ?? growlInfo.IconBrush, _showCloseButton = growlInfo.ShowCloseButton, ActionBeforeClose = growlInfo.ActionBeforeClose, _staysOpen = growlInfo.StaysOpen, ShowDateTime = growlInfo.ShowDateTime, ConfirmStr = growlInfo.ConfirmStr, CancelStr = growlInfo.CancelStr, Type = growlInfo.Type, _waitTime = Math.Max(growlInfo.WaitTime, MinWaitTime), FlowDirection = growlInfo.FlowDirection }; ShowInternal(GrowlWindow.GrowlPanel, ctl); } #if NET40 ) #endif ); } /// /// 显示信息 /// /// private static void Show(GrowlInfo growlInfo) { (Application.Current.Dispatcher ?? growlInfo.Dispatcher)?.Invoke( #if NET40 new Action( #endif () => { var ctl = new Growl { Message = growlInfo.Message, Time = DateTime.Now, Icon = ResourceHelper.GetResource(growlInfo.IconKey) ?? growlInfo.Icon, IconBrush = ResourceHelper.GetResource(growlInfo.IconBrushKey) ?? growlInfo.IconBrush, _showCloseButton = growlInfo.ShowCloseButton, ActionBeforeClose = growlInfo.ActionBeforeClose, _staysOpen = growlInfo.StaysOpen, ShowDateTime = growlInfo.ShowDateTime, ConfirmStr = growlInfo.ConfirmStr, CancelStr = growlInfo.CancelStr, Type = growlInfo.Type, _waitTime = Math.Max(growlInfo.WaitTime, MinWaitTime) }; if (!string.IsNullOrEmpty(growlInfo.Token)) { if (PanelDic.TryGetValue(growlInfo.Token, out var panel)) { ShowInternal(panel, ctl); } } else { // GrowlPanel is null, we create it automatically GrowlPanel ??= CreateDefaultPanel(); ShowInternal(GrowlPanel, ctl); } } #if NET40 ) #endif ); } private static Panel CreateDefaultPanel() { FrameworkElement element = WindowHelper.GetActiveWindow(); var decorator = VisualHelper.GetChild(element); if (decorator != null) { var layer = decorator.AdornerLayer; if (layer != null) { var panel = new StackPanel { VerticalAlignment = VerticalAlignment.Top }; InitGrowlPanel(panel); SetIsCreatedAutomatically(panel, true); var scrollViewer = new ScrollViewer { HorizontalAlignment = HorizontalAlignment.Right, VerticalScrollBarVisibility = ScrollBarVisibility.Hidden, IsInertiaEnabled = true, IsPenetrating = true, Content = panel }; var container = new AdornerContainer(layer) { Child = scrollViewer }; layer.Add(container); return panel; } } return null; } private static void RemoveDefaultPanel(Panel panel) { FrameworkElement element = WindowHelper.GetActiveWindow(); var decorator = VisualHelper.GetChild(element); if (decorator != null) { var layer = decorator.AdornerLayer; var adorner = VisualHelper.GetParent(panel); if (adorner != null) { layer?.Remove(adorner); } } } private static void InitGrowlInfo(ref GrowlInfo growlInfo, InfoType infoType) { if (growlInfo == null) throw new ArgumentNullException(nameof(growlInfo)); growlInfo.Type = infoType; switch (infoType) { case InfoType.Success: if (!growlInfo.IsCustom) { growlInfo.IconKey = ResourceToken.SuccessGeometry; growlInfo.IconBrushKey = ResourceToken.SuccessBrush; } else { growlInfo.IconKey ??= ResourceToken.SuccessGeometry; growlInfo.IconBrushKey ??= ResourceToken.SuccessBrush; } break; case InfoType.Info: if (!growlInfo.IsCustom) { growlInfo.IconKey = ResourceToken.InfoGeometry; growlInfo.IconBrushKey = ResourceToken.InfoBrush; } else { growlInfo.IconKey ??= ResourceToken.InfoGeometry; growlInfo.IconBrushKey ??= ResourceToken.InfoBrush; } break; case InfoType.Warning: if (!growlInfo.IsCustom) { growlInfo.IconKey = ResourceToken.WarningGeometry; growlInfo.IconBrushKey = ResourceToken.WarningBrush; } else { growlInfo.IconKey ??= ResourceToken.WarningGeometry; growlInfo.IconBrushKey ??= ResourceToken.WarningBrush; } break; case InfoType.Error: if (!growlInfo.IsCustom) { growlInfo.IconKey = ResourceToken.ErrorGeometry; growlInfo.IconBrushKey = ResourceToken.DangerBrush; growlInfo.StaysOpen = true; } else { growlInfo.IconKey ??= ResourceToken.ErrorGeometry; growlInfo.IconBrushKey ??= ResourceToken.DangerBrush; } break; case InfoType.Fatal: if (!growlInfo.IsCustom) { growlInfo.IconKey = ResourceToken.FatalGeometry; growlInfo.IconBrushKey = ResourceToken.PrimaryTextBrush; growlInfo.StaysOpen = true; growlInfo.ShowCloseButton = false; } else { growlInfo.IconKey ??= ResourceToken.FatalGeometry; growlInfo.IconBrushKey ??= ResourceToken.PrimaryTextBrush; } break; case InfoType.Ask: growlInfo.StaysOpen = true; growlInfo.ShowCloseButton = false; if (!growlInfo.IsCustom) { growlInfo.IconKey = ResourceToken.AskGeometry; growlInfo.IconBrushKey = ResourceToken.AccentBrush; } else { growlInfo.IconKey ??= ResourceToken.AskGeometry; growlInfo.IconBrushKey ??= ResourceToken.AccentBrush; } break; default: throw new ArgumentOutOfRangeException(nameof(infoType), infoType, null); } } /// /// 成功 /// /// /// public static void Success(string message, string token = "") => Success(new GrowlInfo { Message = message, Token = token }); /// /// 成功 /// /// public static void Success(GrowlInfo growlInfo) { InitGrowlInfo(ref growlInfo, InfoType.Success); Show(growlInfo); } /// /// 成功 /// /// public static void SuccessGlobal(string message) => SuccessGlobal(new GrowlInfo { Message = message }); /// /// 成功 /// /// public static void SuccessGlobal(GrowlInfo growlInfo) { InitGrowlInfo(ref growlInfo, InfoType.Success); ShowGlobal(growlInfo); } /// /// 消息 /// /// /// public static void Info(string message, string token = "") => Info(new GrowlInfo { Message = message, Token = token }); /// /// 消息 /// /// public static void Info(GrowlInfo growlInfo) { InitGrowlInfo(ref growlInfo, InfoType.Info); Show(growlInfo); } /// /// 消息 /// /// public static void InfoGlobal(string message) => InfoGlobal(new GrowlInfo { Message = message }); /// /// 消息 /// /// public static void InfoGlobal(GrowlInfo growlInfo) { InitGrowlInfo(ref growlInfo, InfoType.Info); ShowGlobal(growlInfo); } /// /// 警告 /// /// /// public static void Warning(string message, string token = "") => Warning(new GrowlInfo { Message = message, Token = token }); /// /// 警告 /// /// public static void Warning(GrowlInfo growlInfo) { InitGrowlInfo(ref growlInfo, InfoType.Warning); Show(growlInfo); } /// /// 警告 /// /// public static void WarningGlobal(string message) => WarningGlobal(new GrowlInfo { Message = message }); /// /// 警告 /// /// public static void WarningGlobal(GrowlInfo growlInfo) { InitGrowlInfo(ref growlInfo, InfoType.Warning); ShowGlobal(growlInfo); } /// /// 错误 /// /// /// public static void Error(string message, string token = "") => Error(new GrowlInfo { Message = message, Token = token }); /// /// 错误 /// /// public static void Error(GrowlInfo growlInfo) { InitGrowlInfo(ref growlInfo, InfoType.Error); Show(growlInfo); } /// /// 错误 /// /// public static void ErrorGlobal(string message) => ErrorGlobal(new GrowlInfo { Message = message }); /// /// 错误 /// /// public static void ErrorGlobal(GrowlInfo growlInfo) { InitGrowlInfo(ref growlInfo, InfoType.Error); ShowGlobal(growlInfo); } /// /// 严重 /// /// /// public static void Fatal(string message, string token = "") => Fatal(new GrowlInfo { Message = message, Token = token }); /// /// 严重 /// /// public static void Fatal(GrowlInfo growlInfo) { InitGrowlInfo(ref growlInfo, InfoType.Fatal); Show(growlInfo); } /// /// 严重 /// /// public static void FatalGlobal(string message) => FatalGlobal(new GrowlInfo { Message = message }); /// /// 严重 /// /// public static void FatalGlobal(GrowlInfo growlInfo) { InitGrowlInfo(ref growlInfo, InfoType.Fatal); ShowGlobal(growlInfo); } /// /// 询问 /// /// /// /// public static void Ask(string message, Func actionBeforeClose, string token = "") => Ask(new GrowlInfo { Message = message, ActionBeforeClose = actionBeforeClose, Token = token }); /// /// 询问 /// /// public static void Ask(GrowlInfo growlInfo) { InitGrowlInfo(ref growlInfo, InfoType.Ask); Show(growlInfo); } /// /// 询问 /// /// /// public static void AskGlobal(string message, Func actionBeforeClose) => AskGlobal(new GrowlInfo { Message = message, ActionBeforeClose = actionBeforeClose }); /// /// 询问 /// /// public static void AskGlobal(GrowlInfo growlInfo) { InitGrowlInfo(ref growlInfo, InfoType.Ask); ShowGlobal(growlInfo); } private void ButtonClose_OnClick(object sender, RoutedEventArgs e) => Close(false); /// /// 关闭 /// private void Close(bool invokeParam) { if (ActionBeforeClose?.Invoke(invokeParam) == false) { return; } _timerClose?.Stop(); var transform = new TranslateTransform(); _gridMain.RenderTransform = transform; var animation = AnimationHelper.CreateAnimation(FlowDirection == FlowDirection.LeftToRight ? ActualWidth : -ActualWidth); animation.Completed += (s, e) => { if (Parent is Panel panel) { panel.Children.Remove(this); if (GrowlWindow != null) { if (GrowlWindow.GrowlPanel != null && GrowlWindow.GrowlPanel.Children.Count == 0) { GrowlWindow.Close(); GrowlWindow = null; } } else { if (GrowlPanel != null && GrowlPanel.Children.Count == 0 && GetIsCreatedAutomatically(GrowlPanel)) { // If the count of children is zero, we need to remove the panel, provided that the panel was created automatically RemoveDefaultPanel(GrowlPanel); GrowlPanel = null; } } } }; transform.BeginAnimation(TranslateTransform.XProperty, animation); } /// /// 清除 /// /// public static void Clear(string token = "") { if (!string.IsNullOrEmpty(token)) { if (PanelDic.TryGetValue(token, out var panel)) { Clear(panel); } } else { Clear(GrowlPanel); } } /// /// 清除 /// /// private static void Clear(Panel panel) => panel?.Children.Clear(); /// /// 清除 /// public static void ClearGlobal() { if (GrowlWindow == null) return; Clear(GrowlWindow.GrowlPanel); GrowlWindow.Close(); GrowlWindow = null; } private void ButtonCancel_OnClick(object sender, RoutedEventArgs e) => Close(false); private void ButtonOk_OnClick(object sender, RoutedEventArgs e) => Close(true); }