WPF Fade Out on a control

40,078

Solution 1

The problem is that after your animation completes your control still has Visibility=Visible, so it cannot be entered again.
I would rather use animation that does the whole thing, first shows the control, then hides it.

<Storyboard x:Key="animate">
    <ObjectAnimationUsingKeyFrames BeginTime="0:0:0" Storyboard.TargetProperty="Visibility">
        <DiscreteObjectKeyFrame KeyTime="0">
            <DiscreteObjectKeyFrame.Value>
                <Visibility>Visible</Visibility>
            </DiscreteObjectKeyFrame.Value>
        </DiscreteObjectKeyFrame>
    </ObjectAnimationUsingKeyFrames>
    <DoubleAnimation BeginTime="0:0:0.0" Storyboard.TargetProperty="Opacity" From="0" To="1" Duration="0:0:0.2"/>
    <DoubleAnimation BeginTime="0:0:5.0" Storyboard.TargetProperty="Opacity" From="1" To="0" Duration="0:0:0.5"/>
    <ObjectAnimationUsingKeyFrames BeginTime="0:0:5.5" Storyboard.TargetProperty="Visibility">
        <DiscreteObjectKeyFrame KeyTime="0">
            <DiscreteObjectKeyFrame.Value>
                <Visibility>Hidden</Visibility>
            </DiscreteObjectKeyFrame.Value>
        </DiscreteObjectKeyFrame>
    </ObjectAnimationUsingKeyFrames>
</Storyboard>

And use it as follows:

((Storyboard)FindResource("animate")).Begin(someControl);

Solution 2

Liz is correct about Visibility still being Visible. alpha-mouse is also correct that you need to set it back to Hidden at some point. But it won't work if you set it back before the animation is completed like this:

MyControl.Visibility = System.Windows.Visibility.Visible;
MyControl.Visibility = System.Windows.Visibility.Hidden;

because animations take higher precedence (MSDN)

You can set it back to Hidden in Storyboard.Completed event:

private void Show()
    {
        MyControl.Visibility = System.Windows.Visibility.Visible;

        var a = new DoubleAnimation
                    {
                        From = 1.0,
                        To = 0.0,
                        FillBehavior= FillBehavior.Stop,
                        BeginTime = TimeSpan.FromSeconds(2),
                        Duration = new Duration(TimeSpan.FromSeconds(0.5))
                    };
        var storyboard = new Storyboard();

        storyboard.Children.Add(a);
        Storyboard.SetTarget(a, MyControl);
        Storyboard.SetTargetProperty(a, new PropertyPath(OpacityProperty));
        storyboard.Completed += delegate { MyControl.Visibility = System.Windows.Visibility.Hidden; };
        storyboard.Begin();            
    }

Solution 3

Here is my work around. This fades a control in and back out again. Instead of playing around with the Visibility, I handled it by playing only with the Opacity.

Thanks to Kane from this post for the orginal code: Fade any control using a WPF animation

Storyboard storyboard = new Storyboard();
TimeSpan duration = TimeSpan.FromMilliseconds(500); //

DoubleAnimation fadeInAnimation = new DoubleAnimation() 
    { From = 0.0, To = 1.0, Duration = new Duration(duration) };

DoubleAnimation fadeOutAnimation = new DoubleAnimation()
    { From = 1.0, To = 0.0, Duration = new Duration(duration) };
fadeOutAnimation.BeginTime = TimeSpan.FromSeconds(5);

Storyboard.SetTargetName(fadeInAnimation, element.Name);
Storyboard.SetTargetProperty(fadeInAnimation, new PropertyPath("Opacity", 1));
storyboard.Children.Add(fadeInAnimation);
storyboard.Begin(element);

Storyboard.SetTargetName(fadeOutAnimation, element.Name);
Storyboard.SetTargetProperty(fadeOutAnimation, new PropertyPath("Opacity", 0));
storyboard.Children.Add(fadeOutAnimation);
storyboard.Begin(element);

Solution 4

My God that took forever. Take a look at this, it solves that problem of animating upon Visibility changes to 'Visible' and 'Hidden' using alpha and the animation will not freeze.

using System.Windows;

namespace WpfApplication4
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            button.Visibility = Visibility.Hidden;
        }

        private void button2_Click(object sender, RoutedEventArgs e)
        {
            button.Visibility = Visibility.Visible;
        }
    }
}

XAML:

<Window x:Class="WpfApplication4.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication4"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.Resources>

            <Style BasedOn="{StaticResource {x:Type Button}}" TargetType="{x:Type Button}">
                <Style.Resources>
                    <Storyboard x:Key="FadeOut">
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" FillBehavior="Stop">
                            <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{x:Static Visibility.Visible}"/>
                            <DiscreteObjectKeyFrame KeyTime="0:0:1" Value="{x:Static Visibility.Hidden}"/>
                        </ObjectAnimationUsingKeyFrames>
                        <DoubleAnimation Storyboard.TargetProperty="Opacity" From="1" To="0" Duration="0:0:1" AutoReverse="False" />
                    </Storyboard>
                    <Storyboard x:Key="FadeIn">
                        <DoubleAnimation Storyboard.TargetProperty="Opacity" From="0" To="1" Duration="0:0:1" AutoReverse="False" />
                    </Storyboard>
                </Style.Resources>
                <Setter Property="Width" Value="120"></Setter>
                <Style.Triggers>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="Visibility" Value="Hidden" />
                            <Condition Property="Opacity" Value="1" />
                        </MultiTrigger.Conditions>
                        <MultiTrigger.EnterActions>
                            <StopStoryboard BeginStoryboardName="FadeInStoryboard" />
                            <BeginStoryboard Name="FadeOutStoryboard" Storyboard="{StaticResource FadeOut}" />
                        </MultiTrigger.EnterActions>
                    </MultiTrigger>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="Visibility" Value="Visible" />
                            <Condition Property="Opacity" Value="0" />
                        </MultiTrigger.Conditions>
                        <MultiTrigger.EnterActions>
                            <StopStoryboard BeginStoryboardName="FadeOutStoryboard" />
                            <BeginStoryboard Name="FadeInStoryboard" Storyboard="{StaticResource FadeIn}" />
                        </MultiTrigger.EnterActions>
                    </MultiTrigger>
                </Style.Triggers>
            </Style>

        </Grid.Resources>
        <Button x:Name="button" Content="Button" HorizontalAlignment="Left" Margin="200,186,0,0" VerticalAlignment="Top" Width="75" Height="38" />
        <Button x:Name="button1" Content="Hide it" HorizontalAlignment="Left" Margin="112,96,0,0" VerticalAlignment="Top" Width="75" Click="button1_Click"/>
        <Button x:Name="button2" Content="Show it" HorizontalAlignment="Left" Margin="200,96,0,0" VerticalAlignment="Top" Width="75" Click="button2_Click"/>
        <Label x:Name="label" Content="{Binding ElementName=button, Path=Opacity}" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
        <Label x:Name="label1" Content="{Binding ElementName=button, Path=Visibility}" HorizontalAlignment="Left" Margin="10,36,0,0" VerticalAlignment="Top"/>

    </Grid>
</Window>

Solution 5

All the answers above use at least some sort of XAML code, which I personally am not that great of a fan of (because it's very confusing), so I found a way to do the same with some simple C# code:

int secs = 2; // How long the fade should take in seconds

for (int i = 99; i >= 0; i--)
{
    someControl.Opacity = i / 100d;

    await Task.Delay(secs * 10);
}

someControl.Visibility = Visibility.Hidden;

someControl.Opacity = 1;

You can use this on any Control. You also need to add the async modifier in your method signature. Without the await operator your UI couldn't respond while it's fading out the control.

After the control faded out, you can make it visible again like this:

someControl.Visibility = Visibility.Visible;

This method may not be the "best", but it is certainly the simplest and easiest to understand.

Share:
40,078
sondlerd
Author by

sondlerd

Rob Sondles is an independent software engineer operating under his company named Blueberry Island Media ltd. He started the company in 2003 and has been developing software with .Net since 2002. He is located in Wadsworth, OH.

Updated on April 15, 2021

Comments

  • sondlerd
    sondlerd about 3 years

    In my WPF app, I have a feedback control that I want to appear after a user action completes (save data, delete...). The visibility is set to Hidden to begin and style set to the animateFadeOut style defined as a resource (see below). Then I want to set the text and control Visibility to visible in my C# code and have the feedback control display the message and fade out after 5 seconds and remain hidden (Visibility.Hidden).

    The following XAML works the first time I call control.Visiblity= Visibility.Visible but the control doesn't reappear the second time. I figure that is because the animation is still running, which has control over the feedback control. I then tried to set FillBehavior to "Stop" but that just made the control visible again and I want it hidden. Then, with FillBehavior="Stop", I tried to set a trigger "when Opacity = 0, set the Visibility to Hidden". The trigger didn't seem to fire and I was left with the visible control once more after the animation completed.

    Please help point out what I am doing wrong here.

    Alternatively, if you can suggest a better way to display a control that fades after 5 seconds and can be called over and over, I would appreciate it.

    Thanks!

    <Style TargetType="{x:Type FrameworkElement}" x:Key="animateFadeOut">
            <Style.Triggers>
                <Trigger Property="Visibility" Value="Visible">
                    <Trigger.EnterActions>
                        <BeginStoryboard >
                            <Storyboard>
                                <DoubleAnimation BeginTime="0:0:5.0" Storyboard.TargetProperty="Opacity"
                             From="1.0" To="0.0" Duration="0:0:0.5"/>
                            </Storyboard>
                        </BeginStoryboard>             
                    </Trigger.EnterActions>
                </Trigger>
            </Style.Triggers> 
        </Style>
    
  • sondlerd
    sondlerd over 13 years
    thanks for the quick reply. can I set the Visibility to Hidden at the end of the storyboard to fix the issue you brought up, "your Visibility property is still set to Visible"?
  • sondlerd
    sondlerd over 13 years
    thanks, Alpha-Mouse. You answered the question I posted below to Liz. Another question, once the animation finishes, does the opacity=0.0 or is that only in the animation? I ask because when I set the FillBehavior to Stop, the opacity returned to 1.0.
  • sondlerd
    sondlerd over 13 years
    thanks Kai, I seem to keep coming back to C# to get things done in WPF. I'm relatively new to WPF (worked on it for 6 months in 2007 and then the past three weeks for a project). Maybe it is just the learning curve. Thanks for pointing out the storyboard.Completed event.
  • Liz
    Liz over 13 years
    No, I tried that, but you can't change the value of the property that serves as the condition for your trigger. But now you know how to stop your animation. :)
  • treehouse
    treehouse over 13 years
    @sondlerd: As long as it gets things done, I don't mind doing it in code. In fact, in this case I think doing in code is more clear than in xaml.
  • alpha-mouse
    alpha-mouse over 13 years
    @sondlerd: when FillBehaviour is Stop then yes, the value of the property returns to it's original value
  • Dragon
    Dragon about 9 years
    Nice example. I tried different solutions for fade in/out animation and this one is very simple and clear.
  • Itzalive
    Itzalive almost 8 years
    You can shorten this increase the efficiency by combining the two key frames into the same animation and defining the enum inline <DiscreteObjectKeyFrame KeyTime="0:0:6.5" Value="{x:Static Visibility.Hidden}"/>
  • dcreight
    dcreight almost 7 years
    When I began looking for this I found a lot of half answers and broken code. So I appreciate this elegant solution. Thanks :)