views:

3303

answers:

4

Hi Folks,

I'm trying to use WPF animation to creat an effect where, when the data in a text property changes, the text fades out, then in again.. or preferably a proper crossfade.

I have successfully got half of this working, the code below responds to the text changed event, immediately makes the text invisible then fades it in over 3 seconds.

To fade text out is similarly simple, I just change the From and To properties of the tag. BUT - the problem is that the text on the screen changes immediately. This is usually absolutely required, of course, but in this case I want the OLD text to fade out, then the NEW text to fade in.

Is there any clever trick to doing this in WPF animation?

Current half-finished trigger:

<Style TargetType="TextBlock" x:Key="fadeinout">
        <Style.Triggers>
            <EventTrigger RoutedEvent="Binding.TargetUpdated">
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation Storyboard.TargetProperty="Opacity" Duration="0:0:3" From="0.0" To="1.0" BeginTime="0:0:0" />
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Style.Triggers>
    </Style>
A: 

I don't think this is possibly in a XAML-only solution. The TextBlock doesn't know about 'old' and 'new' text, only Text.

The way I would do this is to create an custom control that derived from TextBlock, and do the following on the TargetUpdated-event:

  • Call 'Fade out' storyboard
  • Change Text
  • Call 'Fade in' storyboard

Good luck :)

A: 

The best solution for this task would be to use a "Transition Presenter". Transition presenter is a container for your control (can be TextBlock or whatever else) which reacts to the change of the content by applying the assigned transition. You can select one of the pre-defined transitions or create your own (using XAML). Usually transition presenter uses a data template to display the bound data. Most basic example would look like this:

<lib:TransitionPresenter Transition="{StaticResource FadeTransition}
    Content="{Binding MyValue}">
    <lib:TransitionPresenter.Resources>
        <DataTemplate DataType="{x:Type System:string}">
            <TextBlock Text={Binding}/>
        </DataTemplate>
    </lib:TransitionPresenter.Resources>
</lib:TransitionPresenter>

Here are two libraries with source code that implement transition presenter:

Sergey Aldoukhov
A: 

I'm with frances1983 on this. What I would do is make a new UserControl that actually handles the old and new text seamlessly with the fades.

I'm doing something similar with a label I want to only display for a couple seconds and then disappear. Here's what I've done:

<Label  Name="lbl" DockPanel.Dock="Bottom" HorizontalAlignment="Center" Visibility="Collapsed">
    <Label.Style>
        <Style TargetType="{x:Type Label}">
            <Style.Triggers>
                <Trigger Property="Visibility" Value="Visible">
                    <Trigger.EnterActions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation Storyboard.TargetProperty="Opacity" Duration="00:00:00" BeginTime="00:00:00" From="0.0" To="1.0" />
                                <DoubleAnimation Storyboard.TargetProperty="Opacity" Duration="00:00:03" BeginTime="00:00:02" From="1.0" To="0.0" />
                            </Storyboard>
                        </BeginStoryboard>
                    </Trigger.EnterActions>
                </Trigger>
            </Style.Triggers>
        </Style>
    </Label.Style>
    Display Text
</Label>

And then in code where the label text changes:

//Make the label visible, starting the storyboard.
lbl.Visibility = Visibility.Visible;

DispatcherTimer t = new DispatcherTimer();
//Set the timer interval to the length of the animation.
t.Interval = new TimeSpan(0, 0, 5);
t.Tick += (EventHandler)delegate(object snd, EventArgs ea)
{
    // The animation will be over now, collapse the label.
    lbl.Visibility = Visibility.Collapsed;
    // Get rid of the timer.
    ((DispatcherTimer)snd).Stop();
};
t.Start();

You could easily modify this sample into a UserControl. Just change the fade out on the Visibility == Hidden, add a storyboard that does the opposite for the Visibility == Visible, and change the text and reset the visibility inside the Tick handler.

Hope this helps!

md5sum
+1  A: 

Here is an implementation that automatically do the fade-out,switch value, fade in

To use (after setting xmlns:l to the correct namespace:

Label l:AnimatedSwitch.Property="Content" l:AnimatedSwitch.Binding="{Binding SomeProp}"/>

The code (this is proof-of-concept code, without error handeling an is not production ready).

public class AnimatedSwitch : DependencyObject
{
    // Define the attached properties

    public static DependencyProperty BindingProperty =
        DependencyProperty.RegisterAttached("Binding", typeof(object), typeof(AnimatedSwitch),
        new PropertyMetadata(BindingChanged));
    public static DependencyProperty PropertyProperty =
        DependencyProperty.RegisterAttached("Property", typeof(string), typeof(AnimatedSwitch));
    public static object GetBinding(DependencyObject e)
    {
        return e.GetValue(BindingProperty);
    }
    public static void SetBinding(DependencyObject e, object value)
    {
        e.SetValue(BindingProperty, value);
    }
    public static string GetProperty(DependencyObject e)
    {
        return (string)e.GetValue(PropertyProperty);
    }
    public static void SetProperty(DependencyObject e, string value)
    {
        e.SetValue(PropertyProperty, value);
    }

    // When the value changes do the fadeout-switch-fadein

    private static void BindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        Storyboard fadeout = new Storyboard();
        var fadeoutAnim = new DoubleAnimation(){To=0,Duration=new Duration(TimeSpan.FromSeconds(0.3))};
        Storyboard.SetTarget(fadeoutAnim,d);
        Storyboard.SetTargetProperty(fadeoutAnim, new PropertyPath("Opacity"));
        fadeout.Children.Add(fadeoutAnim);
        fadeout.Completed += (d1, d2) =>
            {
                d.GetType().GetProperty(GetProperty(d)).SetValue(d, GetBinding(d), null);

                Storyboard fadein = new Storyboard();
                var fadeinAnim = new DoubleAnimation() { To = 1, Duration = new Duration(TimeSpan.FromSeconds(0.3)) };
                Storyboard.SetTarget(fadeinAnim, d);
                Storyboard.SetTargetProperty(fadeinAnim, new PropertyPath("Opacity"));
                fadein.Children.Add(fadeinAnim);
                fadein.Begin();
            };
        fadeout.Begin();
    }
}
Nir