Tony Vitabile
Tony Vitabile

Reputation: 8614

WPF Keyframe animation was working but isn't right now

I have a WPF Custom Control I defined in a WPF Control Library project called Flasher. Basically, its a rectangle whose Fill property flashes back and forth between two colors, like a blinking light on a console. Here's the template for the control, which is in the Generic.xaml file for the library:

<Style TargetType="{x:Type local:Flasher}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:Flasher}">
                <Grid Name="LayoutRoot">
                    <Grid.Resources>
                        <Storyboard x:Key="FlashingStoryboard" AutoReverse="True" RepeatBehavior="Forever">
                            <ColorAnimationUsingKeyFrames BeginTime="00:00:00" 
                                                          Storyboard.TargetName="Flasher" 
                                                          Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                                <LinearColorKeyFrame KeyTime="00:00:00.5" 
                                                     Value="{Binding Path=FlashColor, RelativeSource={RelativeSource AncestorType={x:Type local:Flasher}}}"/>
                            </ColorAnimationUsingKeyFrames>
                            <DoubleAnimation Duration="00:00:00.05" 
                                             From="0" To="10" 
                                             Storyboard.TargetName="FlasherBlur" 
                                             Storyboard.TargetProperty="Radius">
                            </DoubleAnimation>
                        </Storyboard>
                    </Grid.Resources>

                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="FlashStates">
                            <VisualState x:Name="Flashing" Storyboard="{DynamicResource ResourceKey=FlashingStoryboard}"/>
                            <VisualState x:Name="Stopped"/>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>

                    <Rectangle Fill="{TemplateBinding Fill}" 
                               Name="Flasher" 
                               Stroke="{TemplateBinding Stroke}" 
                               StrokeThickness="{TemplateBinding StrokeThickness}">
                        <Rectangle.Effect>
                            <BlurEffect x:Name="FlasherBlur" Radius="0"  />
                        </Rectangle.Effect>
                    </Rectangle>

                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Here's the code-behind for the control:

public partial class Flasher : Control {

    public static readonly DependencyProperty FillProperty =
        DependencyProperty.Register( "Fill", typeof( Brush ), typeof( Flasher),
                                     new FrameworkPropertyMetadata ( new SolidColorBrush( Colors.Silver), FrameworkPropertyMetadataOptions.AffectsRender ) );

    public static readonly DependencyProperty FlashColorProperty =
        DependencyProperty.Register( "FlashColor", typeof( Color ), typeof( Flasher ),
                                     new FrameworkPropertyMetadata( Colors.Transparent, FrameworkPropertyMetadataOptions.AffectsRender ) );

    public static readonly DependencyProperty FlashDurationProperty =
        DependencyProperty.Register( "FlashDuration", typeof( TimeSpan ), typeof( Flasher ), new FrameworkPropertyMetadata( TimeSpan.MinValue ) );

    public static readonly DependencyProperty StrokeProperty =
        DependencyProperty.Register( "Stroke", typeof( Brush ), typeof( Flasher ),
                                     new FrameworkPropertyMetadata( new SolidColorBrush( Colors.Silver ), FrameworkPropertyMetadataOptions.AffectsRender ) );

    public static readonly DependencyProperty StrokeThicknessProperty =
        DependencyProperty.Register( "StrokeThickness", typeof( double ), typeof( Flasher ),
                                     new FrameworkPropertyMetadata( 0.0, FrameworkPropertyMetadataOptions.AffectsRender ) );

    protected Application App {
        get { return Application.Current; }
    }

    protected ILog Log {
        get { return (ILog) App.Properties[ "Log" ]; }
    }

    public Brush Fill {
        get { return (Brush) GetValue( FillProperty ); }
        set { SetValue( FillProperty, value ); }
    }

    public Color FlashColor {
        get { return (Color) GetValue( FlashColorProperty ); }
        set { SetValue( FlashColorProperty, value ); }
    }

    public TimeSpan FlashDuration {
        get { return (TimeSpan) GetValue( FlashDurationProperty ); }
        set { SetValue( FlashDurationProperty, value ); }
    }

    private bool flashing = false;

    public bool IsFlashing {
        get { return flashing; }
        set {
            flashing = value;
            FrameworkElement grid = Template.FindName( "LayoutRoot", this ) as FrameworkElement;
            if ( flashing ) {
                if ( !VisualStateManager.GoToElementState( grid, "Flashing", true ) ) {
                    Log.Debug( "Flasher.cs:  Visual State Manager transition failed." );
                }
                if ( FlashDuration > TimeSpan.MinValue ) {
                    ThreadPool.QueueUserWorkItem( WaitForDuration, FlashDuration );
                }
            } else {
                if ( !VisualStateManager.GoToElementState( grid, "Stopped", true ) ) {
                    Log.Debug( "Flasher.cs:  Visual State Manager transition failed." );
                }
            }
        }
    }

    public Brush Stroke {
        get { return (Brush) GetValue( StrokeProperty ); }
        set { SetValue( StrokeProperty, value ); }
    }

    public double StrokeThickness {
        get { return (double) GetValue( StrokeThicknessProperty ); }
        set { SetValue( StrokeThicknessProperty, value ); }
    }

    public Flasher() : base() {}

    static Flasher() {
        DefaultStyleKeyProperty.OverrideMetadata( typeof( Flasher ), new FrameworkPropertyMetadata( typeof( Flasher ) ) );
    }

    private void TurnFlashingOff() {
        // Set IsFlashing to false
        IsFlashing = false;
    }

    private void WaitForDuration( object state ) {
        System.Threading.Thread.Sleep( (TimeSpan) state );

        Dispatcher.BeginInvoke( new Action( TurnFlashingOff ) );
    }
}

This was all working some months ago, but it's not working now. That is, I used to see the flasher change colors between the two colors I had set in the window that uses the control. I've set breakpoints in the IsFlashing setter and I know that the FindName call is returning the Grid, and I know that the VisualStateManager calls work, so I don't understand why I'm not seeing the colors change. This has me quite baffled.

Plus Snoop can't seem to find the Window that is having the problem. It's not the main window of my application but a modeless pop-up. Essentially, the window with the problem descends from Window and is created and displayed with the following code:

if ( Window == null ) {
    Window = new MyDialog();
    // Set some program-specific window properties that don't affect the display here . . .
    Window.Show();
}

So Snoop has been useless.

If there are no glaring errors in the code I've published, then I'll have to look elsewhere in my code for the issue.

Thanks for any help you can give.

Tony

Upvotes: 0

Views: 811

Answers (1)

Tony Vitabile
Tony Vitabile

Reputation: 8614

I have found the solution to the problem by comparing the xaml code to an earlier version from a time when I knew it was working. It turns out that at some point I had changed the StoryBoard tag in the Visual State Manager's Flashing state to a DynamicResource; when it worked, I had it set to a StaticResource. Changing it back to a StaticResource got it working again.

Upvotes: 1

Related Questions