user1367200
user1367200

Reputation: 300

Show and Hide WPF window with slide animation multiple times while showing animation consistently each time

I have a WPF slide notification I want to show in the bottom right corner of the screen. I need to show and hide the notification multiple times from the main program, so I cannot Close() and Show() the window each time. I have to keep doing Hide() and Show().

IsHitTestVisible is set to False so that I can click anywhere on the window to dismiss and hide the notification and bring another window to the foreground.

The very first time I execute the window animation, it runs perfectly. If I let the window animation run all the way to the end, then the next time the slide notification is shown, the animation is displayed perfectly.

The problem comes in if I click on the window before the animation has run it's full course. Clicking on the window hides the window and then the storyboard is set to SkipStoryboardtoFill and FillBehavior is set to Stop so that the animation clocks get reset to their original values. However, the next time the hidden notification window is shown, it seems to remember the last window position the window had before it was hidden, and then starts the animation. So the animation looks really strange. I am trying to figure out how to set up the animation so that it displays the same way each time without this glitch.

<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="480"
Height="140"
WindowStyle="None"
AllowsTransparency="True"
Background="Transparent"
>

<Grid 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\resources\WindowsLogo-Blue-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="Windows 10 Upgrade" FontWeight="Bold" HorizontalContentAlignment="Center" Width="Auto" HorizontalAlignment="Stretch" IsHitTestVisible="False"/>

                <TextBox Name="TotastMessageTextBox" Margin="5" MaxWidth="340" Background="#333333" BorderThickness="0" IsReadOnly="True" Foreground="LightGray" FontSize="16" Text="A Windows upgrade is available. Click to upgrade or schedule update." HorizontalContentAlignment="Left" TextWrapping="Wrap" IsHitTestVisible="False"/>

            </StackPanel>

        </StackPanel>

    </Border>

    <Grid.Triggers>

        <EventTrigger RoutedEvent="FrameworkElement.Loaded">
            <BeginStoryboard Name="BeginToastAnimationStoryboard">
                <Storyboard Name="ToastAnimationStoryboard" FillBehavior="Stop">
                    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)" FillBehavior="Stop">
                        <SplineDoubleKeyFrame KeyTime="0:0:0" Value="0"/>
                        <SplineDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/>
                    </DoubleAnimationUsingKeyFrames>
                    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" FillBehavior="Stop">
                        <SplineDoubleKeyFrame KeyTime="0:0:18" Value="1"/>
                        <SplineDoubleKeyFrame KeyTime="0:0:20" Value="0"/>
                    </DoubleAnimationUsingKeyFrames>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>

        <EventTrigger RoutedEvent="FrameworkElement.MouseUp">
            <SkipStoryboardToFill BeginStoryboardName="BeginToastAnimationStoryboard"/>
            <RemoveStoryboard BeginStoryboardName="BeginToastAnimationStoryboard"/>
        </EventTrigger>

    </Grid.Triggers>

    <Grid.RenderTransform>
        <ScaleTransform ScaleY="1" />
    </Grid.RenderTransform>

</Grid>

With Code Behind:

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 - 10;
        this.Top = corner.Y - this.ActualHeight;
    }));
}
}

The first time the notification is shown, I use the Loaded event handler to call the Begin method with isControllable set to True. After that, I use the IsVisibleChanged event handler to start the animation if the Visibility property of the window is set to Visible.

Upvotes: 3

Views: 1794

Answers (2)

Phillip Ngan
Phillip Ngan

Reputation: 16106

Change the MouseUp EventTrigger to:

  1. Reset to the beginning of the animation, using SeekStoryboard
  2. Stop the animation, using PauseStoryboard. Using StopStoryboard run the animation to the fill state, which is not what you want: you want the window to disappear.

Here is the change in xaml:

        <EventTrigger RoutedEvent="FrameworkElement.MouseUp">
            <SeekStoryboard BeginStoryboardName="BeginToastAnimationStoryboard" />
            <PauseStoryboard BeginStoryboardName="BeginToastAnimationStoryboard" />
        </EventTrigger>

Resetting the animation ensures that when the animation is restarted it will start again from the beginning of the storyboard.

Upvotes: 1

Mihail Stancescu
Mihail Stancescu

Reputation: 4138

I would recommend to create a new instance for each notification. Let me explain why.

  1. You can configure them independently by using a configuration object, for example one can stay visible for 10 seconds, then the rest notifications with a default 5 seconds.
  2. You can show multiple notifications at once.
  3. Every notification being a window will start it's own event loop and thread.

For this to work however you must ensure that the window is very light and can be initialized very fast.

Upvotes: 1

Related Questions