The recent release of .NET 3.5 SP1 adds hardware-accelerated pixelshader effects to WPF. In addition to replacing a number of the slow software-rendered BitmapEffects with shiny, fast hardware-accelerated ones they also added the ability to write your own effects. In order to provide an easy-to-digest package for those who don't want to write shaders themselves, but DO want to add cool effects to their app I created a community shader project, hosted on codeplex at http://www.codeplex.com/fx . If you're a graphics wonk chances are you can write much cooler effects than the ones I've already build, so please feel free to contribute.
Here is an overview of the effects in the 0.1 release:
Also I've been fortunate enough to enlist the help of a _real_ graphics wonk already, who added a nice radial blur effect. Thanks OJ.
WPF’s animation system is used for changing properties such as width, position or color of an element over time. When animations finish they can either return the property being animated to its original value, or “hold” their final value. This behavior is controlled by the FillBehavior property of the animation. After animating properties where the animation holds the final value developers can run into problems when they try to change those very same properties via code. The changes seem to have no effect!
This strange behavior is a side-effect of the WPF dependency property system. The dependency property system is a special property system built by the WPF team. It allows element properties to be inherited from their container element, animated, data bound and set via regular .NET code. One side-effect of this kind of property system is that a single property on an element could get its value from a number of potential sources. In the regular .NET property system property changes are fairly transparent, with more recent changes over-writing previous ones. The more declarative nature of WPF makes this temporal-based property system less useful. Instead the value an element property has is based on a precedence system which is detailed here. Animations have a higher precedence than everything else, so changes made by them always over-ride other possible property values. While this seems sensible and in most cases gives us the outcome we would expect it can be a little confusing for animations that hold their value after they complete. When the animation ends it holds the property at a certain value, and because animations takes precedence over other ways properties can be set it prevents the property being changed through normal .NET code.
It is possible to change this using a little code to remove the animation when it completes, however a better approach is to not use the HoldEnd fill behavior if you need to subsequently change a value in code. Instead you should use the Stop FillBehavior and “fake” the HoldEnd by handling the completed event for the animation and setting the property value to the value you wish to finish at.
The following code shows a text block that has its FontSize animated to 35 pixels when it loads.
<TextBlock Grid.Row="0" Text="WPF is great" FontFamily="Lucida Sans" Foreground="#feca00" HorizontalAlignment="Center" Name="windowHeading">
<TextBlock.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<EventTrigger.Actions>
<BeginStoryboard Name="windowHeadingStoryboard">
<Storyboard>
<DoubleAnimation To="35" Storyboard.TargetName="windowHeading"
Storyboard.TargetProperty="FontSize" Duration="0:0:5"
Completed="FinishedAnimation" FillBehavior="Stop" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</TextBlock.Triggers>
</TextBlock>
The very simple C# code to simulate the HoldEnd behavior is shown here in the Completed event handler for the animation.
void FinishedAnimation(object sender, EventArgs e)
{
windowHeading.FontSize = 35;
}
The obvious drawback with this approach is the fairly tight coupling between the markup and application code that is required for it to work.
In film editing a “wipe” is a technique for transitioning from one scene to another. We can create a similar effect in WPF by overlapping two items (for instance by placing them both in a single-cell grid) and specifying a LinearGradientBrush as the OpacityMask on the top-most item. Then by animating the Offset property of the GradientStops of the brush we can create the wipe effect. By modifying the start time of the second DoubleAnimation you can modify the width of the “in-between” area. An earlier start-time will give a narrower region that shows pieces of both items.
Xaml Code (RC1)
<Window x:Class="LearnWPF.WipeEffect.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="LearnWPF.WipeEffect" Height="500" Width="700" Name="mainWindow"
>
<Grid>
<Image Source="Images/UFO.jpg" />
<Image Source="Images/ladder.jpg">
<Image.OpacityMask>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Offset="0" Color="Black" x:Name="BlackStop"/>
<GradientStop Offset="0" Color="Transparent" x:Name="TransparentStop"/>
</LinearGradientBrush>
</Image.OpacityMask>
</Image>
</Grid>
<Window.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="TransparentStop"
Storyboard.TargetProperty="Offset" By="1" Duration="0:0:2" />
<DoubleAnimation Storyboard.TargetName="BlackStop"
Storyboard.TargetProperty="Offset" By="1" Duration="0:0:2"
BeginTime="0:0:0.05" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Window.Triggers>
</Window>
| A BeginTime of 50ms for the second animation produces a "tighter" wipe |
A BeginTime of 1 second for the second animation produces a more gradual transition |
|
|
Creating WPF animations in Xaml requires a fair amount of mark-up, and can be somewhat daunting to write by hand. Fortunately animations done in code can be created much more simply (however you do lose the "declarative" way of specifying when animations fire that markup allows). Here is a simple window which contains a single button child element. The hilighted C# code which is executed when the window has loaded animates the height of the button from 25 units to 150 units in 1 second.
Xaml Code (RC1)
<Window x:Class="LearnWPF.AnimationInCode.Window1"
xmlns="
http://schemas.microsoft.com/winfx/2006/xaml/presentation
"
xmlns:x="
http://schemas.microsoft.com/winfx/2006/xaml
"
Title="LearnWPF.AnimationInCode"
Height="300" Width="300" Loaded="WindowLoaded"
>
<Button Name="myButton" Content="Hello" />
</Window>
C# (RC1)
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;
namespace LearnWPF.AnimationInCode
{
public partial class Window1 : System.Windows.Window
{
public Window1()
{
InitializeComponent();
}
void WindowLoaded(object sender, RoutedEventArgs e)
{
myButton.BeginAnimation(Button.HeightProperty,
new DoubleAnimation(25, 150, new Duration(new TimeSpan(0, 0, 1))));
}
}
}
In a previous post Steve asks "is it possible to bind an animation using xaml?". The example below shows how, binding the duration of an animation to a value in a text box. The animation, which is triggered when the button is clicked, changes the button's background color from orange to brown. The duration of the animation is bound to the value entered in the text box using the same {Binding} markup extension used throughout WPF. Although in this example the animation is bound to another element it could be bound to other sources like data from a relational database or a value in an Xml file.
Xaml Code (WPF Beta 2/June CTP)
<Window x:Class="LearnWPF.BoundAnimation.Window1"
xmlns="
http://schemas.microsoft.com/winfx/2006/xaml/presentation
"
xmlns:x="
http://schemas.microsoft.com/winfx/2006/xaml
"
Title="LearnWPF.BoundAnimation" Height="200" Width="350"
>
<StackPanel Margin="2">
<Button Width="200" Height="50" Name="animateMe"
Content="Click The Button to See Animation">
<Button.Background>
<SolidColorBrush x:Name="buttonBrush" />
</Button.Background>
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard Storyboard.TargetName="buttonBrush"
Storyboard.TargetProperty="Color" >
<ColorAnimation To="#58290A" From="#feca00"
Duration="{Binding ElementName=durationText, Path=Text}" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Button.Triggers>
</Button>
<StackPanel Orientation="Horizontal" Margin="2">
<TextBlock VerticalAlignment="Center">Animation Time (hours:minutes:seconds)</TextBlock>
<TextBox Name="durationText" Text="0:0:1" />
</StackPanel>
</StackPanel>
</Window>