尝试创建 Class Toast 通知,无法多次实例化

Trying to create a Class Toast Notification, cannot instance it multiple times

我正在尝试在 WPF 中创建一个 Toast 通知,我的问题是,如果我在这种情况下从数据库中删除某些内容的速度过快,则当前通知只会升级为新文本,而不是创建一个新文本class 的实例,所以我可以看到两个通知。

通知成为来自主窗口的 StackPanel 的 child,这样它始终具有相同的位置。这就是为什么我在开头清除children。

class 有效,只是我一次无法处理多个通知。

我错过了什么?不好意思,我是新手,自己试了一下。

这是我的class代码

{
   public class NotificationToast
    {
        public Border ToastBorder { get; set; }
        public Grid ToastGrid { get; set; }
        public TextBlock BodyBlock { get; set; }
        public String NotificationString { get; set; }
        public DoubleAnimation Animation1 { get; set; }
        public DoubleAnimation Animation2 { get; set; }
        public TranslateTransform Transformation1 { get; set; }
        public TranslateTransform Transformation2 { get; set; }

        public NotificationToast(MainWindow window, string notificationString)
        {
            InitializeWindow(window, notificationString);   
        }

        private void InitializeWindow(MainWindow window, string notificationString)
        {
            NotificationString = notificationString;
            ToastBorder = new Border();
            ToastBorder.Width = 250;
            ToastBorder.Height = 70;
            ToastBorder.BorderThickness = new Thickness(2);
            ToastBorder.BorderBrush = Brushes.IndianRed;
            ToastBorder.Background = new SolidColorBrush(Color.FromRgb(240, 143, 116));
            TextBlock BodyBlock = new TextBlock();
            BodyBlock.Width = 248;
            BodyBlock.Height = 68;
            BodyBlock.TextWrapping = TextWrapping.Wrap;
            BodyBlock.FontSize = 16;
            BodyBlock.Text = NotificationString;
            BodyBlock.Margin = new Thickness(5);

            ToastBorder.Child = BodyBlock;
            window.stackNotification.Children.Clear();
            window.stackNotification.Children.Add(ToastBorder);

            MovingIn(window.stackNotification);
            MovingOut(window.stackNotification);

        }

        private void MovingIn(StackPanel movingBorder)
        {
            TranslateTransform trans = new TranslateTransform();
            movingBorder.RenderTransform = trans;
            Animation1 = new DoubleAnimation(80, 0, TimeSpan.FromSeconds(1));
            trans.BeginAnimation(TranslateTransform.YProperty, Animation1);
        }
        private async void MovingOut(StackPanel movingBorder)
        {
            await Task.Delay(2500);

            TranslateTransform trans = new TranslateTransform();
            movingBorder.RenderTransform = trans;
            Animation2 = new DoubleAnimation(0, 80, TimeSpan.FromSeconds(1));
            trans.BeginAnimation(TranslateTransform.YProperty, Animation2);
        }
    }
}

然后我这样调用 class

WindowToast = new NotificationToast(ParentWindow, Convert.ToString("The Player " + PersonDetails.FirstName + " " + PersonDetails.LastName + " details has been updated."));

这是使用 MVVM 编程模式的 Toast 消息 的简短示例。

免责声明:我自己从头开始做的,我不是专业程序员。 WPF是我的爱好,不工作。因此,解决方案经过测试但可能包含错误或不准确的实现。不要相信我。

由于模式方法,我们需要两个助手 classes

// INPC interface implementation, used for notifying UI if some Property was changed.
public class NotifyPropertyChanged : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

// Needed for easy Commands use
public class RelayCommand : ICommand
{
    private readonly Action<object> _execute;
    private readonly Func<object, bool> _canExecute;

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
    public void Execute(object parameter) => _execute(parameter);
}

解决方案特点:

  • 在右下角显示Toast消息
  • 支持3种严重性:信息、警告、错误。消息的颜色取决于它。
  • 支持调整一次显示的最大消息条数
  • 排队收到超过限制的消息并稍后显示
  • 用户可以立即关闭任何消息
  • 每条消息在约 10 秒后消失
  • 添加了一些出现和消失的动画

注:

  • 不支持并发,例如推送来自不同 Threads/Tasks.
  • 的消息
  • 不是 UserControl 或独立解决方案。

...但您可以改进它。 :)

数据class:

public enum ToastMessageSeverity
{
    Info = 0,
    Warning = 1,
    Error = 2
}

public class ToastMessage
{
    public string Message { get; set; }
    public ToastMessageSeverity Severity { get; set; }
}

ToastViewModel

public class ToastViewModel : ReadOnlyObservableCollection<ToastMessage>
{
    private readonly ObservableCollection<ToastMessage> _items;
    private readonly int _maxCount;
    private readonly Queue<ToastMessage> _messagesQueue;
    private ICommand _removeItem;

    private void RemoveMessage(ToastMessage message)
    {
        if (_items.Contains(message))
        {
            _items.Remove(message);
            if (_messagesQueue.Count > 0) Push(_messagesQueue.Dequeue());
        }
    }

    public void Push(ToastMessage message)
    {
        if (_items.Count >= _maxCount)
            _messagesQueue.Enqueue(message);
        else
        {
            _items.Add(message);
            Task.Run(async () => await Task.Delay(10500)).ContinueWith(_ => RemoveMessage(message), TaskScheduler.FromCurrentSynchronizationContext());
        }
    }

    public ICommand RemoveItem => _removeItem ?? (_removeItem = new RelayCommand(parameter => 
    {
        if (parameter is ToastMessage message) RemoveMessage(message);
    }));

    public ToastViewModel() : this(6) { }
    public ToastViewModel(int maxCount) : this(new ObservableCollection<ToastMessage>(), maxCount) { }
    private ToastViewModel(ObservableCollection<ToastMessage> items, int maxCount) : base(items)
    {
        _items = items;
        _maxCount = maxCount;
        _messagesQueue = new Queue<ToastMessage>();
    }
}

MainViewModel

public class MainViewModel : NotifyPropertyChanged
{
    private ToastViewModel _toastMessages;
    private ICommand _pushToastCommand;
    public ToastViewModel ToastMessages
    {
        get => _toastMessages;
        set
        {
            _toastMessages = value;
            OnPropertyChanged();
        }
    }
    private int counter = 0;
    public ICommand PushToastCommand => _pushToastCommand ?? (_pushToastCommand = new RelayCommand(parameter =>
    {
        ToastMessageSeverity severity = ToastMessageSeverity.Info;
        if (parameter is string severityString)
        {
            foreach (ToastMessageSeverity tms in Enum.GetValues(typeof(ToastMessageSeverity)))
            {
                if (severityString == tms.ToString())
                {
                    severity = tms;
                    break;
                }
            }
        }
        ToastMessages.Push(new ToastMessage { Message = severity + " message " + counter++, Severity = severity });
    }));
    public MainViewModel()
    {
        ToastMessages = new ToastViewModel();
    }
}

和完整标记(将允许复制整个应用程序)

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApp1"
        Title="MainWindow" Height="600" Width="1000" WindowStartupLocation="CenterScreen">
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>
    <Grid>
        <Grid>
            <StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
                <Button Content="Info message" Command="{Binding PushToastCommand}" Padding="10,5" Margin="10" />
                <Button Content="Warning message" Command="{Binding PushToastCommand}" CommandParameter="Warning" Padding="10,5" Margin="10" />
                <Button Content="Error message" Command="{Binding PushToastCommand}" CommandParameter="Error" Padding="10,5" Margin="10" />
            </StackPanel>
        </Grid>
        <Grid>
            <ItemsControl ItemsSource="{Binding ToastMessages}" Margin="10" HorizontalAlignment="Right" VerticalAlignment="Bottom">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Border HorizontalAlignment="Right" >
                            <Border.Style>
                                <Style TargetType="Border">
                                    <Setter Property="BorderThickness" Value="2"/>
                                    <Setter Property="CornerRadius" Value="5"/>
                                    <Setter Property="Margin" Value="10,5"/>
                                    <Setter Property="Padding" Value="15,10"/>
                                    <Setter Property="MaxWidth" Value="300"/>
                                    <Style.Triggers>
                                        <DataTrigger Binding="{Binding Severity}" Value="Info">
                                            <Setter Property="BorderBrush" Value="CadetBlue"/>
                                            <Setter Property="Background" Value="LightCyan"/>
                                        </DataTrigger>
                                        <DataTrigger Binding="{Binding Severity}" Value="Warning">
                                            <Setter Property="BorderBrush" Value="Orange"/>
                                            <Setter Property="Background" Value="LightYellow"/>
                                        </DataTrigger>
                                        <DataTrigger Binding="{Binding Severity}" Value="Error">
                                            <Setter Property="BorderBrush" Value="Red"/>
                                            <Setter Property="Background" Value="LightPink"/>
                                        </DataTrigger>
                                        <EventTrigger RoutedEvent="Border.Loaded">
                                            <BeginStoryboard>
                                                <Storyboard>
                                                    <DoubleAnimation Storyboard.TargetProperty="Opacity" From="0.0" To="1.0" Duration="0:0:0.5" />
                                                    <ThicknessAnimation Storyboard.TargetProperty="Margin" From="10,15,10,-5" To="10,5" Duration="0:0:0.5" />
                                                    <DoubleAnimation Storyboard.TargetProperty="Opacity" From="1.0" To="0.0" BeginTime="0:0:10" Duration="0:0:0.2" />
                                                </Storyboard>
                                            </BeginStoryboard>
                                        </EventTrigger>
                                    </Style.Triggers>
                                </Style>
                            </Border.Style>
                            <Grid>
                                <TextBlock Text="{Binding Message}" FontSize="16" TextWrapping="Wrap" />
                                <Button HorizontalAlignment="Right" VerticalAlignment="Top" Margin="-13" Command="{Binding ItemsSource.RemoveItem, RelativeSource={RelativeSource AncestorType=ItemsControl}}" CommandParameter="{Binding}">
                                    <Button.Template>
                                        <ControlTemplate>
                                            <TextBlock Text="×" FontSize="16" Foreground="{Binding BorderBrush, RelativeSource={RelativeSource AncestorType=Border}}" Cursor="Hand"/>
                                        </ControlTemplate>
                                    </Button.Template>
                                </Button>
                            </Grid>
                        </Border>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </Grid>
    </Grid>
</Window>

[

传统上对于 MVVM 新手:代码隐藏 class

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }
}