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.
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.
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:
- Does the element have the storyboard for this animation in it's resouce collection.
- If so they we pause that animation.
- If not we create our new storyboard, add it the resource collection and set it's target.
- We apply the values of the animation to the storyboard.
- 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
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.