Merry Christmas

December 23, 2008 10:03 by Nigel Sampson

See you all in the new year.

Take care.

Creating a Dependency Property of type System.Type in Silverlight

December 18, 2008 10:36 by Nigel Sampson

Last week I was looking at building a declarative data source control similar to the ObjectDataSource in ASP.NET and ran into a roadblock pretty quickly. Obviously for a control of this nature I want a few properties of type System.Type.

Simply declaring the property and trying to use it fails with a Xaml parser exception, not entirely surprising as it looks like the Xaml parser doesn't know how to convert the string representation of the type to it the actual Type object. We can give it a helping hand using a TypeConvertor, below is the code for a simple StringToTypeConvertor to get around this.

public class StringToTypeConverter : TypeConverter

{

    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)

    {

        return sourceType.IsAssignableFrom(typeof(string));

    }

 

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)

    {

        var typeName = value as string;

 

        if(String.IsNullOrEmpty(typeName))

            return null;

 

        return Type.GetType(typeName);

    }

}

 

Side note: This isn't my idea solution, ideally I'd want to use a similar syntax to the property TargetType on Style, but alas it uses some internal sealed classes (and I suspect some hard coding in the Xaml parser) to achieve what it is. Obviously this style of property is going to be used more and more, especially in the upcoming Alexandria release of Silverlight. I really hope Microsoft don't continue to keep this rather useful piece of functionality to themselves.

Once we've applied our new TypeConverter as shown below we shouldn't be receiving exceptions from the Xaml parser, but more than likely our property is null or you're receiving exceptions from Type.GetType (this depends on what you have in your Xaml as the type string and where the type is).

[TypeConverter(typeof(StringToTypeConverter))]

public Type DataSourceType

{

    get

    {

        return (Type)GetValue(DataSourceTypeProperty);

    }

    set

    {

        SetValue(DataSourceTypeProperty, value);

    }

}

 

From what I can gather the behavior of Type.GetType is different in than in the standard .NET runtime. When referencing a custom type most of the time you could write something along the lines of "CompiledExperience.Core.MyCustomType" if we are in the same assembly or "CompiledExperience.Examples.Animation.Page, CompiledExperience.Examples" if we're referencing a type in a separate assembly.

In Silverlight the former will work, the latter however will cause an FileLoadException in Type.GetType. What will working however is the fully qualified assembly name "CompiledExperience.Examples.Animation.Page, CompiledExperience.Examples, Version 1.0.0.0, Culture = neutral, PublicKey = null". Bit of a mouthful and stuff I'd really prefer not have to in my Xaml (especially when Microsoft don't need to).

DataSourceType="SilverlightExperiments.PeopleData, SilverlightExperiments, Version=1.0.0.0, Culture=neutral, PublicKey=null"

 

So in conclusion you can have dependency properties of System.Type using a TypeConvertor to get past Xaml parsing and using fully qualified type names to avoid differences in Type.GetType.

Fingers crossed this changes in later versions of Silverlight.

Why won't Generic.xaml work?

December 13, 2008 12:26 by Nigel Sampson

The first time I did some control development for Silverlight I ran into a major stumbling block, whatever I put into Generic.xaml just wasn't showing up and none of the tutorials I could find were showing anything different from what I was doing. After some digging through Silverlight with Reflector and a few forum searches show up the culprit ... "DefaultStyleKey".

I'm not sure if this was introduced into later versions of the Silverlight betas but it's certainly not mentioned in a lot of tutorials on the web. OnApplyTemplate uses this key to determine which style to look for in Generic.xaml. This allows you to create controls that inherit from your custom control but still use the same style.

For your control you'll want to override the DefaultStyleKey to the type of your object like follows.

public MyCustomControl()

{

    this.DefaultStyleKey = typeof(MyCustomControl);

}

It's tempting to do something like GetType(), but that'll be different for any subclasses so stick with a known type.

Beginning a Silverlight Animation framework

December 5, 2008 17:09 by Nigel Sampson

Silverlight has an excellent animation system, built on top of storyboards and various sorts of animation classes.

In essence a storyboard is a collection of animations, these are all subclassed from the System.Windows.Media.Animation.Timeline class. Obivously the ColorAnimation classes are for animation a colour and so on. For our basic examples we'll be working with DoubleAnimation's which we'll use to manipulate an object's location and size.

So whats the result we're after? Ideally it'll be a syntax that allows animation of all objects not just ones that have a specific subclass, something like this.

Target.AnimatePosition(Left.Value, Top.Value);

You can see the end result of this post at Silverlight Animation Examples

We also want to have a couple of behavioural constraints, that if an animation (of the same sort) is running then it's halted and the new one is started. We also want to reuse resources as effectively as possible, not creating new storyboards and animations unless we have to, this will speed the animation up and ensure we don't leak resources and memory.

We'll need an AnimationBase class that'll handle the rules we discussed above. This class will have abstract members for creating the storyboard for this animation and because we want to reuse storyboards between animations we'll seperate the values of animation from the storyboard itself.

Overall our process for applying an animation to an element will be as follows:

  1. Does the element have the storyboard for this animation in it's resouce collection.
  2. If so they we pause that animation.
  3. If not we create our new storyboard, add it the resource collection and set it's target.
  4. We apply the values of the animation to the storyboard.
  5. Begin the animation.

The code for Animation base is below:

public abstract class AnimationBase

{

    public event EventHandler AnimationCompleted;

 

    protected virtual void OnAnimationCompleted(EventArgs e)

    {

        if(AnimationCompleted != null)

            AnimationCompleted(this, e);

    }

 

    protected virtual string ResourceKey

    {

        get

        {

            return GetType().FullName;

        }

    }

 

    protected abstract Storyboard CreateStoryboard();

    protected abstract void ApplyValues(Storyboard storyboard);

 

    public virtual void Apply(FrameworkElement element)

    {

        if(element == null)

            throw new ArgumentNullException("element");

 

        Storyboard storyboard = null;

 

        if(element.Resources.Contains(ResourceKey))

        {

            storyboard = element.Resources[ResourceKey] as Storyboard;

            storyboard.Pause();

        }

        else

        {

            storyboard = CreateStoryboard();

            element.Resources.Add(ResourceKey, storyboard);

 

            foreach(var timeline in storyboard.Children)

            {

                Storyboard.SetTarget(timeline, element);

            }

        }

 

        ApplyValues(storyboard);

 

        storyboard.Completed += OnStoryboardCompleted;

        storyboard.Begin();

    }

 

    protected virtual void OnStoryboardCompleted(object sender, EventArgs e)

    {

        OnAnimationCompleted(EventArgs.Empty);

    }

}

Now we have AnimationBase we'll build our first actual animation, PositionAnimation. Our storyboard will have two animations, one for the left property and the other for the top property, I've made them both using KeyFrames in order to give them a bit of spice rather than just linear animations. The important thing to note is that we haven't set any values on the animation about where we're moving the element to. This will come in the ApplyValues overload, as mentioned before this is because we want to reuse storyboards for multiple animations. We'll also add some constructor arguments for configuring the position and duration of the animation.

The code for PositionAnimation is below:

public class PositionAnimation : AnimationBase

{

    public static TimeSpan DefaultDuration = TimeSpan.FromMilliseconds(750);

 

    public PositionAnimation(double left, double top)

        : this(left, top, DefaultDuration)

    {

 

    }

 

    public PositionAnimation(double left, double top, TimeSpan duration)

    {

        Left = left;

        Top = top;

        Duration = duration;

    }

 

    public double Left

    {

        get;

        private set;

    }

 

    public double Top

    {

        get;

        private set;

    }

 

    public TimeSpan Duration

    {

        get;

        private set;

    }

 

    protected override void ApplyValues(Storyboard storyboard)

    {

        if(storyboard == null)

            throw new ArgumentNullException("storyboard");

 

        var leftAnimation = storyboard.Children[0] as DoubleAnimationUsingKeyFrames;

        var topAnimation = storyboard.Children[1] as DoubleAnimationUsingKeyFrames;

 

        leftAnimation.KeyFrames[0].Value = Left;

        leftAnimation.KeyFrames[0].KeyTime = KeyTime.FromTimeSpan(Duration);

        topAnimation.KeyFrames[0].Value = Top;

        topAnimation.KeyFrames[0].KeyTime = KeyTime.FromTimeSpan(Duration);

    }

 

    protected override Storyboard CreateStoryboard()

    {

        var storyboard = new Storyboard();

 

        var leftAnimation = new DoubleAnimationUsingKeyFrames();

        var topAnimation = new DoubleAnimationUsingKeyFrames();

 

        Storyboard.SetTargetProperty(leftAnimation, new PropertyPath("(Canvas.Left)"));

        Storyboard.SetTargetProperty(topAnimation, new PropertyPath("(Canvas.Top)"));

 

        storyboard.Children.Add(leftAnimation);

        storyboard.Children.Add(topAnimation);

 

        leftAnimation.KeyFrames.Add(new SplineDoubleKeyFrame()

        {

            KeySpline = new KeySpline()

            {

                ControlPoint1 = new Point(0.528, 0),

                ControlPoint2 = new Point(0.142, 0.847)

            }

        });

 

        topAnimation.KeyFrames.Add(new SplineDoubleKeyFrame()

        {

            KeySpline = new KeySpline()

            {

                ControlPoint1 = new Point(0.528, 0),

                ControlPoint2 = new Point(0.142, 0.847)

            }

        });

 

        return storyboard;

    }

}

 

With our animation class in place we'll just create some extension methods to use our animation classes and provide the syntax from the beginning.

public static void AnimatePosition(this FrameworkElement element, double left, double top)

{

    new PositionAnimation(left, top).Apply(element);

}

 

Over time I'll create more of these animation classes and post them and our finished result is a Silverlight Animation Examples

Gotcha when deploying Silverlight applications

December 3, 2008 10:55 by Nigel Sampson

I'm just in the process of writing my first Silverlight post, one of the benefits of writing about Silverlight is that you can show it in action. So last night I put together the working example, and was running into a roadblock at the final stage, deployment.

I was using the Silverlight control in the System.Web.Silverlight.dll assembly and no matter what I did with IIS mime types and minimum versions it would always come up with the "Get Silverlight image". It turns out that I was too quick to get the latest bits for Silverlight 2 when it was finally released, the final version of the Silverlight Tools for Visual Studio (and therefore the aforementioned assembly) weren't released till approximately a week later.

The reason I noticed it was the type attribute on the <object> output, it mentioned beta 2 and not the standard "application/x-silverlight-2".

Little frustrating but glad it's solved.