Попытка создать Уведомление Toast Class, не может создать его несколько раз - PullRequest
1 голос
/ 02 мая 2020

Я пытаюсь создать Toast Notification в WPF, моя проблема в том, что если я в этом случае слишком быстро что-то удаляю из базы данных, текущее уведомление просто обновляется новым текстом вместо создания нового экземпляра класс, так что я вижу два уведомления.

Уведомление становится дочерним элементом StackPanel из MainWindow, таким образом, оно всегда имеет одинаковую позицию. Вот почему я очищаю детей в начале.

Класс работает, просто я не могу получить более одного уведомления за раз.

Чего мне не хватает? Извините, я довольно новичок, и я пытался сделать это сам.

Это мой код класса

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

А потом я назвал класс вот так

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

1 Ответ

1 голос
/ 02 мая 2020

Вот краткий пример Тостовых сообщений с использованием MVVM шаблон программирования.

Отказ от ответственности: Я сделал это сам из царапина и я не профессиональный программист. WPF это мое хобби, а не работа. Таким образом, решение протестировано, но может содержать ошибки или неточные реализации. Не верьте мне.

Из-за подхода с использованием шаблонов нам нужны два вспомогательных класса

// 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 message
  • Поддерживает 3 уровня серьезности: информация, предупреждение, ошибка. Цвет сообщения зависит от него.
  • Поддерживает настройку максимального количества сообщений для одновременного отображения
  • Позволяет ставить в очередь полученные сообщения выше предела и показывать их позже
  • Пользователь может закрыть любое сообщение немедленно
  • Каждое сообщение исчезает через ~ 10 секунд
  • Добавлены некоторые анимации появления и исчезновения

Примечание:

  • Нет поддержки параллелизма, например, отправка сообщений из разных потоков / задач.
  • Не UserControl или автономное решение.

... но вы можете улучшить его. :)

Класс данных:

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>

[Toast messages[1]

И традиционно для MVVM новичков: класс с выделенным кодом

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