WPF storyboard animation issue when using VisualBrush
- by Flack
Hey guys,
I was playing around with storyboards, a flipping animation, and visual brushes. I have encountered an issue though. Below is the xaml and code-behind of a small sample I quickly put together to try to demonstrate the problem.
When you first start the app, you are presented with a red square and two buttons. If you click the "Flip" button, the red square will "flip" over and a blue one will appear. In reality, all that is happening is that the scale of the width of the StackPanel that the red square is in is being decreased until it reaches zero and then the StackPanel where a blue square is, whose width is initially scaled to zero, has its width increased. If you click the "Flip" button a few times, the animation looks ok and smooth.
Now, if you hit the "Reflection" button, a reflection of the red/blue buttons is added to their respective StackPanels. Hitting the "Flip" button now will still cause the flip animation but it is no longer a smooth animation. The StackPanels width often does not shrink to zero. The width shrinks somewhat but then just stops before being completely invisible. Then the other StackPanel appears as usual. The only thing that changed was adding the reflection, which is just a VisualBrush.
Below is the code. Does anyone have any idea why the animations are different between the two cases (stalling in the second case)?
Thanks.
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xml:lang="en-US"
xmlns:d="http://schemas.microsoft.com/expression/blend/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
x:Class="WpfFlipTest.Window1"
x:Name="Window"
Title="Window1"
Width="214" Height="224">
<Window.Resources>
<Storyboard x:Key="sbFlip">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="redStack" Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleX)">
<SplineDoubleKeyFrame KeyTime="00:00:00.4" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00.4" Storyboard.TargetName="blueStack" Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleX)">
<SplineDoubleKeyFrame KeyTime="00:00:00.8" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="sbFlipBack">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="blueStack" Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleX)">
<SplineDoubleKeyFrame KeyTime="00:00:00.4" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00.4" Storyboard.TargetName="redStack" Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleX)">
<SplineDoubleKeyFrame KeyTime="00:00:00.8" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Grid x:Name="LayoutRoot" Background="Gray">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Name="redStack" Grid.Row="0" Grid.Column="0" RenderTransformOrigin="0.5,0.5">
<StackPanel.RenderTransform>
<ScaleTransform/>
</StackPanel.RenderTransform>
<Border Name="redBorder" BorderBrush="Transparent" BorderThickness="4" Width="Auto" Height="Auto">
<Button Margin="0" Name="redButton" Height="75" Background="Red" Width="105" />
</Border>
<Border Width="{Binding ElementName=redBorder, Path=ActualWidth}"
Height="{Binding ElementName=redBorder, Path=ActualHeight}"
Opacity="0.2" BorderBrush="Transparent" BorderThickness="4" Name="redRefelction" Visibility="Collapsed">
<Border.OpacityMask>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Black"/>
<GradientStop Offset=".6" Color="Transparent"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Border.OpacityMask>
<Border.Background>
<VisualBrush Visual="{Binding ElementName=redButton}">
<VisualBrush.Transform>
<ScaleTransform ScaleX="1" ScaleY="-1"
CenterX="52.5"
CenterY="37.5" />
</VisualBrush.Transform>
</VisualBrush>
</Border.Background>
</Border>
</StackPanel>
<StackPanel Name="blueStack" Grid.Row="0" Grid.Column="0" RenderTransformOrigin="0.5,0.5">
<StackPanel.RenderTransform>
<ScaleTransform ScaleX="0"/>
</StackPanel.RenderTransform>
<Border Name="blueBorder" BorderBrush="Transparent" BorderThickness="4" Width="Auto" Height="Auto">
<Button Grid.Row="0" Grid.Column="1" Margin="0" Width="105" Background="Blue" Name="blueButton" Height="75"/>
</Border>
<Border Width="{Binding ElementName=blueBorder, Path=ActualWidth}"
Height="{Binding ElementName=blueBorder, Path=ActualHeight}"
Opacity="0.2" BorderBrush="Transparent" BorderThickness="4" Name="blueRefelction" Visibility="Collapsed">
<Border.OpacityMask>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Black"/>
<GradientStop Offset=".6" Color="Transparent"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Border.OpacityMask>
<Border.Background>
<VisualBrush Visual="{Binding ElementName=blueButton}">
<VisualBrush.Transform>
<ScaleTransform ScaleX="1" ScaleY="-1"
CenterX="52.5"
CenterY="37.5" />
</VisualBrush.Transform>
</VisualBrush>
</Border.Background>
</Border>
</StackPanel>
<Button Grid.Row="1" Click="FlipButton_Click" Height="19.45" HorizontalAlignment="Left" VerticalAlignment="Top" Width="76">Flip</Button>
<Button Grid.Row="0" Grid.Column="1" Click="ReflectionButton_Click" Height="19.45" HorizontalAlignment="Left" VerticalAlignment="Top" Width="76">Reflection</Button>
</Grid>
</Window>
Here are the button click handlers:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Media.Animation;
namespace WpfFlipTest
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
bool flipped = false;
private void FlipButton_Click(object sender, RoutedEventArgs e)
{
Storyboard sbFlip = (Storyboard)Resources["sbFlip"];
Storyboard sbFlipBack = (Storyboard)Resources["sbFlipBack"];
if (flipped)
{
sbFlipBack.Begin();
flipped = false;
}
else
{
sbFlip.Begin();
flipped = true;
}
}
bool reflection = false;
private void ReflectionButton_Click(object sender, RoutedEventArgs e)
{
if (reflection)
{
reflection = false;
redRefelction.Visibility = Visibility.Collapsed;
blueRefelction.Visibility = Visibility.Collapsed;
}
else
{
reflection = true;
redRefelction.Visibility = Visibility.Visible;
blueRefelction.Visibility = Visibility.Visible;
}
}
}
}
UPDATE:
I have been testing this some more to try to find out what is causing the issue I am seeing and I believe I found what is causing the issue.
Below I have pasted new xaml and code-behind. The new sample below is very similar to the original sample, with a few minor modifications. The xaml basically consists of two stack panels, each containing two borders. The second border in each stack panel is a visual brush (a reflection of the border above it). Now, when I click the "Flip" button, one stack panel gets its ScaleX reduced to zero, while the second stack panel, whose initial ScaleX is zero, gets its ScaleX increased to 1. This animation gives the illusion of flipping. There are also two textblocks which display the scale factor of each stack panel. I added those to try to diagnose my issue.
The issue is (as described in the oringal post) that the flipping animation is not smooth. Every time I hit the flip button, the animation starts but whenever the ScaleX factor gets to around .14 to .16, the animation looks like it stalls and the stack panels never have there ScaleX reduced to zero, so they never totally disappear. Now, the strange thing is that if I change the Width/Height properties of the "frontBorder" and "backBorder" borders defined below to use explict values instead of Auto, such as Width=105 and Height=75 (to match the button in the border) everything works fine. The animation stutters the first two or three times I run it but after that the flips are smooth and flawless. (BTW, when an animation is run for the first time, is there something going on in the background, some sort of initialization, that causes it to be a little slow the first time?)
Is it possible that the Auto Width/Height of the borders are causing the issue? I can reproduce it everytime but I am not sure why Auto Width/Height would be a problem.
Below is the sample. Thanks for the help.
<Window x:Class="FlipTest.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>
<Storyboard x:Key="sbFlip">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="front" Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleX)">
<SplineDoubleKeyFrame KeyTime="00:00:00.5" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00.5" Storyboard.TargetName="back" Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleX)">
<SplineDoubleKeyFrame KeyTime="00:00:00.5" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="sbFlipBack">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="back" Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleX)">
<SplineDoubleKeyFrame KeyTime="00:00:00.5" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00.5" Storyboard.TargetName="front" Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleX)">
<SplineDoubleKeyFrame KeyTime="00:00:00.5" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Grid x:Name="LayoutRoot" Background="White" ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel x:Name="front" RenderTransformOrigin="0.5,0.5">
<StackPanel.RenderTransform>
<ScaleTransform/>
</StackPanel.RenderTransform>
<Border Name="frontBorder" BorderBrush="Yellow" BorderThickness="2" Width="Auto" Height="Auto">
<Button Margin="0" Name="redButton" Height="75" Background="Red" Width="105" Click="FlipButton_Click"/>
</Border>
<Border Width="{Binding ElementName=frontBorder, Path=ActualWidth}"
Height="{Binding ElementName=frontBorder, Path=ActualHeight}"
Opacity="0.2" BorderBrush="Transparent">
<Border.OpacityMask>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Black"/>
<GradientStop Offset=".6" Color="Transparent"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Border.OpacityMask>
<Border.Background>
<VisualBrush Visual="{Binding ElementName=frontBorder}">
<VisualBrush.Transform>
<ScaleTransform ScaleX="1" ScaleY="-1"
CenterX="52.5"
CenterY="37.5" />
</VisualBrush.Transform>
</VisualBrush>
</Border.Background>
</Border>
</StackPanel>
<StackPanel x:Name="back" RenderTransformOrigin="0.5,0.5">
<StackPanel.RenderTransform>
<ScaleTransform ScaleX="0"/>
</StackPanel.RenderTransform>
<Border Name="backBorder" BorderBrush="Yellow" BorderThickness="2" Width="Auto" Height="Auto">
<Button Margin="0" Width="105" Background="Blue" Name="blueButton" Height="75" Click="FlipButton_Click"/>
</Border>
<Border Width="{Binding ElementName=backBorder, Path=ActualWidth}"
Height="{Binding ElementName=backBorder, Path=ActualHeight}"
Opacity="0.2" BorderBrush="Transparent">
<Border.OpacityMask>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Black"/>
<GradientStop Offset=".6" Color="Transparent"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Border.OpacityMask>
<Border.Background>
<VisualBrush Visual="{Binding ElementName=backBorder}">
<VisualBrush.Transform>
<ScaleTransform ScaleX="1" ScaleY="-1"
CenterX="52.5"
CenterY="37.5" />
</VisualBrush.Transform>
</VisualBrush>
</Border.Background>
</Border>
</StackPanel>
<Button Grid.Row="1" Click="FlipButton_Click" Height="19.45" HorizontalAlignment="Left" VerticalAlignment="Top" Width="76">Flip</Button>
<TextBlock Grid.Row="2" Grid.Column="0" Foreground="DarkRed" Height="19.45" HorizontalAlignment="Left" VerticalAlignment="Top" Width="76" Text="{Binding ElementName=front, Path=(UIElement.RenderTransform).(ScaleTransform.ScaleX)}"/>
<TextBlock Grid.Row="3" Grid.Column="0" Foreground="DarkBlue" Height="19.45" HorizontalAlignment="Left" VerticalAlignment="Top" Width="76" Text="{Binding ElementName=back, Path=(UIElement.RenderTransform).(ScaleTransform.ScaleX)}"/>
</Grid>
</Window>
Code-behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Media.Animation;
namespace FlipTest
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
bool flipped = false;
private void FlipButton_Click(object sender, RoutedEventArgs e)
{
Storyboard sbFlip = (Storyboard)Resources["sbFlip"];
Storyboard sbFlipBack = (Storyboard)Resources["sbFlipBack"];
if (flipped)
{
sbFlipBack.Begin();
flipped = false;
}
else
{
sbFlip.Begin();
flipped = true;
}
}
}
}