代码之家  ›  专栏  ›  技术社区  ›  Antony

在带有.NET的Windows中创建弹出式“烤面包机”通知

  •  63
  • Antony  · 技术社区  · 14 年前

    我正在使用.NET,正在创建一个桌面应用程序/服务,当触发某些事件时,它将在桌面的一角显示通知。我不想使用一个常规的消息框b/c,因为它太过侵入。我希望通知滑入视图,几秒钟后淡出。我正在考虑一些事情,它的作用非常类似于新邮件到达时收到的Outlook警报。问题是:我应该使用wpf吗?我从来没有用过WPF做过任何事情,但如果这是最好的方法,我会很高兴地尝试它。有没有一种方法可以通过常规的.NET库来实现这一点?

    6 回复  |  直到 14 年前
        1
  •  110
  •   Ray Burns    14 年前

    WPF使这一点变得非常微不足道:它可能需要10分钟或更少的时间。步骤如下:

    1. 创建一个窗口,设置allowsTransparency=“true”并向其添加网格
    2. 将网格的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>
    

    代码隐藏:

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

        2
  •  16
  •   LawMan    10 年前

    我接着创建了一个codeplex站点,其中包括“ToastPopups”和“HelpBallots”控件。这些版本比下面描述的功能更多。 https://toastspopuphelpballoon.codeplex.com .

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

    • 我想在鼠标悬停时停止动画。
    • 鼠标离开时“重置”动画。
    • 当不透明度达到0时关闭窗口。
    • 把吐司叠起来(如果窗口数超过屏幕高度,我还没有解决这个问题)
    • 从我的视图模型调用加载

    这是我的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;
                    }
                }
            }
        }
    
        3
  •  7
  •   Anees Deen    10 年前
    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();
        }
    
    }
    

    以上代码是经过改进的@ray Burns方法版本。添加了时间间隔代码。这样通知窗口将在4秒后关闭。

    将窗口称为,

    NotificationWindow nfw = new NotificationWindow();
    nfw.Show();
    
        4
  •  2
  •   egor.zhdan    9 年前
    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();
    
        5
  •  0
  •   Regular Jo    7 年前

    请注意,调用线程必须是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; 
        })); 
    }
    
        6
  •  0
  •   user1367200    6 年前

    我使用上面的答案来设计我自己的通知窗口,在我看来,它对用户更友好,并且使用了一些花了我一点时间来弄清楚的技术。分享以防它帮助其他人。

    1. 添加了mouseenter事件触发器,可以立即将窗口不透明度设置为1,这样用户就不必等待窗口淡入完整视图。
    2. 添加了mouseleave事件触发器,当用户将鼠标移出窗口时,将窗口不透明度淡入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;
            }));
        }
    }