尝试创建 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();
}
}
我正在尝试在 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();
}
}