Вот краткий пример Тостовых сообщений с использованием 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>
[
И традиционно для MVVM новичков: класс с выделенным кодом
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}