WPF消息提示
使用.net6.0
使用的NuGet包
MaterialDesignThemes:4.9.0
Prism.DryIoc:8.1.97
我们要做一个类似Element Plus
或者Ant Design
的Message
,作为消息提示这样的交互比较友好,老是用MessageBox也挺难受,先不管动画效果吧
分析
这种消息提示在应用程序中是个有点怪的东西,因为一个应用程序不一定只有一个窗口,而网页是确定只有一个窗口的,只需要在网页正中间上方显示即可,如果在WPF中要做一个可复用的消息提示其实还挺麻烦的
消息肯定会涉及参数传递,在Prism里肯定就是选择Region
区域导航或者Messenger
消息订阅
- 如果确定只有一个窗口,或者像网页一样,所有内容都在窗口内显示,那么这个消息提示很简单,消息就像网页一样,只要调用显示就可以了
- 如果要考虑多个窗口,麻烦的点在于要确定显示消息的窗口
Region
区域的话,只要通过RequestNavigate
就可以指定执行的区域
Messenger
消息订阅有这么一个重载,filter
可以作为条件判断是否执行
public virtual SubscriptionToken Subscribe(Action<TPayload> action, ThreadOption threadOption, bool keepSubscriberReferenceAlive, Predicate<TPayload> filter)
但是涉及到组件复用,有些东西就不能写死,Region和Messenger都有同一个问题,如何将要显示消息的Window的标识传递到UserControl中?如果UserControl套娃又怎么传递?再加上View和ViewModel是分开的,感觉这是个死局呀
如果用循环或者递归往上找Window,似乎不符合MVVM的设计了
如果用Application.Current.Windows.OfType<Window>().SingleOrDefault(x => x.IsActive);
获取当前激活窗口,那么切到后台了呢?
感觉都不靠谱,不过仔细一想,确实没在多窗口应用程序上见过这种提示,去Google和Github,好像都是在桌面右下角的弹出提示,这种还是算了吧
案例
还真是个死局,还是写个单窗口的吧,就当是记录吧,这年头也流行这种,也方便跨平台
先来个消息类型MessageType
,用来确定消息类型
public enum MessageType
{
Info = 0,
Warning = 1,
Success = 2,
Error = 4,
}
再来个消息命令MessageCommand
,用来确定事件
public enum MessageCommand
{
Close = 0,
Open = 1,
}
至于要不要一个Model,我这里直接用ViewModel了,MessageViewModel
这里就是用来绑定的数据,至于颜色这里我偷懒写死了,合理的写法应该是写资源字典,这样能根据主题切换
public class MessageViewModel : BindableBase
{
private string _message;
/// <summary>
/// 消息
/// </summary>
public string Message
{
get { return _message; }
set
{
_message = value;
this.RaisePropertyChanged(nameof(Message));
}
}
private MessageCommand _messageCommand;
/// <summary>
/// 消息命令
/// </summary>
public MessageCommand MessageCommand
{
get { return _messageCommand; }
set
{
_messageCommand = value;
this.RaisePropertyChanged(nameof(MessageCommand));
}
}
private MessageType _messageType;
/// <summary>
/// 消息类型
/// </summary>
public MessageType MessageType
{
get { return _messageType; }
set
{
_messageType = value;
this.RaisePropertyChanged(nameof(MessageType));
this.ChangeMessageStyle();
}
}
private int _duration;
/// <summary>
/// 持续时间
/// </summary>
public int Duration
{
get { return _duration; }
set
{
_duration = value;
this.RaisePropertyChanged(nameof(Duration));
}
}
private PackIconKind _icon;
/// <summary>
/// 图标
/// </summary>
public PackIconKind Icon
{
get { return _icon; }
set
{
_icon = value;
this.RaisePropertyChanged(nameof(Icon));
}
}
private string _backgroundColor;
/// <summary>
/// 背景颜色
/// </summary>
public string BackgroundColor
{
get { return _backgroundColor; }
set
{
_backgroundColor = value;
this.RaisePropertyChanged(nameof(BackgroundColor));
}
}
private string _foregroundColor;
/// <summary>
/// 字体颜色
/// </summary>
public string ForegroundColor
{
get { return _foregroundColor; }
set
{
_foregroundColor = value;
this.RaisePropertyChanged(nameof(ForegroundColor));
}
}
private Visibility _visibilityStatus;
/// <summary>
/// 显示状态
/// </summary>
public Visibility VisibilityStatus
{
get { return _visibilityStatus; }
set
{
_visibilityStatus = value;
this.RaisePropertyChanged(nameof(VisibilityStatus));
}
}
private Visibility _closeButtonVisibility;
/// <summary>
/// 关闭按钮显示状态
/// </summary>
public Visibility CloseButtonVisibility
{
get { return _closeButtonVisibility; }
set
{
_closeButtonVisibility = value;
this.RaisePropertyChanged(nameof(CloseButtonVisibility));
}
}
/// <summary>
/// 关闭消息命令
/// </summary>
public DelegateCommand CloseMessageCommand { get; set; }
public MessageViewModel()
{
this.Message = string.Empty;
this.MessageType = MessageType.Info;
this.Duration = 3000;
this.CloseButtonVisibility = Visibility.Visible;
this.VisibilityStatus = Visibility.Visible;
this.CloseMessageCommand = new DelegateCommand(this.CloseMessageCommandExecute);
}
/// <summary>
/// 关闭命令事件处理器
/// </summary>
private void CloseMessageCommandExecute()
{
MessageHelper.CloseMessage(this);
}
/// <summary>
/// 改变消息样式
/// </summary>
private void ChangeMessageStyle()
{
switch (this.MessageType)
{
case MessageType.Info:
this.Icon = PackIconKind.Information;
this.BackgroundColor = "#f4f4f5";
this.ForegroundColor = "#909399";
break;
case MessageType.Warning:
this.Icon = PackIconKind.AlertCircle;
this.BackgroundColor = "#fdf6ec";
this.ForegroundColor = "#e6a23c";
break;
case MessageType.Success:
this.Icon = PackIconKind.CheckboxMarkedCircle;
this.BackgroundColor = "#f0f9eb";
this.ForegroundColor = "#67c23a";
break;
case MessageType.Error:
this.Icon = PackIconKind.CloseCircle;
this.BackgroundColor = "#fef0f0";
this.ForegroundColor = "#f56c6c";
break;
default:
break;
}
}
}
给这个Model或者ViewModel准备消息事件MessageEvent
public class MessageEvent : PubSubEvent<MessageViewModel>
{
}
再来个工具类MessageHelper
操作消息事件
public class MessageHelper
{
private readonly static IEventAggregator _eventAggregator = ContainerLocator.Container.Resolve<IEventAggregator>();
/// <summary>
/// 初始化MessageViewModel
/// </summary>
/// <param name="message"></param>
/// <param name="duration"></param>
/// <param name="isCloseButtonVisibility"></param>
/// <returns></returns>
private static MessageViewModel InitMessageViewModel(string message, int duration = 3000, bool isCloseButtonVisibility = false)
{
var messageViewModel = new MessageViewModel();
messageViewModel.Message = message;
messageViewModel.Duration = duration;
messageViewModel.MessageType = MessageType.Info;
switch (isCloseButtonVisibility)
{
case false:
messageViewModel.CloseButtonVisibility = Visibility.Collapsed;
break;
case true:
messageViewModel.CloseButtonVisibility = Visibility.Visible;
break;
default:
break;
}
return messageViewModel;
}
/// <summary>
/// Info类型消息
/// </summary>
/// <param name="message"></param>
/// <param name="duration"></param>
/// <param name="isCloseButtonVisibility"></param>
public static void Info(string message, int duration = 3000, bool isCloseButtonVisibility = false)
{
var messageViewModel = MessageHelper.InitMessageViewModel(message, duration, isCloseButtonVisibility);
messageViewModel.MessageType = MessageType.Info;
MessageHelper.PublishMessage(messageViewModel);
}
/// <summary>
/// Warning类型消息
/// </summary>
/// <param name="message"></param>
/// <param name="duration"></param>
/// <param name="isCloseButtonVisibility"></param>
public static void Warning(string message, int duration = 3000, bool isCloseButtonVisibility = false)
{
var messageViewModel = MessageHelper.InitMessageViewModel(message, duration, isCloseButtonVisibility);
messageViewModel.MessageType = MessageType.Warning;
MessageHelper.PublishMessage(messageViewModel);
}
/// <summary>
/// Success类型消息
/// </summary>
/// <param name="message"></param>
/// <param name="duration"></param>
/// <param name="isCloseButtonVisibility"></param>
public static void Success(string message, int duration = 3000, bool isCloseButtonVisibility = false)
{
var messageViewModel = MessageHelper.InitMessageViewModel(message, duration, isCloseButtonVisibility);
messageViewModel.MessageType = MessageType.Success;
MessageHelper.PublishMessage(messageViewModel);
}
/// <summary>
/// 错误消息
/// </summary>
/// <param name="message"></param>
/// <param name="duration"></param>
/// <param name="isCloseButtonVisibility"></param>
public static void Error(string message, int duration = 3000, bool isCloseButtonVisibility = false)
{
var messageViewModel = MessageHelper.InitMessageViewModel(message, duration, isCloseButtonVisibility);
messageViewModel.MessageType = MessageType.Error;
MessageHelper.PublishMessage(messageViewModel);
}
/// <summary>
/// 发布消息
/// </summary>
/// <param name="messageViewModel"></param>
private static void PublishMessage(MessageViewModel messageViewModel)
{
messageViewModel.MessageCommand = MessageCommand.Open;
MessageHelper._eventAggregator.GetEvent<MessageEvent>().Publish(messageViewModel);
}
/// <summary>
/// 关闭消息
/// </summary>
/// <param name="messageViewModel"></param>
public static void CloseMessage(MessageViewModel messageViewModel)
{
messageViewModel.MessageCommand = MessageCommand.Close;
MessageHelper._eventAggregator.GetEvent<MessageEvent>().Publish(messageViewModel);
}
}
对应的界面MessageView
<UserControl
x:Class="BlankApp2.Views.MessageView"
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:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:local="clr-namespace:BlankApp2.Views"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prism="http://prismlibrary.com/"
Visibility="{Binding VisibilityStatus}"
mc:Ignorable="d">
<Border
Width="auto"
Height="auto"
MinWidth="200"
MinHeight="60"
Background="{Binding BackgroundColor}"
BorderBrush="{Binding ForegroundColor}"
BorderThickness="2"
CornerRadius="10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="50" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal">
<materialDesign:PackIcon
Width="20"
Height="20"
Margin="20,0,20,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Foreground="{Binding ForegroundColor}"
Kind="{Binding Icon}" />
<TextBlock
VerticalAlignment="Center"
FontSize="14"
Foreground="{Binding ForegroundColor}"
Text="{Binding Message}" />
</StackPanel>
<Button
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Command="{Binding CloseMessageCommand}"
Style="{StaticResource MaterialDesignToolButton}"
Visibility="{Binding CloseButtonVisibility}">
<Button.Content>
<materialDesign:PackIcon
Width="20"
Height="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{Binding ForegroundColor}"
Kind="Close" />
</Button.Content>
</Button>
</Grid>
</Border>
</UserControl>
消息打算以列表形式存在,所以再来一个MessageListViewModel
,这里处理消息事件
public class MessageListViewModel : BindableBase
{
private readonly IEventAggregator _eventAggregator;
private ObservableCollection<MessageViewModel> _messageViewModelList;
/// <summary>
/// 消息集合
/// </summary>
public ObservableCollection<MessageViewModel> MessageViewModelList
{
get { return _messageViewModelList; }
set
{
_messageViewModelList = value;
this.RaisePropertyChanged(nameof(MessageViewModelList));
}
}
public MessageListViewModel(IEventAggregator eventAggregator)
{
this._eventAggregator = eventAggregator;
this.MessageViewModelList = new ObservableCollection<MessageViewModel>();
this._eventAggregator.GetEvent<MessageEvent>().Subscribe(arg => this.ShowMessage(arg), filter: arg => MessageCommand.Open == arg.MessageCommand);
this._eventAggregator.GetEvent<MessageEvent>().Subscribe(arg => this.CloseMessage(arg), filter: arg => MessageCommand.Close == arg.MessageCommand);
}
/// <summary>
/// 显示消息
/// </summary>
/// <param name="messageViewModel"></param>
private void ShowMessage(MessageViewModel messageViewModel)
{
this.MessageViewModelList.Add(messageViewModel);
this.AutoCloseMessage(messageViewModel);
}
/// <summary>
/// 关闭消息
/// </summary>
/// <param name="messageViewModel"></param>
private void CloseMessage(MessageViewModel messageViewModel)
{
this.MessageViewModelList.Remove(messageViewModel);
}
/// <summary>
/// 自动关闭消息
/// </summary>
/// <param name="messageViewModel"></param>
private async void AutoCloseMessage(MessageViewModel messageViewModel)
{
await Task.Delay(messageViewModel.Duration);
MessageHelper.CloseMessage(messageViewModel);
}
}
对应的界面MessageListView
,这里手动绑定了MessageView
的ViewModel,所以上面的MessageView
的自动绑定ViewModel要关掉
<UserControl
x:Class="BlankApp2.Views.MessageListView"
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:local="clr-namespace:BlankApp2.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prism="http://prismlibrary.com/"
d:DesignHeight="450"
d:DesignWidth="800"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d">
<ItemsControl ItemsSource="{Binding MessageViewModelList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:MessageView HorizontalAlignment="Center" DataContext="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>
主界面MainView
,只有几个按钮
<Window
x:Class="BlankApp2.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BlankApp2.Views"
xmlns:prism="http://prismlibrary.com/"
Title="{Binding Title}"
Width="600"
Height="500"
prism:ViewModelLocator.AutoWireViewModel="True">
<Canvas x:Name="MainCanvas">
<Canvas
x:Name="MainMessageCanvas"
Width="{Binding Path=ActualWidth, ElementName=MainCanvas}"
Height="{Binding Path=ActualHeight, ElementName=MainCanvas}"
HorizontalAlignment="Center"
Panel.ZIndex="999">
<local:MessageListView Width="{Binding ActualWidth, ElementName=MainMessageCanvas}" />
</Canvas>
<ContentControl prism:RegionManager.RegionName="{Binding MainContentRegionName}" />
<StackPanel Canvas.Bottom="0" Orientation="Horizontal">
<Button
Background="Gray"
Command="{Binding InfoCommand}"
Content="Info" />
<Button
Margin="20,0,0,0"
Background="Orange"
Command="{Binding WarningCommand}"
Content="Warning" />
<Button
Margin="20,0,0,0"
Background="Green"
Command="{Binding SuccessCommand}"
Content="Success" />
<Button
Margin="20,0,0,0"
Background="Red"
Command="{Binding ErrorCommand}"
Content="Error" />
</StackPanel>
</Canvas>
</Window>
还有MainViewModel
,这里绑定点击事件处理器
public class MainViewModel : BindableBase
{
private string _title;
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
private string _mainContentRegionName;
public string MainContentRegionName
{
get { return _mainContentRegionName; }
set
{
_mainContentRegionName = value;
this.RaisePropertyChanged(nameof(MainContentRegionName));
}
}
public DelegateCommand InfoCommand { get; set; }
public DelegateCommand WarningCommand { get; set; }
public DelegateCommand SuccessCommand { get; set; }
public DelegateCommand ErrorCommand { get; set; }
public MainViewModel()
{
this.Title = "测试";
this.MainContentRegionName = "MainContentRegion";
this.InfoCommand = new DelegateCommand(this.InfoCommandExecute);
this.WarningCommand = new DelegateCommand(this.WarningCommandExecute);
this.SuccessCommand = new DelegateCommand(this.SuccessCommandExecute);
this.ErrorCommand = new DelegateCommand(this.ErrorCommandExecute);
}
public void InfoCommandExecute()
{
MessageHelper.Info("这是一条Info消息", isCloseButtonVisibility: true);
}
public void WarningCommandExecute()
{
MessageHelper.Warning("这是一条Warning消息");
}
public void SuccessCommandExecute()
{
MessageHelper.Success("这是一条Success消息");
}
public void ErrorCommandExecute()
{
MessageHelper.Error("这是一条Error消息");
}
}
效果
我就不放gif了,也没做动画效果,就是个消息的显示和消失