it-swarm.dev

Erstellen Sie Popup-Benachrichtigungen für "Toaster" in Windows mit .NET

Ich verwende .NET und erstelle eine Desktop-App/einen Desktop-Dienst, der in der Ecke meines Desktops Benachrichtigungen anzeigt, wenn bestimmte Ereignisse ausgelöst werden. Ich möchte keine normale Message Box b/c verwenden, die zu aufdringlich wäre. Ich möchte, dass Benachrichtigungen sichtbar werden und nach wenigen Sekunden ausgeblendet werden. Ich denke an etwas, das sich sehr wie die Outlook-Warnungen verhält, die man erhält, wenn eine neue Nachricht eintrifft. Die Frage ist: Soll ich WPF dafür verwenden? Ich habe noch nie etwas mit WPF gemacht, werde es aber gerne ausprobieren, wenn es am besten ist. Gibt es eine Möglichkeit, dies mit regulären .NET-Bibliotheken zu erreichen?

62
Antony

WPF macht das absolut trivial: Es würde vermutlich zehn Minuten oder weniger dauern. Hier sind die Schritte:

  1. Erstellen Sie ein Fenster, setzen Sie AllowsTransparency = "true" und fügen Sie ein Raster hinzu
  2. Setzen Sie die RenderTransform des Grids auf eine ScaleTransform mit Origin von 0,1
  3. Erstellen Sie im Raster eine Animation, die ScaleX 0 bis 1 animiert, und später die Deckkraft von 1 bis 0
  4. Berechnen Sie im Konstruktor Window.Top und Window.Left, um das Fenster in der rechten unteren Ecke des Bildschirms zu platzieren.

Das ist alles dazu.

Bei Verwendung von Expression Blend dauerte es etwa 8 Minuten, um den folgenden Arbeitscode zu generieren:

<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>

Mit Code hinter:

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

Da WPF eine der regulären .NET-Bibliotheken ist, ist die Antwort "Ja". Dies ist is möglich, dies mit den "regulären .NET-Bibliotheken" zu erreichen.

Wenn Sie fragen, ob es einen Weg gibt, dies ohne WPF zu tun, ist die Antwort immer noch ja, aber es ist äußerst komplex und dauert mehr als 5 Tage als 5 Minuten.

110
Ray Burns

Ich habe eine CodePlex-Site erstellt, die "Toast Popups" und "Help Balloons" enthält. Diese Versionen verfügen über mehr Funktionen als unten beschrieben. https://toastspopuphelpballoon.codeplex.com .

Dies war ein großartiger Ausgangspunkt für die Lösung, nach der ich gesucht hatte. Ich habe einige Änderungen vorgenommen, um meine Anforderungen zu erfüllen:

  • Ich wollte die Animation mit der Maus anhalten.
  • Animation "zurücksetzen", wenn die Maus verlassen wird.
  • Schließen Sie das Fenster, wenn die Deckkraft 0 erreicht hat.
  • Stapeln Sie den Toast (ich habe das Problem nicht gelöst, wenn die Anzahl der Fenster die Bildschirmhöhe überschreitet)
  • Call Load von meinem ViewModel

Hier ist mein 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>

Der Code dahinter

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

Der Aufruf vom ViewModel:

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

Die in der XAML referenzierten Styles:

     <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>

UPDATE: Ich habe diesen Ereignishandler hinzugefügt, wenn das Formular geschlossen wurde, um die anderen Fenster zu "löschen".

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

}

Der obige Code wurde in der Version @Ray Burns-Methode verfeinert. Mit Zeitintervallcode hinzugefügt. Das Benachrichtigungsfenster würde sich also nach 4 Sekunden schließen.

Rufe das Fenster als

NotificationWindow nfw = new NotificationWindow();
nfw.Show();
7
Anees Deen
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();
2
Mehul Sant

Ich habe die obige Antwort verwendet, um mein eigenes Benachrichtigungsfenster zu entwerfen, das meiner Meinung nach etwas benutzerfreundlicher ist und ein paar Techniken anwendet, die mich ein wenig brauchten, um herauszufinden. Teilen, falls es jemandem da draußen hilft .:

  1. Ein MouseEnter-Ereignisauslöser wurde hinzugefügt, um die Fensterdeckkraft sofort auf 1 festzulegen, sodass ein Benutzer nicht warten muss, bis das Fenster vollständig angezeigt wird.
  2. MouseLeave-Ereignisauslöser hinzugefügt, um die Fenster-Deckkraft auf 0 zu bringen, wenn der Benutzer die Maus aus dem Fenster bewegt.
  3. MouseUp (Mausklick) -Ereignisauslöser hinzugefügt, um die Fensteropazität sofort auf 0 zu setzen, um das Benachrichtigungsfenster auszublenden.
  4. Wenn Sie das Benachrichtigungsfenster mehrmals anzeigen () und ausblenden () müssen, setzt diese Methode auch das Storyboard am Ende zurück, so dass die nächste Show () -Operation den Vorgang von Anfang an startet und die Animation nicht stört.

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>

Code hinter:

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;
        }));
    }
}
0
user1367200

Beachten Sie, dass der aufrufende Thread sta sein muss, da dies für viele ui-Komponenten erforderlich ist, während der folgende Code unter dem abgelaufenen Ereignis system.timers.timer geschrieben wird 

Window1 notifyWin = new Window1();
bool? isOpen = notifyWin.ShowDialog();
if (isOpen != null && isOpen == true)
{
     notifyWin.Close();
}
System.Threading.Thread.Sleep(1000);
notifyWin.ShowDialog();

unter window1-Konstruktor:

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; 
    })); 
}
0
PKBhaiya