Recently we've been building some cool stuff in WPF with resources and animations. In this post I will share some experience with a bug from our code base I fought recently. The actual code is complex, so here is a simple example that I used when hunting the nasty bug (source code is in the bottom of the page):

<Window x:Class="WpfApplication1.Window1"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   Title="Window1" Height="300" Width="300">

    <Window.Resources>

        <Style TargetType="{x:Type Rectangle}" x:Key="RectangleStyle">

            <Setter Property="Fill" Value="Blue" />

        </Style>

    </Window.Resources>

    <Grid Name="testGrid" >

        <Rectangle x:Name="rectangle" Margin="100" Style="{DynamicResource RectangleStyle}">

            <Rectangle.Triggers>

                <EventTrigger RoutedEvent="Mouse.MouseLeave">

                    <BeginStoryboard>

                        <Storyboard>

                            <ColorAnimation Duration="00:00:00.5" From="Blue" To="Red"

                                   Storyboard.TargetProperty="(Rectangle.Fill).(SolidColorBrush.Color)"

                                   AutoReverse = "True"/>

                        </Storyboard>

                    </BeginStoryboard>

                </EventTrigger>

            </Rectangle.Triggers>

        </Rectangle>

    </Grid>

</Window>

 

What we have here - a very simple windows with a Grid and a Rectangle in it. The important things here are:

  • The Rectangle has a style set through a DynamicResource. The style sets the Fill property.
  • We have an event trigger for the MouseLeave event. On that trigger we start a color animation that changes the Fill property of the Rectangle.

 

Everything seems perfectly normal for now, right? Ok, let's add some C# to the picture:

        protected override void OnKeyDown( KeyEventArgs e )

        {

            base.OnKeyDown( e );

            if ( testGrid.Children.Contains( rectangle ) )

            {

                testGrid.Children.Remove( rectangle );

            }

            else

            {

                testGrid.Children.Add( rectangle );

            }

        }

 

The code is very simple - we just remove the Rectangle from the Grid's children if it is there, and add it back if it is already removed. And... we are ready. Let's see the bug - start the project, hold any key (space is good candidate) and in the same time move the mouse over the Rectangle. BOOM - exception :) It says: "System.InvalidOperationException: 'Fill' property does not point to a DependencyObject in path '(0).(1)'." So where exactly is the problem? To answer the question I will explain when exactly the exception is thrown:

1. The mouse leaves the Rectangle and the animation changing the Fill color is started.

2. While the animation is working the Rectangle is removed from the Grid's logical tree.

3. When the Rectangle is removed, the style is applied again (because it is a DynamicResource). The DynamicResource starts searching up the logical tree, but as the Rectangle has already been removed from the Grid, the RectangleStyle is not found, it is not applied and the Fill property is not set.

4. In the same time the animation keeps working and tries to access Fill property as a SolidColorBrush and... the Exception.

 

So how can we fix that? I can propose a few solutions, but I am sure you can think of even more:

1. Replace the DynamicResource with StaticResource. The static resource is applied statically at load time and is not updated, so this completely solves the problem. Actually, this is the preferred approach if you do not need to update the style dynamically.

2. If you need the functionality of DynamicResource - move the animation in the style. When the Rectangle's parent is changed the style is unset, and because the animation is in the style it is removed as well.

 

So that's it - the bug was discovered and smashed. Any comments/ideas are highly appreciated.

Here is the sample code.

Happy coding!


Related Posts

Comments

Comments are disabled in preview mode.