使用.NET 在 Windows 中创建弹出式“烤面包机”通知

我在吸毒。NET,并正在创建一个桌面应用程序/服务,将显示通知在我的桌面角落时,某些事件被触发。我不想使用一个普通的消息框 b/c,这将是太打扰。我希望通知滑入视图,然后在几秒钟后淡出。我正在考虑的东西,将行动非常像 Outlook 提醒,一个人得到的时候,一个新的信息到达。问题是: 我应该为此使用 WPF 吗?我从来没有做任何事情与 WPF,但会很高兴地尝试它,如果这是最好的手段,以最终。有没有一种方法可以实现这一点。NET 库?

74836 次浏览

WPF 让这个过程变得微不足道: 它可能只需要10分钟或更少的时间:

  1. 创建一个窗口,设置 AllowsTransparency = “ true”并向其添加一个 Grid
  2. 将网格的渲染变换设置为原点为0,1的 ScaleTransform
  3. 在网格上创建一个动画,使 ScaleX0变为1,然后将不透明度从1变为0
  4. 在构造函数中计算 Window。顶部和窗户。将窗口放置在屏幕右下角。

就是这样。

使用 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分钟。

请注意,调用线程必须是 sta,因为许多 ui 组件都需要它,同时在 system.timers.timer elapsed 事件下编写以下代码

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


}

The above code is refined version @Ray Burns approach. Added with time interval code. So that Notification window would close after 4 seconds..

调用 Window 为,

NotificationWindow nfw = new NotificationWindow();
nfw.Show();

我继续前进,并为此创建了一个 CodePlex 网站,其中包括“吐司弹出窗口”和控制“帮助气球”。这些版本具有比下面描述的更多的特性。https://toastspopuphelpballoon.codeplex.com.

这是我所寻找的解决方案的一个很好的起点。为了满足我的要求,我做了一些修改:

  • 我想停止鼠标上的动画。
  • 鼠标离开时“重置”动画。
  • 当不透明度达到0时关闭窗口。
  • 堆叠吐司(如果窗口数量超过屏幕高度,我还没有解决这个问题)
  • 从我的 ViewModel 调用 Load

这是我的 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;
}
}
}
}
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();

我使用上面的答案来设计我自己的通知窗口,在我看来,这个窗口更加用户友好,并且使用了一些技术,花了我一点时间才弄明白。分享是为了帮助其他人:

  1. 添加了 MouseEnter 事件触发器,可以立即将窗口不透明度设置为1,这样用户就不必等待窗口淡入全视图。
  2. 添加了 MouseLeaf 事件触发器,当用户将鼠标移出窗口时将窗口不透明度淡化为0。
  3. 添加了 MouseUp (鼠标单击)事件触发器,可以立即将窗口不透明度设置为0以隐藏通知窗口。
  4. 如果必须多次显示()和隐藏()通知窗口,那么下面的方法也会在结束时重置情节串联图板,以便下一个 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;
}));
}
}