I want to animate the form o a circle into a square. The same way we did it in the flash animations time, but in WPF.
I'm open to any suggestion on how to approach it. I do have my path data for the final and initial shapes but I cannot find anything relevant.
My google search only go into the directions of animating a form along a path. This is not what I want, I want my object to stay stationary but change it outline from a circle to a square.
You could animate the RadiusX and RadiusY properties of a Rectangle like this:
<Rectangle Width="100" Height="100" RadiusX="50" RadiusY="50"
Stroke="Red" StrokeThickness="3">
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard BeginTime="0:0:2">
<DoubleAnimation
Storyboard.TargetProperty="RadiusX"
To="0" Duration="0:0:2"/>
<DoubleAnimation
Storyboard.TargetProperty="RadiusY"
To="0" Duration="0:0:2"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Rectangle.Triggers>
</Rectangle>
Alternatively, animate the CornerRadius of a Border with a custom animation:
<Border Width="100" Height="100" CornerRadius="50"
BorderBrush="Red" BorderThickness="3">
<Border.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard BeginTime="0:0:2">
<local:CornerRadiusAnimation
Storyboard.TargetProperty="CornerRadius"
To="0" Duration="0:0:2"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Border.Triggers>
</Border>
The CornerRadiusAnimation:
public class CornerRadiusAnimation : AnimationTimeline
{
public CornerRadius To { get; set; }
public override Type TargetPropertyType
{
get { return typeof(CornerRadius); }
}
protected override Freezable CreateInstanceCore()
{
return new CornerRadiusAnimation { To = To };
}
public override object GetCurrentValue(object defaultOriginValue, object defaultDestinationValue, AnimationClock animationClock)
{
if (!animationClock.CurrentProgress.HasValue)
{
return defaultOriginValue;
}
var p = animationClock.CurrentProgress.Value;
var from = (CornerRadius)defaultOriginValue;
return new CornerRadius(
(1 - p) * from.TopLeft + p * To.TopLeft,
(1 - p) * from.TopRight + p * To.TopRight,
(1 - p) * from.BottomRight + p * To.BottomRight,
(1 - p) * from.BottomLeft + p * To.BottomLeft);
}
}
Related
I am trying to change Background of a Border when user is dragging a file on it.
I want to define the effect using XAML only.
I tried the below but the Background is not changed when dragging a file on the Border.
<Border Name="dropBorder" BorderThickness="1" AllowDrop="True">
<Border.Triggers>
<EventTrigger RoutedEvent="DragOver">
<BeginStoryboard>
<Storyboard Storyboard.TargetProperty="Background">
<ColorAnimation From="Transparent" To="#FF444444" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Border.Triggers>
<TextBlock Text="Drag and drop file(s) here" Foreground="Gray" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="10"/>
</Border>
I also tried to use DragEnter as below with no results
<EventTrigger RoutedEvent="Border.DragEnter">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="dropBorder"
Storyboard.TargetProperty="Background"
Duration="0:0:0.5"
From="Transparent" To="#FF444444"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
I didnt quite meet your 100% requirement. I created an attached property, which I set via code-behind, so you will want to assess this. Also, moved the color animation around as you were trying to animate a brush, not a color.
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfApplication1="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<SolidColorBrush x:Key="SharedBackgroundBrush" Color="Transparent" />
</Window.Resources>
<Border Name="dropBorder" BorderThickness="1" AllowDrop="True" DragEnter="DropBorder_OnDragEnter" DragLeave="DropBorder_OnPreviewDragLeave" Background="{StaticResource SharedBackgroundBrush}">
<Border.Style>
<Style>
<Style.Triggers>
<Trigger Property="wpfApplication1:DragDropHelper.IsDragOver" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard Storyboard.Target="{StaticResource SharedBackgroundBrush}" Storyboard.TargetProperty="Color">
<ColorAnimation From="Transparent" To="Yellow" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard Storyboard.Target="{StaticResource SharedBackgroundBrush}" Storyboard.TargetProperty="Color">
<ColorAnimation From="Yellow" To="Transparent" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Text="Drag and drop file(s) here" Foreground="Gray" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="10"/>
</Border>
</Window>
Code:
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void DropBorder_OnDragEnter(object sender, DragEventArgs e)
{
DragDropHelper.SetIsDragOver((DependencyObject)sender, true);
}
private void DropBorder_OnPreviewDragLeave(object sender, DragEventArgs e)
{
DragDropHelper.SetIsDragOver((DependencyObject)sender, false);
}
}
public class DragDropHelper
{
public static readonly DependencyProperty IsDragOverProperty = DependencyProperty.RegisterAttached(
"IsDragOver", typeof (bool), typeof (DragDropHelper), new PropertyMetadata(default(bool)));
public static void SetIsDragOver(DependencyObject element, bool value)
{
element.SetValue(IsDragOverProperty, value);
}
public static bool GetIsDragOver(DependencyObject element)
{
return (bool) element.GetValue(IsDragOverProperty);
}
}
}
I'm yet again a bit stumped, and was hoping someone could please help.
Apologies in advance to the long code.
Issue - I have a DataTrigger that starts off firing as expected, but it eventually fails and I can't work out why. In the example provided, it moves a rectangle around a Canvas. Each click of the button moves it in this sequence; N, NE, E, SE, S, SW, W, NW. Then it starts the sequence again, starting with N. Once the first sequence is completed, it won't move North. It will only ever move NW again (ie the last successful move).
The property that triggers the DataTrigger is updating.
thanks
XAML;
<Window.Resources>
<Style x:Key="TestRectStyle" TargetType="{x:Type Rectangle}">
<Style.Triggers>
<DataTrigger Binding="{Binding UI_DirectionOfMovement}" Value="North">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Top)" By="-50" Duration="0:0:0.8" AutoReverse="False" />
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" By="0" Duration="0:0:0.8" AutoReverse="False" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding UI_DirectionOfMovement}" Value="NorthEast">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Top)" By="-50" Duration="0:0:0.8" AutoReverse="False" />
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" By="50" Duration="0:0:0.8" AutoReverse="False" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding UI_DirectionOfMovement}" Value="East">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Top)" By="0" Duration="0:0:0.8" AutoReverse="False" />
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" By="50" Duration="0:0:0.8" AutoReverse="False" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding UI_DirectionOfMovement}" Value="SouthEast">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Top)" By="50" Duration="0:0:0.8" AutoReverse="False" />
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" By="50" Duration="0:0:0.8" AutoReverse="False" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding UI_DirectionOfMovement}" Value="South">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Top)" By="50" Duration="0:0:0.8" AutoReverse="False" />
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" By="0" Duration="0:0:0.8" AutoReverse="False" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding UI_DirectionOfMovement}" Value="SouthWest">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Top)" By="50" Duration="0:0:0.8" AutoReverse="False" />
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" By="-50" Duration="0:0:0.8" AutoReverse="False" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding UI_DirectionOfMovement}" Value="West">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Top)" By="0" Duration="0:0:0.8" AutoReverse="False" />
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" By="-50" Duration="0:0:0.8" AutoReverse="False" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding UI_DirectionOfMovement}" Value="NorthWest">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Top)" By="-50" Duration="0:0:0.8" AutoReverse="False" />
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" By="-50" Duration="0:0:0.8" AutoReverse="False" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
<DataTemplate x:Key="TestDataTemplate01" DataType="BO:MyPerson">
<Canvas Width="1000" Height="1000" Background="Transparent">
<Rectangle Width="50" Height="50" Fill="Red" Style="{StaticResource TestRectStyle}" Canvas.Top="300" Canvas.Left="300" />
</Canvas>
</DataTemplate>
</Window.Resources>
<Canvas Width="1000" Height="1000">
<ItemsControl Name="ic_People" ItemTemplate="{StaticResource TestDataTemplate01}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Width="1000" Height="1000" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<Button Canvas.Right="0" Click="Button_Click_1" Width="120">Next Move</Button>
</Canvas>
Codebehind;
public partial class Window1 : Window
{
private ObservableCollection<MyPerson> _personList = new ObservableCollection<MyPerson>();
public Window1()
{
InitializeComponent();
MyPerson person1 = new MyPerson();
_personList.Add(person1);
ic_People.ItemsSource = _personList;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
DoNextMove();
}
private int debugDirection = 0;
private void DoNextMove()
{
if (debugDirection > 15)
debugDirection = 0;
_personList[0].MoveOneTile(debugDirection);
debugDirection += 2; // increase by 2 to as I've not implemented the odd numbers yet
}
}
MyPerson code;
public class MyPerson : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
private string _dirMov = "";
public string UI_DirectionOfMovement
{
get { return _dirMov; }
set
{
_dirMov = value;
OnPropertyChanged("UI_DirectionOfMovement");
}
}
public void MoveOneTile(int directionToMove)
{
this.UI_DirectionOfMovement = "clear"; // clearing it first forces an update
switch (directionToMove)
{
case 0: { this.UI_DirectionOfMovement = "North"; break; }
case 2: { this.UI_DirectionOfMovement = "NorthEast"; break; }
case 4: { this.UI_DirectionOfMovement = "East"; break; }
case 6: { this.UI_DirectionOfMovement = "SouthEast"; break; }
case 8: { this.UI_DirectionOfMovement = "South"; break; }
case 10: { this.UI_DirectionOfMovement = "SouthWest"; break; }
case 12: { this.UI_DirectionOfMovement = "West"; break; }
case 14: { this.UI_DirectionOfMovement = "NorthWest"; break; }
default: { throw new Exception(); }
}
}
public MyPerson()
{
}
}
The problem here ultimately comes down to the fact that animations don't actually change the values of the Canvas.Left and Canvas.Top properties. They only appear to do so, as values obtained from animation override values obtained via data binding.
After each animation finishes, the animation 'holds' the value of the Canvas.Left or Canvas.Top dependency properties at its final value. This 'held' value is what is returned if you get the value of the dependency property, and it overrides any value set via data binding. As you start a second animation, the dependency property's value is obtained by working from the previous animation's held value. As more and more animations happen, WPF has to determine the location of the rectangle by going back through a chain of more and more animations.
I can't say why only the last (NW) animation runs after you've been run all of them. It quite probably has something to do with dependency property value precedence. This page doesn't say what happens if there are multiple animations on a dependency property, but in this situation I would assume that the last animation to start on that property takes precedence. I suspect that the DataTriggers are firing, but the WPF dependency property system for some reason is ignoring values coming from the 'overridden' animations.
I would recommend avoiding having a chain of animations like this. Instead, change your Person objects to keep track of where they are on the canvas, for example, by adding Left and Top properties. You can then bind Canvas.Left and Canvas.Top to these properties. Your DoNextMove method should also set the values of these properties to where the animation moves them to. Do this after changing the value of theUI_DirectionOfMovement property. Finally, stop your animations from 'holding' their final values by setting FillBehavior="Stop" on each DoubleAnimation.
As animated values take precedence over locally-set values, it's not a problem to set the property values for Left and Top at the start of the animation. While the animation is running, the animated values for Canvas.Left and Canvas.Top take precedence over any value set via data binding. As the animation finishes, the animation releases its hold over the Canvas.Left and Canvas.Top dependency properties, and the location of the rectangle reverts to being obtained via data binding. With any luck, this location will be the same as at the end of the animation.
How to define XAML to rotate a rectangle infinitely?
So far I found a solution with code but no xaml:
http://www.codeproject.com/Articles/23257/Beginner-s-WPF-Animation-Tutorial
which I use like this:
private void Window_Loaded_1(object sender, RoutedEventArgs e)
{
var doubleAnimation = new DoubleAnimation(360, 0, new Duration(TimeSpan.FromSeconds(1)));
var rotateTransform = new RotateTransform();
rect1.RenderTransform = rotateTransform;
rect1.RenderTransformOrigin = new Point(0.5, 0.5);
doubleAnimation.RepeatBehavior = RepeatBehavior.Forever;
rotateTransform.BeginAnimation(RotateTransform.AngleProperty, doubleAnimation);
}
But how can I achieve this with XAML only?
Something like this
<Rectangle x:Name="rect1" RenderTransformOrigin="0.5, 0.5">
<Rectangle.RenderTransform>
<!-- giving the transform a name tells the framework not to freeze it -->
<RotateTransform x:Name="noFreeze" />
</Rectangle.RenderTransform>
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="(Rectangle.RenderTransform).(RotateTransform.Angle)"
To="-360" Duration="0:0:1" RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Rectangle.Triggers>
</Rectangle>
Of course you can remove Loaded trigger and run this storyboard whenever you want.
I've got this in my XAML:
<Grid.Resources>
<Storyboard x:Name="Storyboard_Animation">
<DoubleAnimation
Storyboard.TargetName="button_Submit"
Storyboard.TargetProperty="Angle"
From="0"
To="360"
Duration="0:0:1"></DoubleAnimation>
</Storyboard>
</Grid.Resources>
I have the button in the same Grid:
<Button Grid.Row="0" Grid.Column="1" Content="Submit" Margin="0" Name="button_Submit" Click="button_Submit_Click">
<Button.Template>
<ControlTemplate>
<Image Source="Images/buttonImage.png"></Image>
</ControlTemplate>
</Button.Template>
<Button.RenderTransform>
<RotateTransform></RotateTransform>
</Button.RenderTransform>
</Button>
I have this in my click method:
private void button_Submit_Click(object sender, RoutedEventArgs e)
{
Storyboard_Animation.Begin();
}
When I click on my button I get the error:
Cannot resolve TargetProperty Angle on specified object.
But, I have no idea what I'm supposed to use other than Angle.
I have this other piece of code that works fine:
private void RotateStar()
{
button_Submit.RenderTransformOrigin = new Point(0.5, 0.5);
button_Submit.RenderTransform = new RotateTransform();
DoubleAnimation da = new DoubleAnimation
{
From = 0,
To = 360,
Duration = TimeSpan.FromSeconds(0.3)
};
Storyboard.SetTarget(da, button_Submit.RenderTransform);
Storyboard.SetTargetProperty(da, new PropertyPath(RotateTransform.AngleProperty));
Storyboard sb = new Storyboard();
sb.Children.Add(da);
sb.Begin();
}
I'd like to put the storyboard in the XAML instead of in code. What do I need to add/change in my XAML version so that it works like the code version?
Try this:
<Grid.Resources>
<Storyboard x:Name="Storyboard_Animation">
<DoubleAnimation
Storyboard.TargetName="button_Submit"
Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)"
From="0"
To="360"
Duration="0:0:1">
</DoubleAnimation>
</Storyboard>
</Grid.Resources>
Your problem is in incorrect using "TargetProperty". Button doesn't have Angle property, you should use it for RenderTransform.
Such this:
<Storyboard x:Name="Storyboard_Animation">
<DoubleAnimation Duration="0:0:1" To="-180.221" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.Rotation)" Storyboard.TargetName="button" d:IsOptimized="True"/>
</Storyboard>
Regards,
Roman.
I am learning WPF animation, and I created a simple demo app with a pretty straightforward animation. I have divided the main grid into three rows; a Buttons Row at the top, and two content rows that for the remainder of the screen, one red and one blue. Complete XAML is below.
There are two buttons, Show Red and Show Blue. When each button is pressed, I want the area below the Buttons Row to change color with a slow top-to-bottom wipe. The Storyboard sets the height of both rows to 0, then animates the desired row to a height of 1*, like this:
<Storyboard>
<Utility:GridLengthAnimation Storyboard.TargetName="RedRow" Storyboard.TargetProperty="Height" To="0" Duration="0:0:0" />
<Utility:GridLengthAnimation Storyboard.TargetName="BlueRow" Storyboard.TargetProperty="Height" To="0" Duration="0:0:0" />
<Utility:GridLengthAnimation Storyboard.TargetName="BlueRow" Storyboard.TargetProperty="Height" From="0" To="1*" Duration="0:0:5" />
</Storyboard>
The colors change as expected, but there is no animation. So, my question is simple: Why isn't the animation working?
I am using a custom animation class, GridLengthAnimation (adapted from this CodeProject article) to animate the grid lengths. I have reproduced the class below.
To recreate the demo project: To recreate my demo project, create a new WPF project (I used VS 2010) and replace the XAML in MainWindow.xaml with the following:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Utility="clr-namespace:Utility" Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Name="Buttons" Height="35" />
<RowDefinition Name="RedRow" Height="0.5*" />
<RowDefinition Name="BlueRow" Height="0.5*" />
</Grid.RowDefinitions>
<!-- Buttons -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Content="Show Red" Width="100" Margin="5" >
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<Utility:GridLengthAnimation Storyboard.TargetName="RedRow" Storyboard.TargetProperty="Height" To="0" Duration="0:0:0" />
<Utility:GridLengthAnimation Storyboard.TargetName="BlueRow" Storyboard.TargetProperty="Height" To="0" Duration="0:0:0" />
<Utility:GridLengthAnimation Storyboard.TargetName="RedRow" Storyboard.TargetProperty="Height" From="0" To="1*" Duration="0:0:5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Button.Triggers>
</Button>
<Button Content="Show Blue" Width="100" Margin="5" >
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<Utility:GridLengthAnimation Storyboard.TargetName="RedRow" Storyboard.TargetProperty="Height" To="0" Duration="0:0:0" />
<Utility:GridLengthAnimation Storyboard.TargetName="BlueRow" Storyboard.TargetProperty="Height" To="0" Duration="0:0:0" />
<Utility:GridLengthAnimation Storyboard.TargetName="BlueRow" Storyboard.TargetProperty="Height" From="0" To="1*" Duration="0:0:5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Button.Triggers>
</Button>
</StackPanel>
<!-- Grid Fills-->
<Border Grid.Row="1" Background="Red" />
<Border Grid.Row="2" Background="Blue" />
</Grid>
</Window>
There is no code-behind added to MainWindow.xaml.
Add a C# class to the project named GridLengthAnimation.cs. Replace the code in that class with the following:
using System;
using System.Windows.Media.Animation;
using System.Windows;
namespace Utility
{
/// <summary>
/// Enables animation of WPF Grid row heights and column widths.
/// </summary>
/// <remarks>Adapted from Graus & Sivakumar, "WPF Tutorial - Part 2 : Writing a custom animation class",
/// http://www.codeproject.com/KB/WPF/GridLengthAnimation.aspx, retrieved 08/12/2010.</remarks>
internal class GridLengthAnimation : AnimationTimeline
{
static GridLengthAnimation()
{
FromProperty = DependencyProperty.Register("From", typeof(GridLength),
typeof(GridLengthAnimation));
ToProperty = DependencyProperty.Register("To", typeof(GridLength),
typeof(GridLengthAnimation));
}
public override Type TargetPropertyType
{
get
{
return typeof(GridLength);
}
}
protected override Freezable CreateInstanceCore()
{
return new GridLengthAnimation();
}
public static readonly DependencyProperty FromProperty;
public GridLength From
{
get
{
return (GridLength)GetValue(FromProperty);
}
set
{
SetValue(FromProperty, value);
}
}
public static readonly DependencyProperty ToProperty;
public GridLength To
{
get
{
return (GridLength)GetValue(ToProperty);
}
set
{
SetValue(ToProperty, value);
}
}
public override object GetCurrentValue(object defaultOriginValue, object defaultDestinationValue, AnimationClock animationClock)
{
double fromVal = ((GridLength)GetValue(FromProperty)).Value;
double toVal = ((GridLength)GetValue(ToProperty)).Value;
if (animationClock.CurrentProgress != null)
{
if (fromVal > toVal)
{
return new GridLength((1 - animationClock.CurrentProgress.Value) * (fromVal - toVal) + toVal, GridUnitType.Star);
}
else
{
return new GridLength(animationClock.CurrentProgress.Value * (toVal - fromVal) + fromVal, GridUnitType.Star);
}
}
else
{
return null;
}
}
}
}
I found my answer in this blog post. It turns out there is a problem animating height or width properties. I worked around the problem by using a dissolve effect, instead of a wipe. To animate a dissolve, declare both controls in the same Grid row and column, which will load them on top of each other. Declare the default control last, which will make it the visible control. Then, animate the Opacity value of the default control to zero to hide it, and back to 1 to show it.
If the controls being animated are UserControls or other controls you need to click on, you need to take one more step. That's because setting the Opacity of a control to zero simply makes it invisible. It will still prevent a click on the control beneath it. So, declare a Render.Transform on the default control, then animate the ScaleY property to set it to 0 when invisible and 1 when showing.
Here is an example from the production app I am working on. It switches between a note list and a calendar (two different UserControls) in the Navigator pane of an Explorer-style interface. Here is the declaration of the two controls:
<!-- ClientArea: Navigator -->
<Grid x:Name="Navigator">
<View:CalendarNavigator x:Name="Calendar" />
<View:NoteListNavigator x:Name="NoteList">
<View:NoteListNavigator.RenderTransform>
<ScaleTransform ScaleX="1" ScaleY="1" />
</View:NoteListNavigator.RenderTransform>
</View:NoteListNavigator>
</Grid>
Note the declaration of the ScaleTransform on the note list. I use a couple of Ribbon buttons to switch between the two UserControls:
<ribbon:RibbonToggleButton x:Name="NoteListViewButton" LargeImageSource="..\Images\ListViewLarge.png" SmallImageSource="..\Images\ListViewSmall.png" Label="Note List" Click="OnViewButtonClick">
<ribbon:RibbonToggleButton.Triggers>
<EventTrigger RoutedEvent="ribbon:RibbonToggleButton.Checked">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="NoteList" Storyboard.TargetProperty="(View:NoteListNavigator.RenderTransform).(ScaleTransform.ScaleY)" To="1" Duration="0:0:0" />
<DoubleAnimation Storyboard.TargetName="NoteList" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</ribbon:RibbonToggleButton.Triggers>
</ribbon:RibbonToggleButton>
<ribbon:RibbonToggleButton x:Name="CalendarViewButton" LargeImageSource="..\Images\CalendarViewLarge.png" SmallImageSource="..\Images\CalendarViewSmall.png" Label="Calendar" Click="OnViewButtonClick">
<ribbon:RibbonToggleButton.Triggers>
<EventTrigger RoutedEvent="ribbon:RibbonToggleButton.Checked">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="NoteList" Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:1" />
<DoubleAnimation Storyboard.TargetName="NoteList" Storyboard.TargetProperty="(View:NoteListNavigator.RenderTransform).(ScaleTransform.ScaleY)" To="0" Duration="0:0:0" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</ribbon:RibbonToggleButton.Triggers>
</ribbon:RibbonToggleButton>
The ScaleY transforms get the invisible note list out of the way when the Calendar is showing so that I can click on my calendar controls. Note that I needed fully-qualified references to the ScaleY properties in my Storyboards. That's why the references are enclosed in parentheses.
Hope that helps someone else down the road! It's likely to be me, since I'll probably forget how I did this...