2010-06-14 13 views
58

Estoy usando .NET y estoy creando una aplicación de escritorio/servicio que mostrará las notificaciones en la esquina de mi Escritorio cuando se desencadenen ciertos eventos. No quiero utilizar un cuadro de mensaje normal b/c que sería demasiado intrusivo. Quiero que las notificaciones se deslicen a la vista y luego desaparezcan después de unos segundos. Estoy pensando en algo que se parecerá mucho a las alertas de Outlook que uno recibe cuando llega un nuevo mensaje. La pregunta es: ¿Debería usar WPF para esto? Nunca he hecho nada con WPF, pero lo intentaré felizmente si es lo mejor para el final. ¿Hay alguna manera de lograr esto con las librerías .NET regulares?Crear notificaciones emergentes de "tostadora" en Windows con .NET

+1

Creo que de alguna manera es posible con .Net regular, Infragistics logró hacerlo: http://www.infragistics.com/dotnet/netadvantage/winforms/windesktopalert.aspx#Overview –

+1

Sí, eso es exactamente lo que quise decir. Gente, por favor comparte tus pensamientos. Solo busco una dirección general, no un procedimiento detallado. Gracias. – Antony

+0

aquí es uno que ha estado en codeProject por un tiempo. http://www.codeproject.com/KB/miscctrl/taskbarnotifier.aspx tomará menos de 5 días. – quimbo

Respuesta

102

WPF hace esto absolutamente trivial: Tomaría probablemente diez minutos o menos. Estos son los pasos:

  1. crear una ventana, establecer AllowsTransparency = "true" y añadir una rejilla para que
  2. Establecer RenderTransform de la cuadrícula a un ScaleTransform con origen de 0,1
  3. Crear una animación en la cuadrícula que anima a ScaleX 0 a 1 luego anima la opacidad de 1 a 0
  4. En el constructor, calcule Window.Top y Window.Left para colocar la ventana en la esquina inferior derecha de la pantalla.

Eso es todo.

Usando Expression Blend tardó aproximadamente 8 minutos mí para generar el siguiente código de trabajo:

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

Con código detrás:

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

Desde WPF es una de las bibliotecas .NET regulares, el la respuesta es sí, es es posible para lograr esto con las "bibliotecas .NET regulares".

Si está preguntando si hay una forma de hacerlo sin usar WPF, la respuesta es sí, pero es extremadamente compleja y tomará más de 5 días que 5 minutos.

+0

Esto es genial. Funciona de maravilla. ¡Gracias! Impresionante qué tan poco código tomó. – Antony

+0

+ ¡Genial, me has ahorrado un poco de tiempo también! Gracias) –

+17

No estoy seguro si esto es necesario, pero agregué un evento Completado a la animación de opacidad, y en el código detrás agregué "this.Close();". De lo contrario, la ventana siempre estaría abierta. Puede estropear una aplicación que se cierra en función del cierre de la última ventana. – Paul

0

Tenga en cuenta que el subproceso de llamada debe ser sta debido a que muchos componentes de interfaz de usuario requieren de este, mientras que la escritura siguiente código bajo System.Timers.Timer transcurrido evento

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

bajo window1 constructor:

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

} 

El código anterior es versión refinada @Ray Burns. Agregado con código de intervalo de tiempo. De manera que la ventana de notificación de cerraría después de 4 segundos ..

de llamadas La ventana como,

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

Aaahaaaaaaan. . – Gopichandar

+0

@Gopichandar No pude entender Gopi –

16

que siguió adelante y creó un sitio CodePlex para este que incluye "Toast Popups" y "control de globos de ayuda" . Estas versiones tienen más características que las que se describen a continuación. https://toastspopuphelpballoon.codeplex.com.

Este fue un gran punto de partida para la solución que estaba buscando.He hecho un par de modificaciones para cumplir con mis requisitos:

  • Quería detener la animación sobre el mouse.
  • Animación "Restablecer" cuando el mouse se va.
  • cerrar la ventana cuando la opacidad llegó a 0.
  • Pila del pan tostado (no he resuelto el problema si el número de ventanas excede la altura de la pantalla) Carga
  • llamada de mi modelo de vista

Aquí está mi 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> 

el código detrás

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

la llamada desde el ViewModel:

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

Los estilos referencia en el 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> 

UPDATE: añadí este controlador de eventos cuando se cierra el formulario a "drop "las otras ventanas".

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; 
       } 
      } 
     } 
    } 
+1

¿Cómo funciona NotificationWindowClosed? No se hace referencia en ningún otro lugar en el código – HoKy22

+0

. Disculpa. He agregado el código faltante. Este ha sido un trabajo en progreso, y lo eché de menos en la última actualización. – LawMan

+0

¡Funciona a la perfección! ¿Puede explicar un poco más sobre el significado de "Agregué este controlador de eventos cuando el formulario está cerrado para" soltar "las otras ventanas". ¿Qué quieres decir con "dejar caer"? – HoKy22

2
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(); 
Cuestiones relacionadas