0
votes

I'm trying to create a marching dot animation where there's a series of small circles and one will grow a bit and change color, then the next will, and so on, and this will bounce back and forth. The animation will be triggered by a bool value in the ViewModel.

I actually had this working before deciding to use the MVVM pattern, it was easy: I had the storyboard in the Page.Resources tags and called the animation from code behind on button click. Now I'm stuck with this decoupled, it seems no matter what I try there's always an error of some kind.

Here's the dots in xaml:

<Grid Grid.Row="4" Grid.Column="3" Grid.ColumnSpan="2" Margin="20">
            <Grid.RowDefinitions>
                <RowDefinition Height="2*"/>
                <RowDefinition Height="1*"/>
                <RowDefinition Height="2*"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>

            <Ellipse Name="Dot5" Grid.Row="1" Grid.Column="0" Style="{StaticResource ConnectionMeterOff}"/>
            <Ellipse Name="Dot4" Grid.Row="1" Grid.Column="1" Style="{StaticResource ConnectionMeterOff}"/>
            <Ellipse Name="Dot3" Grid.Row="1" Grid.Column="2" Style="{StaticResource ConnectionMeterOff}"/>
            <Ellipse Name="Dot2" Grid.Row="1" Grid.Column="3" Style="{StaticResource ConnectionMeterOff}"/>
            <Ellipse Name="Dot1" Grid.Row="1" Grid.Column="4" Style="{StaticResource ConnectionMeterOff}"/>

        </Grid>

And my current animation:

<DataTemplate x:Key="MarchingDots">
  <DataTemplate.Triggers>
    <DataTrigger Binding="{Binding IsConnecting}" Value="true">
      <DataTrigger.EnterActions>
        <BeginStoryboard Name="MarchingDotsAnimation">
          <Storyboard RepeatBehavior="Forever" AutoReverse="True">
            <DoubleAnimation Storyboard.TargetName="Dot1" BeginTime="0:0:0.1"
                             Storyboard.TargetProperty="Width" From="10" To="15" 
                             Duration="0:0:0.2" AutoReverse="True"/>
            <ColorAnimation Storyboard.TargetName="Dot1" BeginTime="0:0:0.1"
                            Storyboard.TargetProperty="(Ellipse.Fill).(SolidColorBrush.Color)"                   
                            From="#FF5B5B5B" To="#FF84161C" Duration="0:0:0.3" AutoReverse="True"/>

            <DoubleAnimation Storyboard.TargetName="Dot2" BeginTime="0:0:0.2" 
                             Storyboard.TargetProperty="Width" From="10" To="15" 
                             Duration="0:0:0.2" AutoReverse="True"/>
            <ColorAnimation Storyboard.TargetName="Dot2" BeginTime="0:0:0.2" 
                            Storyboard.TargetProperty="(Ellipse.Fill).(SolidColorBrush.Color)" 
                            From="#FF5B5B5B" To="#FF84161C" Duration="0:0:0.3" AutoReverse="True"/>

            <DoubleAnimation Storyboard.TargetName="Dot3" BeginTime="0:0:0.3" 
                             Storyboard.TargetProperty="Width" From="10" To="15" 
                             Duration="0:0:0.2" AutoReverse="True"/>
            <ColorAnimation Storyboard.TargetName="Dot3" BeginTime="0:0:0.3" 
                            Storyboard.TargetProperty="(Ellipse.Fill).(SolidColorBrush.Color)" 
                            From="#FF5B5B5B" To="#FF84161C" Duration="0:0:0.3" AutoReverse="True"/>

            <DoubleAnimation Storyboard.TargetName="Dot4" BeginTime="0:0:0.4" 
                             Storyboard.TargetProperty="Width" From="10" To="15" 
                             Duration="0:0:0.2" AutoReverse="True"/>
            <ColorAnimation Storyboard.TargetName="Dot4" BeginTime="0:0:0.4" 
                            Storyboard.TargetProperty="(Ellipse.Fill).(SolidColorBrush.Color)" 
                            From="#FF5B5B5B" To="#FF84161C" Duration="0:0:0.3" AutoReverse="True"/>

            <DoubleAnimation Storyboard.TargetName="Dot5" BeginTime="0:0:0.5" 
                             Storyboard.TargetProperty="Width" From="10" To="15" 
                             Duration="0:0:0.2" AutoReverse="True"/>
            <ColorAnimation Storyboard.TargetName="Dot5" BeginTime="0:0:0.5" 
                            Storyboard.TargetProperty="(Ellipse.Fill).(SolidColorBrush.Color)" 
                            From="#FF5B5B5B" To="#FF84161C" Duration="0:0:0.3" AutoReverse="True"/>
          </Storyboard>
        </BeginStoryboard>
      </DataTrigger.EnterActions>
    </DataTrigger>
  </DataTemplate.Triggers>
</DataTemplate>

I'm pretty sure my issue isn't the binding, but either that the animation isn't attached to the dots, or how I'm trying to wrap it to get access to the triggers. If I keep it just a Storyboard like before, I don't have anything to trigger it. If I try a style, it complains that I can use "Storyboard.TargetName". I've also tried EventTriggers. And copious amounts of Googling...all the examples seem to be single elements (like a rectangle) where the storyboard can be attached to the element's style.

I'm sure this is simple, but what am I doing wrong?

1
Any particular reason you're using an EventTrigger? If you use one to start the anmimation then you're going to need another to stop it. The usual solution here is to make a looping animation and then just control its visibility with a boolean flag, would that work for you?Mark Feldman
@MarkFeldman: Well I want to show the "off dots" as a visual representation of a USB cable and then when the connection button is pressed the dots will first march away from the host then back. I'm trying to trigger that with a bool IsConnecting in the viewModel, so provided we can trigger the animation's visibility that same way, why did they make it tricky for the start/stop? Also, I worry with it running the whole time we're just eating resources we don't need.liquidair

1 Answers

1
votes

This needs a bit of cleanup, but it will get you started. First declare your animations:

<Storyboard x:Key="Storyboard1" RepeatBehavior="Forever">
    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
        <EasingDoubleKeyFrame KeyTime="0:0:0" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:0.25" Value="1.15"/>
        <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:1.5" Value="1"/>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
        <EasingDoubleKeyFrame KeyTime="0:0:0" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:0.25" Value="1.15"/>
        <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:1.5" Value="1"/>
    </DoubleAnimationUsingKeyFrames>
</Storyboard>

<Storyboard x:Key="Storyboard2" RepeatBehavior="Forever">
    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
        <EasingDoubleKeyFrame KeyTime="0:0:0" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:0.25" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="1.15"/>
        <EasingDoubleKeyFrame KeyTime="0:0:0.75" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:1.5" Value="1"/>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
        <EasingDoubleKeyFrame KeyTime="0:0:0" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:0.25" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="1.15"/>
        <EasingDoubleKeyFrame KeyTime="0:0:0.75" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:1.5" Value="1"/>
    </DoubleAnimationUsingKeyFrames>
</Storyboard>

<Storyboard x:Key="Storyboard3" RepeatBehavior="Forever">
    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
        <EasingDoubleKeyFrame KeyTime="0:0:0" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:0.75" Value="1.15"/>
        <EasingDoubleKeyFrame KeyTime="0:0:1.0" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:1.5" Value="1"/>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
        <EasingDoubleKeyFrame KeyTime="0:0:0" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:0.75" Value="1.15"/>
        <EasingDoubleKeyFrame KeyTime="0:0:1.0" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:1.5" Value="1"/>
    </DoubleAnimationUsingKeyFrames>
</Storyboard>

<Storyboard x:Key="Storyboard4" RepeatBehavior="Forever">
    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
        <EasingDoubleKeyFrame KeyTime="0:0:0" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:0.75" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:1.0" Value="1.15"/>
        <EasingDoubleKeyFrame KeyTime="0:0:1.25" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:1.5" Value="1"/>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
        <EasingDoubleKeyFrame KeyTime="0:0:0" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:0.75" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:1.0" Value="1.15"/>
        <EasingDoubleKeyFrame KeyTime="0:0:1.25" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:1.5" Value="1"/>
    </DoubleAnimationUsingKeyFrames>
</Storyboard>

<Storyboard x:Key="Storyboard5" RepeatBehavior="Forever">
    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
        <EasingDoubleKeyFrame KeyTime="0:0:0" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:1.0" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:1.25" Value="1.15"/>
        <EasingDoubleKeyFrame KeyTime="0:0:1.5" Value="1"/>
    </DoubleAnimationUsingKeyFrames>
    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
        <EasingDoubleKeyFrame KeyTime="0:0:0" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:1.0" Value="1"/>
        <EasingDoubleKeyFrame KeyTime="0:0:1.25" Value="1.15"/>
        <EasingDoubleKeyFrame KeyTime="0:0:1.5" Value="1"/>
    </DoubleAnimationUsingKeyFrames>
</Storyboard>

I'm going to build it out of ellipses, so create a base style for them:

<Style x:Key="EllipseStyle" TargetType="Ellipse">
    <Setter Property="Fill" Value="Black" />
    <Setter Property="Width" Value="32" />
    <Setter Property="Height" Value="32" />
    <Setter Property="RenderTransformOrigin" Value="0.5,0.5" />
    <Setter Property="Margin" Value="10" />
    <Setter Property="RenderTransform">
        <Setter.Value>
            <TransformGroup>
                <ScaleTransform/>
                <SkewTransform/>
                <RotateTransform/>
                <TranslateTransform/>
            </TransformGroup>
        </Setter.Value>
    </Setter>
</Style>

Then create five ellipses, assign each one it's corresponding storyboard and use a DataTrigger to turn the animation on and off:

    <UniformGrid Columns="5">

        <Ellipse>
            <Ellipse.Style>
                <Style TargetType="Ellipse" BasedOn="{StaticResource EllipseStyle}">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding Animating}" Value="True">
                            <DataTrigger.EnterActions>
                                <BeginStoryboard Name="Storyboard1" Storyboard="{StaticResource Storyboard1}" />
                            </DataTrigger.EnterActions>
                            <DataTrigger.ExitActions>
                                <StopStoryboard BeginStoryboardName="Storyboard1" />
                            </DataTrigger.ExitActions>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Ellipse.Style>
        </Ellipse>

        <Ellipse>
            <Ellipse.Style>
                <Style TargetType="Ellipse" BasedOn="{StaticResource EllipseStyle}">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding Animating}" Value="True">
                            <DataTrigger.EnterActions>
                                <BeginStoryboard Name="Storyboard2" Storyboard="{StaticResource Storyboard2}" />
                            </DataTrigger.EnterActions>
                            <DataTrigger.ExitActions>
                                <StopStoryboard BeginStoryboardName="Storyboard2" />
                            </DataTrigger.ExitActions>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Ellipse.Style>
        </Ellipse>

        <Ellipse>
            <Ellipse.Style>
                <Style TargetType="Ellipse" BasedOn="{StaticResource EllipseStyle}">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding Animating}" Value="True">
                            <DataTrigger.EnterActions>
                                <BeginStoryboard Name="Storyboard3" Storyboard="{StaticResource Storyboard3}" />
                            </DataTrigger.EnterActions>
                            <DataTrigger.ExitActions>
                                <StopStoryboard BeginStoryboardName="Storyboard3" />
                            </DataTrigger.ExitActions>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Ellipse.Style>
        </Ellipse>

        <Ellipse>
            <Ellipse.Style>
                <Style TargetType="Ellipse" BasedOn="{StaticResource EllipseStyle}">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding Animating}" Value="True">
                            <DataTrigger.EnterActions>
                                <BeginStoryboard Name="Storyboard4" Storyboard="{StaticResource Storyboard4}" />
                            </DataTrigger.EnterActions>
                            <DataTrigger.ExitActions>
                                <StopStoryboard BeginStoryboardName="Storyboard4" />
                            </DataTrigger.ExitActions>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Ellipse.Style>
        </Ellipse>

        <Ellipse>
            <Ellipse.Style>
                <Style TargetType="Ellipse" BasedOn="{StaticResource EllipseStyle}">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding Animating}" Value="True">
                            <DataTrigger.EnterActions>
                                <BeginStoryboard Name="Storyboard5" Storyboard="{StaticResource Storyboard5}" />
                            </DataTrigger.EnterActions>
                            <DataTrigger.ExitActions>
                                <StopStoryboard BeginStoryboardName="Storyboard5" />
                            </DataTrigger.ExitActions>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Ellipse.Style>
        </Ellipse>

    </UniformGrid>

    <CheckBox Content="Animate" IsChecked="{Binding Animating}" HorizontalAlignment="Center"/>
</StackPanel>

I'm assuming there's a property in your view model called Animating that you're using to control this, I've added a checkbox to this code as well for testing:

enter image description here

EDIT: just updated the animations to get rid of that flicker on the right.