Создание всплывающих уведомлений "тостер" в Windows with.NET
Я использую .NET и создаю настольное приложение/службу, которая будет отображать уведомления в углу моего рабочего стола при запуске определенных событий. Я не хочу использовать обычное окно сообщений b/c, которое было бы слишком навязчивым. Я хочу, чтобы уведомления скользили в поле зрения, а затем исчезали через несколько секунд. Я думаю о чем-то, что будет действовать очень похоже на предупреждения Outlook, которые вы получаете, когда приходит новое сообщение. Вопрос: Должен ли я использовать WPF для этого? Я никогда этого не делал что-нибудь с WPF, но с радостью попробует, если это лучший способ до конца. Есть ли способ сделать это с помощью обычных библиотек .NET?
6 ответов:
WPF делает это абсолютно тривиальным: это, вероятно, займет десять минут или меньше. Вот шаги:
- создайте окно, установите AllowsTransparency= "true" и добавьте к нему сетку
- установите Rendertransform сетки в ScaleTransform с началом 0,1
- создать анимацию на сетке, которая анимирует ScaleX от 0 до 1, а затем анимирует непрозрачность от 1 до 0
- в окне конструктор вычислить.Верх и окно.Осталось разместить окно в правом нижнем углу экрана.
вот и все.
С помощью Expression Blend мне потребовалось около 8 минут, чтобы сгенерировать следующий рабочий код:
<Window x:Class="NotificationWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Notification Popup" Width="300" SizeToContent="Height" WindowStyle="None" AllowsTransparency="True" Background="Transparent"> <Grid RenderTransformOrigin="0,1" > <!-- Notification area --> <Border BorderThickness="1" Background="Beige" BorderBrush="Black" CornerRadius="10"> <StackPanel Margin="20"> <TextBlock TextWrapping="Wrap" Margin="5"> <Bold>Notification data</Bold><LineBreak /><LineBreak /> Something just happened and you are being notified of it. </TextBlock> <CheckBox Content="Checkable" Margin="5 5 0 5" /> <Button Content="Clickable" HorizontalAlignment="Center" /> </StackPanel> </Border> <!-- Animation --> <Grid.Triggers> <EventTrigger RoutedEvent="FrameworkElement.Loaded"> <BeginStoryboard> <Storyboard> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)"> <SplineDoubleKeyFrame KeyTime="0:0:0" Value="0"/> <SplineDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)"> <SplineDoubleKeyFrame KeyTime="0:0:2" Value="1"/> <SplineDoubleKeyFrame KeyTime="0:0:4" Value="0"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger> </Grid.Triggers> <Grid.RenderTransform> <ScaleTransform ScaleY="1" /> </Grid.RenderTransform> </Grid> </Window>
С кодом:
using System; using System.Windows; using System.Windows.Threading; public partial class NotificationWindow { public NotificationWindow() { InitializeComponent(); Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() => { var workingArea = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea; var transform = PresentationSource.FromVisual(this).CompositionTarget.TransformFromDevice; var corner = transform.Transform(new Point(workingArea.Right, workingArea.Bottom)); this.Left = corner.X - this.ActualWidth - 100; this.Top = corner.Y - this.ActualHeight; })); } }
поскольку WPF является одной из обычных библиотек .NET, ответ да, это и это можно сделать с помощью "обычных библиотек .NET".
Если вы спрашиваете, есть ли способ сделать это без использования WPF ответ по-прежнему да, но это чрезвычайно сложно и займет больше времени, чем 5 дней 5 минут.
Я пошел вперед и создал сайт CodePlex для этого, который включает в себя "всплывающие окна тостов" и контроль "воздушные шары помощи". Эти версии имеют больше возможностей, чем описано ниже. https://toastspopuphelpballoon.codeplex.com.
Это был отличный трамплин для решения, которое я искал. Я сделал несколько изменений, чтобы удовлетворить мои требования:
- я хотел остановить анимацию на мыши свыше.
- "сброс" анимации при уходе мыши.
- закройте окно, когда непрозрачность достигла 0.
- стек тост (я еще не решил проблему, если количество окон превышает высоту экрана)
- вызов нагрузки из моего ViewModel
вот мой XAML
<Window x:Class="Foundation.FundRaising.DataRequest.Windows.NotificationWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="NotificationWindow" Height="70" Width="300" ShowInTaskbar="False" WindowStyle="None" AllowsTransparency="True" Background="Transparent"> <Grid RenderTransformOrigin="0,1" > <Border BorderThickness="2" Background="{StaticResource GradientBackground}" BorderBrush="DarkGray" CornerRadius="7"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="60"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="24"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="30"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Image Grid.Column="0" Grid.RowSpan="2" Source="Resources/data_information.png" Width="40" Height="40" VerticalAlignment="Center" HorizontalAlignment="Center"/> <Image Grid.Column="2" Source="Resources/error20.png" Width="20" Height="20" VerticalAlignment="Center" ToolTip="Close" HorizontalAlignment="Center" Cursor="Hand" MouseUp="ImageMouseUp"/> <TextBlock Grid.Column="1" Grid.Row="0" VerticalAlignment="Center" HorizontalAlignment="Center" FontWeight="Bold" FontSize="15" Text="A Request has been Added"/> <Button Grid.Column="1" Grid.Row="1" FontSize="15" Margin="0,-3,0,0" HorizontalAlignment="Center" VerticalAlignment="Center" Content="Click Here to View" Style="{StaticResource LinkButton}"/> </Grid> </Border> <!-- Animation --> <Grid.Triggers> <EventTrigger RoutedEvent="FrameworkElement.Loaded"> <BeginStoryboard x:Name="StoryboardLoad"> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="(UIElement.Opacity)" From="0.0" To="1.0" Duration="0:0:2" /> <DoubleAnimation Storyboard.TargetProperty="(UIElement.Opacity)" From="1.0" To="0.0" Duration="0:0:8" BeginTime="0:0:5" Completed="DoubleAnimationCompleted"/> </Storyboard> </BeginStoryboard> </EventTrigger> <EventTrigger RoutedEvent="Mouse.MouseEnter"> <EventTrigger.Actions> <RemoveStoryboard BeginStoryboardName="StoryboardLoad"/> <RemoveStoryboard BeginStoryboardName="StoryboardFade"/> </EventTrigger.Actions> </EventTrigger> <EventTrigger RoutedEvent="Mouse.MouseLeave"> <BeginStoryboard x:Name="StoryboardFade"> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="(UIElement.Opacity)" From="1.0" To="0.0" Duration="0:0:8" BeginTime="0:0:2" Completed="DoubleAnimationCompleted"/> </Storyboard> </BeginStoryboard> </EventTrigger> </Grid.Triggers> <Grid.RenderTransform> <ScaleTransform ScaleY="1" /> </Grid.RenderTransform> </Grid>
Код
public partial class NotificationWindow : Window { public NotificationWindow() : base() { this.InitializeComponent(); this.Closed += this.NotificationWindowClosed; } public new void Show() { this.Topmost = true; base.Show(); this.Owner = System.Windows.Application.Current.MainWindow; this.Closed += this.NotificationWindowClosed; var workingArea = Screen.PrimaryScreen.WorkingArea; this.Left = workingArea.Right - this.ActualWidth; double top = workingArea.Bottom - this.ActualHeight; foreach (Window window in System.Windows.Application.Current.Windows) { string windowName = window.GetType().Name; if (windowName.Equals("NotificationWindow") && window != this) { window.Topmost = true; top = window.Top - window.ActualHeight; } } this.Top = top; } private void ImageMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e) { this.Close(); } private void DoubleAnimationCompleted(object sender, EventArgs e) { if (!this.IsMouseOver) { this.Close(); } } }
вызов из ViewModel:
private void ShowNotificationExecute() { App.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action( () => { var notify = new NotificationWindow(); notify.Show(); })); }
стили, на которые ссылаются в XAML:
<Style x:Key="LinkButton" TargetType="Button"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <TextBlock> <ContentPresenter /> </TextBlock> </ControlTemplate> </Setter.Value> </Setter> <Setter Property="Foreground" Value="Blue"/> <Setter Property="Cursor" Value="Hand"/> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="ContentTemplate"> <Setter.Value> <DataTemplate> <TextBlock TextDecorations="Underline" Text="{TemplateBinding Content}"/> </DataTemplate> </Setter.Value> </Setter> </Trigger> </Style.Triggers> </Style> <LinearGradientBrush x:Key="GradientBackground" EndPoint="0.504,1.5" StartPoint="0.504,0.03"> <GradientStop Color="#FFFDD5A7" Offset="0"/> <GradientStop Color="#FFFCE79F" Offset="0.567"/> </LinearGradientBrush>
обновление: я добавил этот обработчик событий, когда форма закрыта, чтобы "отбросить" другие окна.
private void NotificationWindowClosed(object sender, EventArgs e) { foreach (Window window in System.Windows.Application.Current.Windows) { string windowName = window.GetType().Name; if (windowName.Equals("NotificationWindow") && window != this) { // Adjust any windows that were above this one to drop down if (window.Top < this.Top) { window.Top = window.Top + this.ActualHeight; } } } }
public partial class NotificationWindow : Window { DispatcherTimer timer = new System.Windows.Threading.DispatcherTimer(); public NotificationWindow() : base() { this.InitializeComponent(); Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() => { var workingArea = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea; var transform = PresentationSource.FromVisual(this).CompositionTarget.TransformFromDevice; var corner = transform.Transform(new Point(workingArea.Right, workingArea.Bottom)); this.Left = corner.X - this.ActualWidth; this.Top = corner.Y - this.ActualHeight; })); timer.Interval = TimeSpan.FromSeconds(4d); timer.Tick += new EventHandler(timer_Tick); } public new void Show() { base.Show(); timer.Start(); } void timer_Tick(object sender, EventArgs e) { //set default result if necessary timer.Stop(); this.Close(); } }
приведенный выше код является усовершенствованной версией подход @лучевых ожогах. Добавлено с кодом временного интервала. Так что окно уведомления закроется через 4 секунды..
вызвать окно как
NotificationWindow nfw = new NotificationWindow(); nfw.Show();
NotifyIcon notifyIcon = new NotifyIcon(); Stream iconStream = System.Windows.Application.GetResourceStream(new Uri("pack://application:,,,/Assets/ic_instant_note_tray.ico")).Stream; notifyIcon.Icon = new System.Drawing.Icon(iconStream); notifyIcon.Text = string.Format(Properties.Resources.InstantNoteAppName, Constants.Application_Name); notifyIcon.Visible = true; notifyIcon.ShowBalloonTip(5000, "tooltiptitle", "tipMessage", ToolTipIcon.Info); notifyIcon.Visible = false; notifyIcon.Dispose();
обратите внимание, что вызывающий поток должен быть sta, потому что многие компоненты пользовательского интерфейса требуют этого при написании следующего кода в системе.таймеры.таймер прошедшее событие
Window1 notifyWin = new Window1(); bool? isOpen = notifyWin.ShowDialog(); if (isOpen != null && isOpen == true) { notifyWin.Close(); } System.Threading.Thread.Sleep(1000); notifyWin.ShowDialog();
в конструкторе window1:
public Window1() { InitializeComponent(); Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() => { var workingArea = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea; var transform = PresentationSource.FromVisual(this).CompositionTarget.TransformFromDevice; var corner = transform.Transform(new Point(workingArea.Right, workingArea.Bottom)); this.Left = corner.X - this.ActualWidth - 100; this.Top = corner.Y - this.ActualHeight; })); }
я использовал ответ выше, чтобы создать свое собственное окно уведомлений, которое немного более удобно для пользователя, на мой взгляд, и использует несколько методов, которые заняли у меня немного времени, чтобы понять. Обмен в случае, если это поможет кому-то еще там.:
- добавлен триггер события MouseEnter, чтобы сразу установить непрозрачность окна на 1, поэтому пользователю не нужно ждать, пока окно полностью исчезнет.
- добавлен триггер события MouseLeave, чтобы уменьшить непрозрачность окна до 0, когда пользователь перемещает мышь из окна.
- добавлен триггер события MouseUp (щелчок мыши), чтобы сразу установить непрозрачность окна в 0, чтобы скрыть окно уведомления.
- Если вам нужно показать() и скрыть() окно уведомления несколько раз, то ниже метод также сбрасывает раскадровку в конце, так что следующая операция Show () запускает операцию с самого начала, и в анимации нет сбоя.
XAML:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="ToastNotificationWindow" Title="Notification Popup" Width="480" Height="140" WindowStyle="None" AllowsTransparency="True" Background="Transparent" BorderThickness="0" Topmost="True" > <Grid Background="Transparent" Name="ToastWindowGrid" RenderTransformOrigin="0,1"> <Border Name="ToastWindowBorder" BorderThickness="0" Background="#333333"> <StackPanel Name="ToastWindowStackPanel" Margin="10" Orientation="Horizontal"> <Image Name="ToastLogo" Width="100" Height="100" Source="D:\Development\ToastNotification\resources\Logo-x100.png"/> <StackPanel Name="ToastMessageStackPanel" Width="359"> <TextBox Name="ToastTitleTextBox" Margin="5" MaxWidth="340" Background="#333333" BorderThickness="0" IsReadOnly="True" Foreground="White" FontSize="20" Text="Toast Title" FontWeight="Bold" HorizontalContentAlignment="Center" Width="Auto" HorizontalAlignment="Stretch" IsHitTestVisible="False"/> <TextBox Name="ToastMessageTextBox" Margin="5" MaxWidth="340" Background="#333333" BorderThickness="0" IsReadOnly="True" Foreground="LightGray" FontSize="16" Text="Toast title message. Click to start something." HorizontalContentAlignment="Left" TextWrapping="Wrap" IsHitTestVisible="False"/> </StackPanel> </StackPanel> </Border> <Grid.Triggers> <EventTrigger RoutedEvent="FrameworkElement.Loaded"> <EventTrigger.Actions> <BeginStoryboard Name="StoryboardLoad"> <Storyboard Name="ToastAnimationStoryboard"> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)" FillBehavior="HoldEnd"> <SplineDoubleKeyFrame KeyTime="0:0:0" Value="0"/> <SplineDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" FillBehavior="HoldEnd"> <SplineDoubleKeyFrame KeyTime="0:0:0" Value="0"/> <SplineDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" FillBehavior="HoldEnd"> <SplineDoubleKeyFrame KeyTime="0:0:20" Value="1"/> <SplineDoubleKeyFrame KeyTime="0:0:23" Value="0"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <EventTrigger RoutedEvent="Mouse.MouseEnter"> <EventTrigger.Actions> <StopStoryboard BeginStoryboardName="StoryboardMouseLeaveFadeOut"/> <BeginStoryboard Name="StoryboardMouseEnterFadeIn"> <Storyboard Name="ToastAnimationStoryboardMouseEnterFadeIn"> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" FillBehavior="HoldEnd"> <DiscreteDoubleKeyFrame KeyTime="0:0:0.1" Value="1"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <EventTrigger RoutedEvent="Mouse.MouseLeave"> <EventTrigger.Actions> <RemoveStoryboard BeginStoryboardName="StoryboardMouseLeaveFadeOut"/> <BeginStoryboard Name="StoryboardMouseLeaveFadeOut"> <Storyboard Name="ToastAnimationStoryboardMouseLeaveFadeOut"> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" FillBehavior="HoldEnd"> <SplineDoubleKeyFrame KeyTime="0:0:0" Value="1"/> <SplineDoubleKeyFrame KeyTime="0:0:3" Value="0"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <EventTrigger RoutedEvent="Mouse.MouseUp"> <EventTrigger.Actions> <StopStoryboard BeginStoryboardName="StoryboardMouseEnterFadeIn"/> <RemoveStoryboard BeginStoryboardName="StoryboardMouseEnterFadeIn"/> <StopStoryboard BeginStoryboardName="StoryboardMouseLeaveFadeOut"/> <RemoveStoryboard BeginStoryboardName="StoryboardMouseLeaveFadeOut"/> <BeginStoryboard Name="StoryboardMouseClickFadeOut"> <Storyboard Name="ToastAnimationStoryboardMouseClickFadeOut"> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" FillBehavior="HoldEnd"> <DiscreteDoubleKeyFrame KeyTime="0:0:0.1" Value="0"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> <SeekStoryboard BeginStoryboardName="StoryboardLoad"/> <PauseStoryboard BeginStoryboardName="StoryboardLoad"/> </EventTrigger.Actions> </EventTrigger> </Grid.Triggers> <Grid.RenderTransform> <ScaleTransform ScaleY="1" /> </Grid.RenderTransform> </Grid> </Window>
код сзади:
using System; using System.Windows; using System.Windows.Threading; public partial class ToastNotificationWindow { public ToastNotificationWindow() { InitializeComponent(); Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() => { var workingArea = System.Windows.SystemParameters.WorkArea; var transform = PresentationSource.FromVisual(this).CompositionTarget.TransformFromDevice; var corner = transform.Transform(new Point(workingArea.Right, workingArea.Bottom)); this.Left = corner.X - this.ActualWidth - 10; this.Top = corner.Y - this.ActualHeight; })); } }