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

Ant*_*ony 64 .net windows wpf desktop

我正在使用.NET并且正在创建一个桌面应用程序/服务,当触发某些事件时,它将在桌面的角落显示通知.我不想使用过于干扰的常规消息框b/c.我希望通知滑入视图,然后在几秒后淡出.我正在考虑一些非常类似于新消息到达时获得的Outlook警报的内容.问题是:我应该使用WPF吗?我从未对WPF做过任何事情,但如果这是最好的手段,我会很乐意尝试.有没有办法通过常规.NET库实现这一目标?

Ray*_*rns 110

WPF使得这绝对是微不足道的:它可能需要十分钟或更短时间.以下是步骤:

  1. 创建一个Window,设置AllowTransparency ="true"并为其添加一个Grid
  2. 将Grid的RenderTransform设置为原点为0,1的ScaleTransform
  3. 在网格上创建动画,将ScaleX 0设置为1,然后将不透明度从1设置为0
  4. 在构造函数中计算Window.Top和Window.Left,将窗口放在屏幕的右下角.

这里的所有都是它的.

使用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>
Run Code Online (Sandbox Code Playgroud)

代码背后:

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;
    }));
  }
}
Run Code Online (Sandbox Code Playgroud)

由于WPF是普通.NET库之一,答案是肯定的,这可能的"常规.NET库",以实现这一目标.

如果您在没有使用WPF的情况下询问是否有办法做到这一点,答案仍然是肯定的,但它非常复杂,需要5天而不是5分钟.

  • 不确定是否需要这个,但我在不透明度动画中添加了一个Completed事件,并在后面的代码中添加了"this.Close();".否则窗口将始终保持打开状态.可能会搞乱一个基于最后一个窗口关闭关闭的应用程序. (18认同)
  • 也可以使用`System.Windows.SystemParameters.WorkArea`,因此不需要使用windows.forms dll(如本问题中所述:http://stackoverflow.com/questions/7620488/how-to-set-the -location-的-WPF窗口到最右下方刀尖的桌面) (16认同)
  • PresentationSource.FromVisual(this)在这里返回null,任何想法可能导致什么? (6认同)
  • 抱歉重新审议这个问题; 但@Paul,你能不能〜演示你是如何添加已完成的活动的? (3认同)
  • @NewAmbition,如果仍然没有想到,我做了,但不知道这是否是保罗使用的.无论如何,在这里它 - 使用动画的已完成事件来动画该不透明度 (3认同)

Law*_*Man 17

我继续为此创建了一个CodePlex网站,其中包括"Toast Popups"和控制"Help Balloons".这些版本具有比下面描述的更多功能. 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>
Run Code Online (Sandbox Code Playgroud)

守则背后

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

来自ViewModel的调用:

    private void ShowNotificationExecute()
    {
        App.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(
            () =>
            {
                var notify = new NotificationWindow();
                notify.Show();
            }));
    }
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

更新:当窗体关闭以"删除"其他窗口时,我添加了此事件处理程序.

    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;
                }
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)


Ane*_*een 7

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

}
Run Code Online (Sandbox Code Playgroud)

以上代码是@Ray Burns方法的精炼版.添加了时间间隔代码.因此通知窗口将在4秒后关闭.

称之为窗口,

NotificationWindow nfw = new NotificationWindow();
nfw.Show();
Run Code Online (Sandbox Code Playgroud)