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