WPF自定义控件三:消息提示框
需求:实现全局消息提示框
一:创建全局Message
public class Message { private static readonly Style infoStyle = (Style)Application.Current.Resources["InfoMessage"]; private static readonly Style warningStyle = (Style)Application.Current.Resources["WarningMessage"]; private static readonly Style successStyle = (Style)Application.Current.Resources["SuccessMessage"]; private static readonly Style errorStyle = (Style)Application.Current.Resources["ErrorMessage"]; #region 全局 public static void Show(MessageType type, UIElement element, int millisecondTimeOut = 3000, bool isClearable = true) { if (millisecondTimeOut <= 0) { isClearable = true; } MessageWindow messageWindow = MessageWindow.GetInstance(); messageWindow.Dispatcher.VerifyAccess(); MessageCard messageCard; switch (type) { default: case MessageType.None: messageCard = new MessageCard { Content = element, IsClearable = isClearable }; break; case MessageType.Info: messageCard = new MessageCard { Content = element, IsClearable = isClearable, Style = infoStyle }; break; case MessageType.Warning: messageCard = new MessageCard { Content = element, IsClearable = isClearable, Style = warningStyle }; break; case MessageType.Success: messageCard = new MessageCard { Content = element, IsClearable = isClearable, Style = successStyle }; break; case MessageType.Error: messageCard = new MessageCard { Content = element, IsClearable = isClearable, Style = errorStyle }; break; } messageWindow.AddMessageCard(messageCard, millisecondTimeOut); messageWindow.Show(); } public static void Show(UIElement element, int millisecondTimeOut = 3000, bool isClearable = true) { Show(MessageType.None, element, millisecondTimeOut, isClearable); } public static void ShowInfo(UIElement element, int millisecondTimeOut = 3000, bool isClearable = true) { Show(MessageType.Info, element, millisecondTimeOut, isClearable); } public static void ShowSuccess(UIElement element, int millisecondTimeOut = 3000, bool isClearable = true) { Show(MessageType.Success, element, millisecondTimeOut, isClearable); } public static void ShowWarning(UIElement element, int millisecondTimeOut = 3000, bool isClearable = true) { Show(MessageType.Warning, element, millisecondTimeOut, isClearable); } public static void ShowError(UIElement element, int millisecondTimeOut = 3000, bool isClearable = true) { Show(MessageType.Error, element, millisecondTimeOut, isClearable); } public static void Show(MessageType type, string message, int millisecondTimeOut = 3000, bool isClearable = true) { Show(type, new TextBlock { Text = message }, millisecondTimeOut, isClearable); } public static void Show(string message, int millisecondTimeOut = 3000, bool isClearable = true) { Show(MessageType.None, new TextBlock { Text = message }, millisecondTimeOut, isClearable); } public static void ShowInfo(string message, int millisecondTimeOut = 3000, bool isClearable = true) { Show(MessageType.Info, new TextBlock { Text = message }, millisecondTimeOut, isClearable); } public static void ShowSuccess(string message, int millisecondTimeOut = 3000, bool isClearable = true) { Show(MessageType.Success, new TextBlock { Text = message }, millisecondTimeOut, isClearable); } public static void ShowWarning(string message, int millisecondTimeOut = 3000, bool isClearable = true) { Show(MessageType.Warning, new TextBlock { Text = message }, millisecondTimeOut, isClearable); } public static void ShowError(string message, int millisecondTimeOut = 3000, bool isClearable = true) { Show(MessageType.Error, new TextBlock { Text = message }, millisecondTimeOut, isClearable); } #endregion #region 指定容器 public static void Show(string containerIdentifier, MessageType type, UIElement element, int millisecondTimeOut = 3000, bool isClearable = true) { if (!MessageContainer.Containers.ContainsKey(containerIdentifier)) { return; } if (millisecondTimeOut <= 0) { isClearable = true; } Panel messagePanel = MessageContainer.Containers[containerIdentifier]; messagePanel.Dispatcher.VerifyAccess(); MessageCard messageCard; switch (type) { default: case MessageType.None: messageCard = new MessageCard { Content = element, IsClearable = isClearable }; break; case MessageType.Info: messageCard = new MessageCard { Content = element, IsClearable = isClearable, Style = infoStyle }; break; case MessageType.Warning: messageCard = new MessageCard { Content = element, IsClearable = isClearable, Style = warningStyle }; break; case MessageType.Success: messageCard = new MessageCard { Content = element, IsClearable = isClearable, Style = successStyle }; break; case MessageType.Error: messageCard = new MessageCard { Content = element, IsClearable = isClearable, Style = errorStyle }; break; } messagePanel.Children.Add(messageCard); // 进入动画 Storyboard enterStoryboard = new Storyboard(); DoubleAnimation opacityAnimation = new DoubleAnimation { From = 0, To = 1, Duration = new Duration(TimeSpan.FromMilliseconds(300)), EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn } }; Storyboard.SetTargetProperty(opacityAnimation, new PropertyPath(UIElement.OpacityProperty)); DoubleAnimation transformAnimation = new DoubleAnimation { From = -30, To = Application.Current.MainWindow.Height / 2-100, Duration = new Duration(TimeSpan.FromMilliseconds(300)), EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn } }; Storyboard.SetTargetProperty(transformAnimation, new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.Y)")); enterStoryboard.Children.Add(opacityAnimation); enterStoryboard.Children.Add(transformAnimation); if (millisecondTimeOut > 0) { // 进入动画完成 enterStoryboard.Completed += async (sender, e) => { await Task.Run(() => { Thread.Sleep(millisecondTimeOut); }); messagePanel.Children.Remove(messageCard); }; } messageCard.BeginStoryboard(enterStoryboard); // 退出动画 //Storyboard exitStoryboard = new Storyboard(); //DoubleAnimation exitOpacityAnimation = new DoubleAnimation //{ // From = 1, // To = Application.Current.MainWindow.Height / 2, // Duration = new Duration(TimeSpan.FromMilliseconds(300)), // EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn } //}; //Storyboard.SetTargetProperty(exitOpacityAnimation, new PropertyPath(UIElement.OpacityProperty)); //DoubleAnimation exitTransformAnimation = new DoubleAnimation //{ // From = 0, // To = -30, // Duration = new Duration(TimeSpan.FromMilliseconds(300)), // EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn } //}; //Storyboard.SetTargetProperty(exitTransformAnimation, new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.Y)")); //exitStoryboard.Children.Add(exitOpacityAnimation); //exitStoryboard.Children.Add(exitTransformAnimation); //if (millisecondTimeOut > 0) //{ // // 进入动画完成 // enterStoryboard.Completed += async (sender, e) => // { // await Task.Run(() => // { // Thread.Sleep(millisecondTimeOut); // }); // messageCard.BeginStoryboard(exitStoryboard); // }; //} // 退出动画完成 //exitStoryboard.Completed += (sender, e) => //{ // messagePanel.Children.Remove(messageCard); //}; //messageCard.BeginStoryboard(enterStoryboard); } public static void Show(string containerIdentifier, UIElement element, int millisecondTimeOut = 3000, bool isClearable = true) { Show(containerIdentifier, MessageType.None, element, millisecondTimeOut, isClearable); } public static void ShowInfo(string containerIdentifier, UIElement element, int millisecondTimeOut = 3000, bool isClearable = true) { Show(containerIdentifier, MessageType.Info, element, millisecondTimeOut, isClearable); } public static void ShowSuccess(string containerIdentifier, UIElement element, int millisecondTimeOut = 3000, bool isClearable = true) { Show(containerIdentifier, MessageType.Success, element, millisecondTimeOut, isClearable); } public static void ShowWarning(string containerIdentifier, UIElement element, int millisecondTimeOut = 3000, bool isClearable = true) { Show(containerIdentifier, MessageType.Warning, element, millisecondTimeOut, isClearable); } public static void ShowError(string containerIdentifier, UIElement element, int millisecondTimeOut = 3000, bool isClearable = true) { Show(containerIdentifier, MessageType.Error, element, millisecondTimeOut, isClearable); } public static void Show(string containerIdentifier, MessageType type, string message, int millisecondTimeOut = 3000, bool isClearable = true) { Show(containerIdentifier, type, new TextBlock { Text = message }, millisecondTimeOut, isClearable); } public static void Show(string containerIdentifier, string message, int millisecondTimeOut = 3000, bool isClearable = true) { Show(containerIdentifier, MessageType.None, new TextBlock { Text = message }, millisecondTimeOut, isClearable); } public static void ShowInfo(string containerIdentifier, string message, int millisecondTimeOut = 3000, bool isClearable = true) { Show(containerIdentifier, MessageType.Info, new TextBlock { Text = message, Foreground = new SolidColorBrush(Colors.White) }, millisecondTimeOut, isClearable); } public static void ShowSuccess(string containerIdentifier, string message, int millisecondTimeOut = 3000, bool isClearable = true) { Show(containerIdentifier, MessageType.Success, new TextBlock { Text = message, Foreground = new SolidColorBrush(Colors.White) }, millisecondTimeOut, isClearable); } public static void ShowWarning(string containerIdentifier, string message, int millisecondTimeOut = 3000, bool isClearable = true) { Show(containerIdentifier, MessageType.Warning, new TextBlock { Text = message, Foreground = new SolidColorBrush(Colors.White) }, millisecondTimeOut, isClearable); } public static void ShowError(string containerIdentifier, string message, int millisecondTimeOut = 3000, bool isClearable = true) { Show(containerIdentifier, MessageType.Error, new TextBlock { Text = message, Foreground = new SolidColorBrush(Colors.White) }, millisecondTimeOut, isClearable); } #endregion } public enum MessageType { None = 0, Info, Success, Warning, Error }
二:创建消息提示控件
1:创建名为MessageCard资源字典与MessageCard类
<LinearGradientBrush x:Key="GridBackGrounds1" EndPoint="0,1" StartPoint="0,0"> <GradientStop Color="#FF061118" Offset="0.191"/> <GradientStop Color="#FF173A52" Offset="1"/> </LinearGradientBrush> <Style x:Key="MessageCard" TargetType="{x:Type local:MessageCard}"> <Setter Property="HorizontalContentAlignment" Value="Left"/> <Setter Property="VerticalContentAlignment" Value="Center"/> <Setter Property="HorizontalAlignment" Value="Center"/> <Setter Property="VerticalAlignment" Value="Center"/> <Setter Property="SnapsToDevicePixels" Value="True"/> <Setter Property="BorderBrush" Value="{StaticResource BorderGray}"/> <Setter Property="BorderThickness" Value="0"/> <Setter Property="CornerRadius" Value="2.5"/> <Setter Property="Background" Value="{DynamicResource GridBackGrounds1}"/> <Setter Property="Foreground" Value="White"/> <Setter Property="ThemeColorBrush" Value="{DynamicResource DefaultForeground}"/> <Setter Property="Margin" Value="2.5"/> <Setter Property="Padding" Value="5"/> <Setter Property="MinHeight" Value="60"/> <Setter Property="MinWidth" Value="200"/> <Setter Property="IsShwoIcon" Value="False"/> <Setter Property="FontSize" Value="18"/> <Setter Property="RenderTransform"> <Setter.Value> <TranslateTransform /> </Setter.Value> </Setter> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:MessageCard}"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding ThemeColorBrush}" BorderThickness="{TemplateBinding BorderThickness}" Margin="{TemplateBinding Margin}" CornerRadius="{TemplateBinding CornerRadius}"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding ThemeColorBrush}" BorderThickness="{TemplateBinding BorderThickness}" Effect="{StaticResource AllDirectionEffect}" Padding="{TemplateBinding Padding}" CornerRadius="{TemplateBinding CornerRadius}" Grid.ColumnSpan="3"/> <ContentPresenter x:Name="contentPresenter" Focusable="False" Grid.Column="1" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" Margin="8 0"/> <Button x:Name="clearButton" Foreground="{TemplateBinding ThemeColorBrush}" Grid.Column="2" Style="{x:Null}" Height="20" BorderThickness="0" BorderBrush="Transparent" Background="Transparent"
local:ButtonHelper.ButtonStyle="Link" Visibility="{TemplateBinding IsClearable,Converter={StaticResource boolToVisibility}}"
Content="X"
> </Button> </Grid> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style x:Key="WarningMessage" TargetType="{x:Type local:MessageCard}" BasedOn="{StaticResource MessageCard}"> <Setter Property="ThemeColorBrush" Value="{StaticResource WarningBrush}"/> <Setter Property="IconType" Value="ErrorWarningFill"/>//IOC提示类型 <Setter Property="IsShwoIcon" Value="True"/> </Style> <Style x:Key="SuccessMessage" TargetType="{x:Type local:MessageCard}" BasedOn="{StaticResource MessageCard}"> <Setter Property="ThemeColorBrush" Value="{StaticResource SuccessBrush}"/> <Setter Property="IconType" Value="CheckboxCircleFill"/> <Setter Property="IsShwoIcon" Value="True"/> </Style> <Style x:Key="ErrorMessage" TargetType="{x:Type local:MessageCard}" BasedOn="{StaticResource MessageCard}"> <Setter Property="ThemeColorBrush" Value="{StaticResource ErrorBrush}"/> <Setter Property="IconType" Value="CloseCircleFill"/> <Setter Property="IsShwoIcon" Value="True"/> </Style> <Style x:Key="InfoMessage" TargetType="{x:Type local:MessageCard}" BasedOn="{StaticResource MessageCard}"> <Setter Property="ThemeColorBrush" Value="{StaticResource InfoBrush}"/> <Setter Property="IconType" Value="InformationFill"/> <Setter Property="IsShwoIcon" Value="True"/> </Style>
using System; using System.Collections.Generic; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Animation; using Zt.UI.Silver.Utils; namespace Zt.UI.Silver { public class MessageCard : ContentControl { public static readonly RoutedEvent CloseEvent; static MessageCard() { CloseEvent = EventManager.RegisterRoutedEvent("Close", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MessageCard)); DefaultStyleKeyProperty.OverrideMetadata(typeof(MessageCard), new FrameworkPropertyMetadata(typeof(MessageCard))); } #region 事件 // 关闭消息事件 public event RoutedEventHandler Close { add { base.AddHandler(MessageCard.CloseEvent, value); } remove { base.RemoveHandler(MessageCard.CloseEvent, value); } } #endregion #region 依赖属性 public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(MessageCard), new PropertyMetadata(default(CornerRadius))); public CornerRadius CornerRadius { get { return (CornerRadius)GetValue(CornerRadiusProperty); } set { SetValue(CornerRadiusProperty, value); } } public static readonly DependencyProperty ThemeColorBrushProperty = DependencyProperty.Register("ThemeColorBrush", typeof(SolidColorBrush), typeof(MessageCard), new PropertyMetadata(default(SolidColorBrush))); public SolidColorBrush ThemeColorBrush { get { return (SolidColorBrush)GetValue(ThemeColorBrushProperty); } set { SetValue(ThemeColorBrushProperty, value); } } public static readonly DependencyProperty IconTypeProperty = DependencyProperty.Register("IconType", typeof(IconType), typeof(MessageCard), new PropertyMetadata(default(IconType))); public IconType IconType { get { return (IconType)GetValue(IconTypeProperty); } set { SetValue(IconTypeProperty, value); } } public static readonly DependencyProperty IsShwoIconProperty = DependencyProperty.Register("IsShwoIcon", typeof(bool), typeof(MessageCard), new PropertyMetadata(default(bool))); public bool IsShwoIcon { get { return (bool)GetValue(IsShwoIconProperty); } set { SetValue(IsShwoIconProperty, value); } } public static readonly DependencyProperty IsClearableProperty = DependencyProperty.Register("IsClearable", typeof(bool), typeof(MessageCard), new PropertyMetadata(default(bool), OnIsClearbleChanged)); public bool IsClearable { get { return (bool)GetValue(IsClearableProperty); } set { SetValue(IsClearableProperty, value); } } private static void OnIsClearbleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is MessageCard messageCard) { RoutedEventHandler handle = (sender, args) => { if (VisualTreeHelper.GetParent(messageCard) is Panel panel) { // 退出动画 Storyboard exitStoryboard = new Storyboard(); DoubleAnimation exitOpacityAnimation = new DoubleAnimation { From = 1, To = 0, Duration = new Duration(TimeSpan.FromMilliseconds(300)), EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn } }; Storyboard.SetTargetProperty(exitOpacityAnimation, new PropertyPath(FrameworkElement.OpacityProperty)); DoubleAnimation exitTransformAnimation = new DoubleAnimation { From = 0, To = -30, Duration = new Duration(TimeSpan.FromMilliseconds(300)), EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn } }; Storyboard.SetTargetProperty(exitTransformAnimation, new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.Y)")); exitStoryboard.Children.Add(exitOpacityAnimation); exitStoryboard.Children.Add(exitTransformAnimation); // 动画完成 exitStoryboard.Completed += (a, b) => { panel.Children.Remove(messageCard); RoutedEventArgs eventArgs = new RoutedEventArgs(MessageCard.CloseEvent, messageCard); messageCard.RaiseEvent(eventArgs); }; messageCard.BeginStoryboard(exitStoryboard); // 执行动画 } }; messageCard.Foreground =new SolidColorBrush( Colors.Black); messageCard.Loaded += (sender, arg) => { if (messageCard.Template.FindName("clearButton", messageCard) is Button clearButton) { if (messageCard.IsClearable) { clearButton.Click += handle; } else { clearButton.Click -= handle; } } }; messageCard.Unloaded += (sender, arg) => { if (messageCard.Template.FindName("clearButton", messageCard) is Button clearButton) { if (messageCard.IsClearable) { clearButton.Click -= handle; } } }; } } #endregion } }
2:创建名为MessageWindow的窗体
<Window x:Class="Zt.UI.Silver.MessageWindow" 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" xmlns:local="clr-namespace:Zt.UI.Silver" mc:Ignorable="d" Background="Transparent" WindowStyle="None" AllowsTransparency="True" WindowState="Maximized" ShowInTaskbar="False" WindowStartupLocation="CenterOwner" VerticalAlignment="Top" Topmost="True"> <StackPanel x:Name="messageStackPanel" Margin="10"> </StackPanel> </Window>
后台代码:
public partial class MessageWindow : Window { private static MessageWindow messageWindow = null; private MessageWindow() { InitializeComponent(); } public static MessageWindow GetInstance() { if (messageWindow == null) { messageWindow = new MessageWindow(); } else if (!messageWindow.IsLoaded) { messageWindow = new MessageWindow(); } return messageWindow; } public void AddMessageCard(MessageCard messageCard, int millisecondTimeOut) { messageCard.Close += MessageCard_Close; messageStackPanel.Children.Add(messageCard); // 进入动画 Storyboard enterStoryboard = new Storyboard(); DoubleAnimation opacityAnimation = new DoubleAnimation { From = 0, To = 1, Duration = new Duration(TimeSpan.FromMilliseconds(300)), EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn } }; Storyboard.SetTargetProperty(opacityAnimation, new PropertyPath(OpacityProperty)); DoubleAnimation transformAnimation = new DoubleAnimation { From = -30, To = 0, Duration = new Duration(TimeSpan.FromMilliseconds(300)), EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn } }; Storyboard.SetTargetProperty(transformAnimation, new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.Y)")); enterStoryboard.Children.Add(opacityAnimation); enterStoryboard.Children.Add(transformAnimation); // 退出动画 Storyboard exitStoryboard = new Storyboard(); DoubleAnimation exitOpacityAnimation = new DoubleAnimation { From = 1, To = 0, Duration = new Duration(TimeSpan.FromMilliseconds(300)), EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn } }; Storyboard.SetTargetProperty(exitOpacityAnimation, new PropertyPath(OpacityProperty)); DoubleAnimation exitTransformAnimation = new DoubleAnimation { From = 0, To = -30, Duration = new Duration(TimeSpan.FromMilliseconds(300)), EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn } }; Storyboard.SetTargetProperty(exitTransformAnimation, new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.Y)")); exitStoryboard.Children.Add(exitOpacityAnimation); exitStoryboard.Children.Add(exitTransformAnimation); // 进入动画完成 if (millisecondTimeOut > 0) { enterStoryboard.Completed += async (sender, e) => { await Task.Run(() => { Thread.Sleep(millisecondTimeOut); }); Dispatcher.Invoke(() => { messageCard.BeginStoryboard(exitStoryboard); }); }; } // 退出动画完成 exitStoryboard.Completed += (sender, e) => { Dispatcher.Invoke(() => { messageStackPanel.Children.Remove(messageCard); if (messageStackPanel.Children.Count == 0) { this.Close(); } }); }; messageCard.BeginStoryboard(enterStoryboard); } /// <summary> /// 消息卡片关闭按钮事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void MessageCard_Close(object sender, RoutedEventArgs e) { if (messageStackPanel.Children.Count == 0) { this.Close(); } } }
三:添加转换器BoolToVisibilityConverter
public class BoolToVisibilityConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if ((bool)value) { return Visibility.Visible; } else { return Visibility.Collapsed; } } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return DependencyProperty.UnsetValue; } }
四:用法
APP引用
<ResourceDictionary Source="pack://application:,,,/Zt.UI.Silver;component/Themes/MessageCard.xaml" /> <Style TargetType="{x:Type local:MessageCard}" BasedOn="{StaticResource MessageCard}"/>
Main 下使用
<zt:MessageContainer Identifier="MessageContainer" Grid.RowSpan="10"/>
CS:后台代码
局部提示 Message.ShowError("MessageContainer","123",1000);
全局提示 Message.ShowError("123",1000);
五:演示