DCSIMG
WPF - Good Design Principles - Essential WPF

WPF - Good Design Principles

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.

Published Saturday, May 12, 2007 11:17 PM by Tomer Shamam
תגים:, , ,

Comments

No Comments

Leave a Comment

(required) 
(required) 
(optional)
(required) 

Enter the numbers above:
Powered by Community Server (Commercial Edition), by Telligent Systems