Silverlight 2 UserControl base class in Blend

24 08 2008

I have been doing a lot of Silverlight2 development the last few months. And one of the things you will start to do pretty quickly is make base classes for your pages. You can do this by creating a base class which inherits a UserControl, inherit from it in your Page.xaml.cs and then changing the root of your XAML from <UserControl to <local:MyPageBase. Like this:

Page.xaml.cs:

public partial class Page : PageBase

{

    public Page()

    {

        InitializeComponent();

    }

}

PageBase.cs: (In another "common" project)

public class PageBase : UserControl

{

 

}

Page.xaml:

<common:PageBase x:Class="Silverlight2UserControlWithBaseClassBlendFix.Client.Page"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   xmlns:common="clr-namespace:Silverlight2UserControlWithBaseClassBlendFix.Client.Common;assembly=Silverlight2UserControlWithBaseClassBlendFix.Client.Common">

    <Grid x:Name="LayoutRoot"/>

</common:PageBase>

 

The blend catch

However there is one catch to this whole exercise: you lose editing capabilities in blend!

This is of course something that will be fixed in the final version, but for now it is a breaking bug. Which "almost" makes you run away from base classes.

I have seen a lot of people complain about the bug, but I have not found anyone giving at least a temporary solution until it is fixed.

Not so much a solution

First I thought about a very simple solution. To use the page only as a container and put all the content in another user control. But this has the disadvantage that you loose all the functionality from the base class again, and that was was we were aiming for. So it solves one problem and we get back the one we had.

At least it works

First couple of weeks, I just stuck with it, no base classes. I used extension methods and other "not so much solutions" to work around the problem. But today I finally had enough and started on a bandage that should hold at least until the final versions are released.

The idea is to use a text SearchAndReplace tool in the pre build event, to replace <UserControl to <local:BasePage and back again in the post build.

I had already written a simple command line SeardAndReplace tool before so this was ideal for the job. It does gives you random build errors, but this is simply resolved by building your project again.

Pre-Build: (replace <UserControl to <common:PageBase )

$(SolutionDir)SearchAndReplace $(ProjectDir)Page.xaml /search="<UserControl " /replace="<common:PageBase "

$(SolutionDir)SearchAndReplace $(ProjectDir)Page.xaml /search="</UserControl>" /replace="</common:PageBase>"

 

Post-Build: (replace back to <UserControl)

$(SolutionDir)SearchAndReplace $(ProjectDir)Page.xaml /search="<common:PageBase " /replace="<UserControl "

$(SolutionDir)SearchAndReplace $(ProjectDir)Page.xaml /search="</common:PageBase>" /replace="</UserControl>"

 

Click here to download the whole "proof of concept project".

Stay in the light!

Robertjan Tuit



AnimateTo extension methods in Silverlight 2

13 08 2008

So, back from a fair long time of blog silence. Had some pretty busy weeks, with some very interesting projects. Among them I had the honor of working on the Olympics website (http://os2008.nos.nl) for the NOS (Dutch Broadcasting Company). I just wanted to mention it because I think the team over there did an excellent job, you can check it out yourself.

Since I have done lots of work with Silverlight 2 the past months, I wish to share with you some of the findings, and handy pieces of code I have gathered. I’ll start with the AnimateTo Extension methods I wrote for Silverlight UIElements and Transforms.

The reason I started writing these extensions was the frequency with which I needed to do simple animations, especially with transforms the result is fantastic.

The extensions methods can be used as follows:

// ————————————

// These examples create the elements only for

// demo purposes, normally you would use elements from XAML.

// ————————————

 

// This will fade out the rectangle, scale it to very small,

// and at the end set visibility to collapsed

Rectangle rectangle = new Rectangle();

rectangle.RenderTransform = new ScaleTransform();

rectangle.RenderTransformOrigin = new Point(0.5, 0.5);

rectangle.AnimateDoubleTo(300, "(UIElement.Opacity)", 0, null);

((ScaleTransform)rectangle.RenderTransform).AnimateTo(300, 0.1, 0.1, (sender, e) =>{

    rectangle.Visibility = Visibility.Collapsed;

});

 

// This example will rotate an element for 30 degrees

// in 200 milliseconds and then rotate -30 degrees the

// other way

var rotateTransform = new RotateTransform();

rotateTransform.AnimateTo(200, 30, true, (sender,e) =>{

    rotateTransform.AnimateTo(200, -30, true, null);

    });

}

 

Below the AnimateTo Extention method for UIElements:

public static class UIElementExtentions

{

    public static void AnimateDoubleTo(this UIElement element, int miliseconds, string propertyPath, double value, EventHandler completed)

    {

        // Create a new storyboard

        var sb = new Storyboard();

 

        // Create a double animation

        var da = new DoubleAnimation();

        da.Duration = new TimeSpan(0, 0, 0, 0, miliseconds);

        // Set target property and target

        Storyboard.SetTargetProperty(da, new PropertyPath(propertyPath));

        Storyboard.SetTarget(da, element);

        da.To = value;

        // Add the doubleanimation to the storyboard

        sb.Children.Add(da);

 

        // Add the storyboard to the Rootvisual of the application

        ((FrameworkElement)Application.Current.RootVisual).Resources.Add(Guid.NewGuid().ToString(), sb);

 

        // Begin the animation

        sb.Begin();

 

        if (completed != null)

            sb.Completed += completed;

    }

}

 

And here is the rest, without comments, because they are basicly the samen as the previous code:

public static class ScaleTransformExtentions

{

    public static void AnimateTo(this ScaleTransform scaleTransform, int milliseconds, double scaleX, double scaleY, EventHandler completed)

    {

        var sb = new Storyboard();

 

        var daX = new DoubleAnimation();

        daX.Duration = new TimeSpan(0, 0, 0, 0, milliseconds);

        Storyboard.SetTargetProperty(daX, new PropertyPath("(ScaleTransform.ScaleX)"));

        Storyboard.SetTarget(daX, scaleTransform);

        daX.To = scaleX;

 

        var daY = new DoubleAnimation();

        daY.Duration = new TimeSpan(0, 0, 0, 0, milliseconds);

        Storyboard.SetTargetProperty(daY, new PropertyPath("(ScaleTransform.ScaleY)"));

        Storyboard.SetTarget(daY, scaleTransform);

        daY.To = scaleY;

 

        sb.Children.Add(daX);

        sb.Children.Add(daY);

 

        ((FrameworkElement)Application.Current.RootVisual).Resources.Add(Guid.NewGuid().ToString(), sb);

 

        sb.Begin();

 

        if (completed != null)

            sb.Completed += completed;

    }

}

 

public static class TranslateTransformExtentions

{

    public static void AnimateTo(this TranslateTransform translateTransform, int milliseconds, double x, double y, EventHandler completed)

    {

        var sb = new Storyboard();

 

        var daX = new DoubleAnimation();

        daX.Duration = new TimeSpan(0, 0, 0, 0, milliseconds);

        Storyboard.SetTargetProperty(daX, new PropertyPath("(TranslateTransform.X)"));

        Storyboard.SetTarget(daX, translateTransform);

        daX.To = x;

 

        var daY = new DoubleAnimation();

        daY.Duration = new TimeSpan(0, 0, 0, 0, milliseconds);

        Storyboard.SetTargetProperty(daY, new PropertyPath("(TranslateTransform.Y)"));

        Storyboard.SetTarget(daY, translateTransform);

        daY.To = y;

 

        sb.Children.Add(daX);

        sb.Children.Add(daY);

 

        ((FrameworkElement)Application.Current.RootVisual).Resources.Add(Guid.NewGuid().ToString(), sb);

 

        sb.Begin();

 

        if (completed != null)

            sb.Completed += completed;

    }

}

 

public static class RotateTransformExtentions

{

    public static void AnimateTo(this RotateTransform rotateTransform, int milliseconds, double angle, bool relative, EventHandler completed)

    {

        var sb = new Storyboard();

 

        if (relative)

            angle = rotateTransform.Angle + angle;

 

        var da = new DoubleAnimation();

        da.Duration = new TimeSpan(0, 0, 0, 0, milliseconds);

 

        Storyboard.SetTargetProperty(da, new PropertyPath("(RotateTransform.Angle)"));

        Storyboard.SetTarget(da, rotateTransform);

        da.To = angle;

 

        sb.Children.Add(da);

 

        ((FrameworkElement)Application.Current.RootVisual).Resources.Add(Guid.NewGuid().ToString(), sb);

 

        sb.Begin();

 

        if (completed != null)

            sb.Completed += completed;

    }

}

 

Have fun with it!

Robertjan Tuit