0

following situation: I got a splashscreen which has to fade in over 2 seconds (from transparent to opaque). While the window nicely fades in, the program should wait until it is fully visible (the Storyboard finished) and then it should continue doing it's stuff. While the window is fading in, the user should already see the animations of the control (so blocking the UI thread is not an option, of course). And with animations I mean the loading circle which spins happily.

After I found a nice variant of how to fade in and out a window with WPF's storyboards, I tried to accomplish this by using the EventWaitHandle. As my intialization routine already runs asynchronous I was able to use it. This blocked the worker thread and stopped my application doing the initialization stuff before the Splashscreen was fully visible. But somehow this has been broken after a while and it doesn't seem to be the best solution.

Here's how I'm doing it, currently:

public async Task Appear(double time)
{
    Core.IDE.GetGUICore().GetUIDispatcher().Invoke(() =>
    {
        this.Opacity = 0;
        this.Show();

        _fadeInStoryboard = new Storyboard();
        _fadeInStoryboard.Completed += this.FadeInAnimation;
        DoubleAnimation fadeInAnimation = new DoubleAnimation(0.0, 1.0, new Duration(TimeSpan.FromSeconds(time)));
        Storyboard.SetTarget(fadeInAnimation, this);
        Storyboard.SetTargetProperty(fadeInAnimation, new PropertyPath(OpacityProperty));
        _fadeInStoryboard.Children.Add(fadeInAnimation);
    });

    _currentHandle = new EventWaitHandle(false, EventResetMode.AutoReset);
    Core.IDE.GetGUICore()
        .GetUIDispatcher()
        .InvokeAsync(this._fadeInStoryboard.Begin, DispatcherPriority.Render);
    _currentHandle.WaitOne();
}

/// <summary>
/// Hides the SplashScreen with a fade-out animation.
/// </summary>
/// <param name="time">The fade-out time in seconds.</param>
public void Disappear(double time)
{
    Core.IDE.GetGUICore().GetUIDispatcher().Invoke(() =>
    {
        _fadeOutStoryboard = new Storyboard();
        _fadeOutStoryboard.Completed += this.FadeOutAnimation;
        DoubleAnimation fadeOutAnimation = new DoubleAnimation(1.0, 0.0,
            new Duration(TimeSpan.FromSeconds(time)));
        Storyboard.SetTarget(fadeOutAnimation, this);
        Storyboard.SetTargetProperty(fadeOutAnimation, new PropertyPath(OpacityProperty));
        _fadeOutStoryboard.Children.Add(fadeOutAnimation);
    });

    Core.IDE.GetGUICore()
       .GetUIDispatcher()
       .BeginInvoke(new Action(_fadeOutStoryboard.Begin), DispatcherPriority.Render, null);
}

private void FadeInAnimation(object sender, EventArgs e)
{
    _currentHandle.Set();
}

private void FadeOutAnimation(object sender, EventArgs e)
{
    this.Hide();
}

Is this the right approach? Any better solutions? Any ideas why it is broken? By the way, with broken I mean that the application continues doing its initialization stuff whilst the window is fading in, which ends in an animation which runs until it's probably at 30% visibility and then fading out because the main window already showed up.

Thanks in advance

daniele3004
  • 13,072
  • 12
  • 67
  • 75
SharpShade
  • 1,761
  • 2
  • 32
  • 45

1 Answers1

0

If you declare your Storyboard in XAML, it really will simplify things for you. Try using this Trigger in your splash screen control:

<Grid>
    <Grid.Triggers>
        <EventTrigger RoutedEvent="Loaded">
            <BeginStoryboard>
                <Storyboard Duration="0:0:5" Completed="Storyboard_Completed">
                    <DoubleAnimation From="0.0" To="1.0" 
                        Storyboard.TargetProperty="Opacity" />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Grid.Triggers>
    <!--Put your UI elements here-->
</Grid>

You can load the rest of your app from the Storyboard_Completed handler, or if this is in a separate Window, then you can raise some event or delegate from there, which you should handle in the MainWindow.

Sheridan
  • 68,826
  • 24
  • 143
  • 183
  • Thanks, but this does not work. In the class which initializes my application I want to call "Appear" or something similiar. Then the Splashscreen should fade in and meanwhile the initialization method should wait until it's fully visible. Then it should continue. I don't want to use an event. This would mean I must continue the initialization in the handler of this event, which makes the (already pretty long) initialization routine less readable. The approach I used already worked, but meanwhile it doesn't anymore... – SharpShade Jun 24 '14 at 19:35
  • Perhaps you should think about doing things differently. Take a look at my answer to the [How to open a child Window like a splash screen before MainWindow in WPF?](http://stackoverflow.com/questions/24299924/how-to-open-a-child-window-like-a-splash-screen-before-mainwindow-in-wpf/24302981#24302981) question. You could easily add your `Completed` handler to the `App.xaml.cs` file with the `App_Startup` method. – Sheridan Jun 24 '14 at 20:05
  • ... in place of the `DispatcherTimer` in that answer. – Sheridan Jun 24 '14 at 20:25