none
Two Animations in Two DataTriggers

    Question

  • So I'm writing the "Game of Life" in WPF and as a cell comes alive I want it to look like it is growing and when it dies I want it to shrink into nothing. Each "cell" is represented by an ellipse and each ellipse shares the same style. The the problem is that the only animation that will play is the one in the first DataTrigger, meaning it will only shrink or only grow depending on which one is first. Everything works fine if I just use setters, but I'd really like to have that second animation fire. I've looked into modifying FillBehavior on the storyboard to no avail. Any help would be greatly appreciated


    <Style x:Key="lifeStyle" TargetType="{x:Type Ellipse}">
      <Style.Triggers>
        <DataTrigger Binding="{Binding IsAlive}" Value="True" >
          <DataTrigger.EnterActions>
            <BeginStoryboard>
              <Storyboard>
                <DoubleAnimation Storyboard.TargetProperty="Width"
                                 From="0" To="10" Duration="0:0:1"
                                 />
                <DoubleAnimation Storyboard.TargetProperty="Height"
                                 From="0" To="10" Duration="0:0:1"
                                 />
                <!--<ColorAnimation Storyboard.TargetProperty="Fill.Color"
                                 From="Blue" To="Red" Duration="0:0:1"></ColorAnimation>-->
              </Storyboard>
            </BeginStoryboard>
          </DataTrigger.EnterActions>
        </DataTrigger>


        <DataTrigger Binding="{Binding IsAlive}" Value="False" >
          <DataTrigger.EnterActions>
            <BeginStoryboard>
              <Storyboard>
                <DoubleAnimation Storyboard.TargetProperty="Width"
                                 From="10" To="0" Duration="0:0:1"
                                 />
                <DoubleAnimation Storyboard.TargetProperty="Height"
                                 From="10" To="0" Duration="0:0:1"
                                 />
                <!--<ColorAnimation Storyboard.TargetProperty="Fill.Color"
                                 From="Red" To="Blue" Duration="0:0:1"></ColorAnimation>-->
              </Storyboard>
            </BeginStoryboard>
          </DataTrigger.EnterActions>
        </DataTrigger>
      </Style.Triggers>
      <Setter Property="Fill" Value ="Blue" />
      <Setter Property="Width" Value ="0" />
      <Setter Property="Height" Value ="0" />
    </Style>

    Sunday, September 30, 2007 10:07 PM

Answers

  • You have competing triggers since both are triggering off of equivalent bindings.  In this case, the last one wins.

     

    The easy solution in your scenario (since you're triggering off of a bool) is to use the EnterActions to grow the item and the ExitActions to shrink it.  The following is an example that you can play with in XamlPad:

     

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

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

      <Grid>

        <Grid.Resources>

          <Style x:Key="lifeStyle" TargetType="{x:Type Ellipse}">

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

            <Setter Property="Width" Value ="0" />

            <Setter Property="Height" Value ="0" />

            <Style.Triggers>

              <DataTrigger Binding="{Binding ElementName=IsAlive,

                  Path=IsChecked}" Value="True" >

                <DataTrigger.EnterActions>

                  <BeginStoryboard>

                    <Storyboard>

                      <DoubleAnimation Storyboard.TargetProperty="Width"

                          To="10" Duration="0:0:0.4" DecelerationRatio="1" />

                      <DoubleAnimation Storyboard.TargetProperty="Height"

                          To="10" Duration="0:0:0.4" DecelerationRatio="1" />

                    </Storyboard>

                  </BeginStoryboard>

                </DataTrigger.EnterActions>

                <DataTrigger.ExitActions>

                  <BeginStoryboard>

                    <Storyboard>

                      <DoubleAnimation Storyboard.TargetProperty="Width"

                          From="10" Duration="0:0:0.4" DecelerationRatio="1" />

                      <DoubleAnimation Storyboard.TargetProperty="Height"

                          From="10" Duration="0:0:0.4" DecelerationRatio="1" />

                    </Storyboard>

                  </BeginStoryboard>

                </DataTrigger.ExitActions>

              </DataTrigger>

            </Style.Triggers>

          </Style>

        </Grid.Resources>

        <CheckBox Name="IsAlive">IsAlive</CheckBox>

        <Ellipse Style="{StaticResource lifeStyle}" />

      </Grid>

    </Page>

     

    If you don't mind additional advice, I would typically not use a width or height animation, as these will incur a layout pass for the parent of the ellipse on each frame of the animation.  A more performant approach is to animate a RenderTransform on the ellipse.  This transform is applied post-layout, so it is more lightweight and performant.

     

    Below is an example of this approach:

     

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

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

      <Grid>

        <Grid.Resources>

          <Style x:Key="lifeStyle" TargetType="{x:Type Ellipse}">

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

            <Setter Property="Width" Value ="10" />

            <Setter Property="Height" Value ="10" />

            <Setter Property="RenderTransformOrigin" Value="0.5,0.5" />

            <Setter Property="RenderTransform">

              <Setter.Value>

                <ScaleTransform ScaleX="0" ScaleY="0" />

              </Setter.Value>

            </Setter>

            <Style.Triggers>

              <DataTrigger Binding="{Binding ElementName=IsAlive,

                  Path=IsChecked}" Value="True" >

                <DataTrigger.EnterActions>

                  <BeginStoryboard>

                    <Storyboard>

                      <DoubleAnimation

                          Storyboard.TargetProperty="RenderTransform.ScaleX"

                          To="1" Duration="0:0:0.4" DecelerationRatio="1" />

                      <DoubleAnimation

                          Storyboard.TargetProperty="RenderTransform.ScaleY"

                          To="1" Duration="0:0:0.4" DecelerationRatio="1" />

                    </Storyboard>

                  </BeginStoryboard>

                </DataTrigger.EnterActions>

                <DataTrigger.ExitActions>

                  <BeginStoryboard>

                    <Storyboard>

                      <DoubleAnimation

                          Storyboard.TargetProperty="RenderTransform.ScaleX"

                          From="1" Duration="0:0:0.4" DecelerationRatio="1" />

                      <DoubleAnimation

                          Storyboard.TargetProperty="RenderTransform.ScaleY"

                          From="1" Duration="0:0:0.4" DecelerationRatio="1" />

                    </Storyboard>

                  </BeginStoryboard>

                </DataTrigger.ExitActions>

              </DataTrigger>

            </Style.Triggers>

          </Style>

        </Grid.Resources>

        <CheckBox Name="IsAlive">IsAlive</CheckBox>

        <Ellipse Style="{StaticResource lifeStyle}" />

      </Grid>

    </Page>

    Sunday, September 30, 2007 11:35 PM

All replies

  • You have competing triggers since both are triggering off of equivalent bindings.  In this case, the last one wins.

     

    The easy solution in your scenario (since you're triggering off of a bool) is to use the EnterActions to grow the item and the ExitActions to shrink it.  The following is an example that you can play with in XamlPad:

     

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

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

      <Grid>

        <Grid.Resources>

          <Style x:Key="lifeStyle" TargetType="{x:Type Ellipse}">

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

            <Setter Property="Width" Value ="0" />

            <Setter Property="Height" Value ="0" />

            <Style.Triggers>

              <DataTrigger Binding="{Binding ElementName=IsAlive,

                  Path=IsChecked}" Value="True" >

                <DataTrigger.EnterActions>

                  <BeginStoryboard>

                    <Storyboard>

                      <DoubleAnimation Storyboard.TargetProperty="Width"

                          To="10" Duration="0:0:0.4" DecelerationRatio="1" />

                      <DoubleAnimation Storyboard.TargetProperty="Height"

                          To="10" Duration="0:0:0.4" DecelerationRatio="1" />

                    </Storyboard>

                  </BeginStoryboard>

                </DataTrigger.EnterActions>

                <DataTrigger.ExitActions>

                  <BeginStoryboard>

                    <Storyboard>

                      <DoubleAnimation Storyboard.TargetProperty="Width"

                          From="10" Duration="0:0:0.4" DecelerationRatio="1" />

                      <DoubleAnimation Storyboard.TargetProperty="Height"

                          From="10" Duration="0:0:0.4" DecelerationRatio="1" />

                    </Storyboard>

                  </BeginStoryboard>

                </DataTrigger.ExitActions>

              </DataTrigger>

            </Style.Triggers>

          </Style>

        </Grid.Resources>

        <CheckBox Name="IsAlive">IsAlive</CheckBox>

        <Ellipse Style="{StaticResource lifeStyle}" />

      </Grid>

    </Page>

     

    If you don't mind additional advice, I would typically not use a width or height animation, as these will incur a layout pass for the parent of the ellipse on each frame of the animation.  A more performant approach is to animate a RenderTransform on the ellipse.  This transform is applied post-layout, so it is more lightweight and performant.

     

    Below is an example of this approach:

     

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

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

      <Grid>

        <Grid.Resources>

          <Style x:Key="lifeStyle" TargetType="{x:Type Ellipse}">

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

            <Setter Property="Width" Value ="10" />

            <Setter Property="Height" Value ="10" />

            <Setter Property="RenderTransformOrigin" Value="0.5,0.5" />

            <Setter Property="RenderTransform">

              <Setter.Value>

                <ScaleTransform ScaleX="0" ScaleY="0" />

              </Setter.Value>

            </Setter>

            <Style.Triggers>

              <DataTrigger Binding="{Binding ElementName=IsAlive,

                  Path=IsChecked}" Value="True" >

                <DataTrigger.EnterActions>

                  <BeginStoryboard>

                    <Storyboard>

                      <DoubleAnimation

                          Storyboard.TargetProperty="RenderTransform.ScaleX"

                          To="1" Duration="0:0:0.4" DecelerationRatio="1" />

                      <DoubleAnimation

                          Storyboard.TargetProperty="RenderTransform.ScaleY"

                          To="1" Duration="0:0:0.4" DecelerationRatio="1" />

                    </Storyboard>

                  </BeginStoryboard>

                </DataTrigger.EnterActions>

                <DataTrigger.ExitActions>

                  <BeginStoryboard>

                    <Storyboard>

                      <DoubleAnimation

                          Storyboard.TargetProperty="RenderTransform.ScaleX"

                          From="1" Duration="0:0:0.4" DecelerationRatio="1" />

                      <DoubleAnimation

                          Storyboard.TargetProperty="RenderTransform.ScaleY"

                          From="1" Duration="0:0:0.4" DecelerationRatio="1" />

                    </Storyboard>

                  </BeginStoryboard>

                </DataTrigger.ExitActions>

              </DataTrigger>

            </Style.Triggers>

          </Style>

        </Grid.Resources>

        <CheckBox Name="IsAlive">IsAlive</CheckBox>

        <Ellipse Style="{StaticResource lifeStyle}" />

      </Grid>

    </Page>

    Sunday, September 30, 2007 11:35 PM
  • To My Good Doctor: THANKS!

    It works like a champ, and thanks for the suggestion to use ScaleX/Y instead of width/height, its great to know things like that. Out of curiosity, if I was using an enum instead of a bool (pretend I have the states: CellState.Alive, CellState.Dead, CellState.Zombie and CellState.Limbo) Where I may want Zombie and Limbo to animate to different colors, any idea on how I could still do that? I really to appreciate your time, I assure you I've spent multiple hours scouring the web for something like this and I don't make this post willy-nilly. Like I said in my original post, setters on multiple data triggers (bound to the same property) work fine, it just the animations that don't work.

    Have a good one!
    Tuesday, October 02, 2007 2:34 AM
  • If you want to leverage multiple data triggers to begin storyboards that target the same property, then you must be more hands-on when it comes to the coordination of your storyboards.  The way you do this is by naming the BeginStoryboard actions (using the x:Name attribute).  This makes the storyboards controllable.  (It's the same as starting the storyboard in code and setting the isControllable parameter to true).

     

    Then, in each distinct trigger, you must stop all other storyboards that are acting on the target property prior to starting the new BeginStoryboard.

     

    Below is an updated sample that demonstrates this approach.  Note that I've made the checkbox a 3-state button and there are now 3 separate data triggers being used to watch the IsChecked property:

     

    Code Block

     

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

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

      <StackPanel>

        <StackPanel.Resources>

          <Style x:Key="lifeStyle" TargetType="{x:Type Ellipse}">

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

            <Setter Property="Width" Value ="10" />

            <Setter Property="Height" Value ="10" />

            <Setter Property="RenderTransformOrigin" Value="0.5,0.5" />

            <Setter Property="RenderTransform">

              <Setter.Value>

                <ScaleTransform ScaleX="0" ScaleY="0" />

              </Setter.Value>

            </Setter>

            <Style.Triggers>

              <DataTrigger Binding="{Binding ElementName=IsAlive,

                  Path=IsChecked}" Value="True" >

                <DataTrigger.EnterActions>

                  <StopStoryboard BeginStoryboardName="ScaleDown" />

                  <StopStoryboard BeginStoryboardName="Disappear" />

                  <BeginStoryboard x:Name="ScaleUp">

                    <Storyboard>

                      <DoubleAnimation

                          Storyboard.TargetProperty="RenderTransform.ScaleX"

                          To="1" Duration="0:0:0.4" DecelerationRatio="1" />

                      <DoubleAnimation

                          Storyboard.TargetProperty="RenderTransform.ScaleY"

                          To="1" Duration="0:0:0.4" DecelerationRatio="1" />

                    </Storyboard>

                  </BeginStoryboard>

                </DataTrigger.EnterActions>

              </DataTrigger>

              <DataTrigger Binding="{Binding ElementName=IsAlive,

                  Path=IsChecked}" Value="{x:Null}" >

                <DataTrigger.EnterActions>

                  <StopStoryboard BeginStoryboardName="ScaleUp" />

                  <StopStoryboard BeginStoryboardName="Disappear" />

                  <BeginStoryboard x:Name="ScaleDown">

                    <Storyboard>

                      <DoubleAnimation

                          Storyboard.TargetProperty="RenderTransform.ScaleX"

                          To="0.5" Duration="0:0:0.4" DecelerationRatio="1" />

                      <DoubleAnimation

                          Storyboard.TargetProperty="RenderTransform.ScaleY"

                          To="0.5" Duration="0:0:0.4" DecelerationRatio="1" />

                    </Storyboard>

                  </BeginStoryboard>

                </DataTrigger.EnterActions>

              </DataTrigger>

              <DataTrigger Binding="{Binding ElementName=IsAlive,

                  Path=IsChecked}" Value="False" >

                <DataTrigger.EnterActions>

                  <StopStoryboard BeginStoryboardName="ScaleUp" />

                  <StopStoryboard BeginStoryboardName="ScaleDown" />

                  <BeginStoryboard x:Name="Disappear">

                    <Storyboard>

                      <DoubleAnimation

                          Storyboard.TargetProperty="RenderTransform.ScaleX"

                          To="0" Duration="0:0:0.4" DecelerationRatio="1" />

                      <DoubleAnimation

                          Storyboard.TargetProperty="RenderTransform.ScaleY"

                          To="0" Duration="0:0:0.4" DecelerationRatio="1" />

                    </Storyboard>

                  </BeginStoryboard>

                </DataTrigger.EnterActions>

              </DataTrigger>

            </Style.Triggers>

          </Style>

        </StackPanel.Resources>

        <CheckBox IsThreeState="True" Name="IsAlive"

            IsChecked="{x:Null}" >IsAlive</CheckBox>

        <Ellipse Style="{StaticResource lifeStyle}"

            HorizontalAlignment="Left" />

      </StackPanel>

    </Page>

     

     

     

     

    Tuesday, October 02, 2007 3:20 AM
  • Thanks Again, Dr. WPF!
    Sunday, October 07, 2007 9:36 PM
  • Dr. WPF,

    Seems like this animation could use some improvement. (I am stuck with a similar issue for a while now). Please try these steps:

    1) Uncheck the checkbox. This will cause the ellipse to start slowly shrinking. Before it completely shrinks go to second step.
    2) Now Check the checkbox. Now the ellipse's scale with reset to 0, 0 and then start expanding to 1,1.

    Instead, as the state goes from unchecked to checked, I want the shrinking to stop, and from that "latest" position I want the ellipse to start expanding.

    All this can be visualized better if you increase the size and duration of the ellipse.

    Hope there is an easy way to accomplish this.
    Praveen
    Monday, July 14, 2008 8:03 PM
  •    I've been fighting with similar problem. Here's the testing code that I've been using (works in Kaxaml or XamlPad):

    <Page

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

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

    <Page.Resources>

    <Storyboard x:Key="first" Storyboard.TargetProperty="(Canvas.Top)">

    <DoubleAnimation To="100" Duration="0:0:2" />

    </Storyboard>

    <Storyboard x:Key="second" Storyboard.TargetProperty="(Canvas.Top)">

    <DoubleAnimation To="200" Duration="0:0:2" />

    </Storyboard>

    <Storyboard x:Key="third" Storyboard.TargetProperty="(Canvas.Top)">

    <DoubleAnimation To="300" Duration="0:0:2" />

    </Storyboard>

    <Storyboard x:Key="fourth" Storyboard.TargetProperty="(Canvas.Top)">

    <DoubleAnimation To="400" Duration="0:0:2" />

    </Storyboard>

    </Page.Resources>

    <Canvas>

    <Rectangle x:Name="rect1" Canvas.Left="10" Canvas.Top="10" Width="50" Height="50" Fill="Blue">

    <Rectangle.Style>

    <Style>

    <Style.Triggers>

    <DataTrigger Binding="{Binding ElementName=rect2, Path=IsMouseOver}" Value="True">

    <DataTrigger.EnterActions>

    <StopStoryboard BeginStoryboardName="second" />

    <StopStoryboard BeginStoryboardName="third" />

    <StopStoryboard BeginStoryboardName="fourth" />

    <BeginStoryboard x:Name="first">

    <StaticResource ResourceKey="first" />

    </BeginStoryboard>

    </DataTrigger.EnterActions>

    </DataTrigger>

    <DataTrigger Binding="{Binding ElementName=rect3, Path=IsMouseOver}" Value="True">

    <DataTrigger.EnterActions>

    <StopStoryboard BeginStoryboardName="first" />

    <StopStoryboard BeginStoryboardName="third" />

    <StopStoryboard BeginStoryboardName="fourth" />

    <BeginStoryboard x:Name="second">

    <StaticResource ResourceKey="second" />

    </BeginStoryboard>

    </DataTrigger.EnterActions>

    </DataTrigger>

    <DataTrigger Binding="{Binding ElementName=rect4, Path=IsMouseOver}" Value="True">

    <DataTrigger.EnterActions>

    <StopStoryboard BeginStoryboardName="first" />

    <StopStoryboard BeginStoryboardName="second" />

    <StopStoryboard BeginStoryboardName="fourth" />

    <BeginStoryboard x:Name="third">

    <StaticResource ResourceKey="third" />

    </BeginStoryboard>

    </DataTrigger.EnterActions>

    </DataTrigger>

    <DataTrigger Binding="{Binding ElementName=rect5, Path=IsMouseOver}" Value="True">

    <DataTrigger.EnterActions>

    <StopStoryboard BeginStoryboardName="first" />

    <StopStoryboard BeginStoryboardName="second" />

    <StopStoryboard BeginStoryboardName="third" />

    <BeginStoryboard x:Name="fourth">

    <StaticResource ResourceKey="fourth" />

    </BeginStoryboard>

    </DataTrigger.EnterActions>

    </DataTrigger>

    </Style.Triggers>

    </Style>

    </Rectangle.Style>

    </Rectangle>

    <Rectangle x:Name="rect2" Canvas.Left="500" Canvas.Top="50" Width="70" Height="70" Fill="Orange"/>

    <Rectangle x:Name="rect3" Canvas.Left="500" Canvas.Top="150" Width="70" Height="70" Fill="Orange"/>

    <Rectangle x:Name="rect4" Canvas.Left="500" Canvas.Top="250" Width="70" Height="70" Fill="Orange"/>

    <Rectangle x:Name="rect5" Canvas.Left="500" Canvas.Top="350" Width="70" Height="70" Fill="Orange"/>

    </Canvas>

    </Page>



    In the above code, if one takes mouse on orange rectangles one after another proceeding from top to bottom then animations proceed smoothly. This is also the order in which data triggers are defined in code. However, if one does not use the order of data triggers, then animations always start from beginning.
    Tuesday, July 15, 2008 8:02 AM