WPF消息提示

WPF消息提示

使用.net6.0

使用的NuGet包

MaterialDesignThemes:4.9.0
Prism.DryIoc:8.1.97

我们要做一个类似Element Plus或者Ant DesignMessage,作为消息提示这样的交互比较友好,老是用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了,也没做动画效果,就是个消息的显示和消失

WPF消息提示 结束

posted @ 2023-08-03 17:51  .NET好耶  阅读(347)  评论(1编辑  收藏  举报