Last few days, I saw a WPF Video Post that demonstrates how to start a blast graphic effect, based on image series. See this link.
Yap, this is a very cool blast, but IMHO this is not the right way to go with WPF. The tips bellow will help you to redesign the application correctly, using WPF fundamentals.
Tip 1: Design your model/data to support Data Binding (provide property for almost everything). Do not forget that data-binding in WPF is everywhere!
Tip 2: Do not hesitate to use Dependency Properties instead of CLR properties (yap, the syntax is cumbersome: use code-snippets!). DP will help you with better data-binding, styling, animation and data templating.
Tip 3: Do not use the DispatcherTimer for creating animations. WPF has a strong built-in support for animations, which can be also customized by deriving classes.
Tip 4: Use data-templates for translating Data into View. Do not create your view and then bind it to data, instead create an appropriate data structure and then create one or more data-templates to translate the data into view. This clever mechanism provides best design practices for decoupling the data from the UI.
Now after you read all the tips, let's look on how the previous solution should look like:
ImageSequence.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Media.Imaging;
using System.Windows;
namespace SpecialEffect
{ class ImageSequence : DependencyObject
{ public ImageSequence()
{ // Loading FX images
for (int imageId = 0; imageId < 5; ++imageId)
{ BitmapImage image = LoadImage(imageId + 1);
_images.Add(image);
}
_images.Add(EmptyImage);
}
#region Current Property
/// <value>Current frame dependency property</value>
public static readonly DependencyProperty CurrentProperty =
DependencyProperty.Register("Current", typeof(BitmapSource), typeof(ImageSequence),
new FrameworkPropertyMetadata(EmptyImage));
/// <value>Current frame</value>
public BitmapSource Current
{ get { return (BitmapSource)GetValue(CurrentProperty); } private set { SetValue(CurrentProperty, value); } }
#endregion
#region Index Property
/// <value>Current index dependency property</value>
public static readonly DependencyProperty IndexProperty =
DependencyProperty.Register(
"Index", typeof(int), typeof(ImageSequence),
new FrameworkPropertyMetadata(0, OnIndexChanged));
/// <value>Current index</value>
public int Index
{ get { return (int)GetValue(IndexProperty); } set { SetValue(IndexProperty, value); } }
static void OnIndexChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{ // Update current frame
ImageSequence sequence = (ImageSequence)d;
sequence.Current = sequence._images[(int)e.NewValue];
}
#endregion
private static BitmapImage LoadImage(int imageId)
{ // Load an image from resource
string imageUri = string.Format(
"pack://application:,,,/Images/Fx0{0}.png", imageId); BitmapImage image = new BitmapImage(new Uri(imageUri));
return image;
}
private List<BitmapSource> _images = new List<BitmapSource>();
private static readonly BitmapSource EmptyImage = LoadImage(0);
}
}
Window1.xaml
<Window x:Class="SpecialEffect.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:SpecialEffect"
Title="SpecialEffect" Height="300" Width="300">
<Window.Resources>
<ObjectDataProvider x:Key="sequence"
ObjectType="{x:Type c:ImageSequence}" /> <DataTemplate DataType="{x:Type c:ImageSequence}"> <Border>
<Image Source="{Binding Path=Current}" /> </Border>
</DataTemplate>
</Window.Resources>
<Grid>
<Button Content="Bug Effect!" Height="25" Width="100">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<Int32Animation
Storyboard.TargetName="content"
Storyboard.TargetProperty="Content.Index"
From="0" To="5" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
<ContentControl Name="content" IsHitTestVisible="False"
Content="{Binding Source={StaticResource sequence}}" /> </Grid>
</Window>
Although it seems that I'm trying to kill an ant with a nuclear bomb, this solution is much more compatible with the WPF desing concepts. It completely decouples the data from the view, and can be easily customized to look differently.