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?
WPF macht das absolut trivial: Es würde vermutlich zehn Minuten oder weniger dauern. Hier sind die Schritte:
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.
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:
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;
}
}
}
}
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();
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();
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 .:
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;
}));
}
}
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;
}));
}