If broken it is, fix it you should

Using the powers of the debugger to solve the problems of the world - and a bag of chips    by Tess Ferrandez, ASP.NET Escalation Engineer (Microsoft)

Silverlight/WPF FlipImage Animation

Silverlight/WPF FlipImage Animation

  • Comments 19

I was working on some Silverlight samples and needed an image that could flip over as in the example below.

All the samples I could find on the net were pretty complex and contained a lot of code to do the animation and I wanted something really simple.

To create the illusion of an image that flips over (or any kind of UI element that flips over) you just need 4 things

1. A front image / UI element
2. A back image / UI element
3. A flip animation
4. A reverse animation

The flip and reverse animations can be very simple as you will see below. 

In order to make it reusable in our application we can create a UserControl and call it FlipImage

First create 2 UI elements (front and back), in this case Grids but they could really be any kind of items, like images, datagrids etc.

 

<Grid x:Name="LayoutRoot">
    <Grid x:Name="front">
        <Border Background="AntiqueWhite"  BorderBrush="DarkGray" BorderThickness="3" CornerRadius="10" />
        <Image x:Name="imgFront" Stretch="Fill" Height="100" Width="100" />
    </Grid>
    <Grid x:Name="back" >
        <Border Background="AliceBlue" BorderBrush="DarkGray" BorderThickness="3" CornerRadius="10" />
        <Image x:Name="imgBack" Stretch="Fill" Height="100" Width="100" />
    </Grid>
</Grid>

The animation for flipping the image is very simple. First we make the back image/UI element zero size, then to do the flip animation we just shrink the front image towards the middle, and when it is shrinked to zero size we expand the back image.

To get it to shrink around the middle we need to set the RenderTransformOrigin to 0.5,0.5... and in order to be able to do the shrinking/expanding we need to add <ScaleTransform...> to the UI Elements that we can target in the animations.

With all this, our XAML now looks like this

 

<Grid x:Name="LayoutRoot">
    <Grid x:Name="front" RenderTransformOrigin="0.5,0.5">
        <Grid.RenderTransform>
            <ScaleTransform/>
        </Grid.RenderTransform>
        <Border Background="AntiqueWhite"  BorderBrush="DarkGray" BorderThickness="3" CornerRadius="10" />
        <Image x:Name="imgFront" Stretch="Fill" Height="100" Width="100" />
    </Grid>
    <Grid x:Name="back" RenderTransformOrigin="0.5,0.5">
        <Grid.RenderTransform>
            <ScaleTransform ScaleX="0"/>
        </Grid.RenderTransform>
        <Border Background="AliceBlue" BorderBrush="DarkGray" BorderThickness="3" CornerRadius="10" />
        <Image x:Name="imgBack" Stretch="Fill" Height="100" Width="100" />
    </Grid>
</Grid>

Now we can add the storyboard to the UserControl.Resources to do the filp animation

 

<UserControl.Resources>
    <Storyboard x:Name="sbFlip">
        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="front"  Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleX)">
            <SplineDoubleKeyFrame KeyTime="00:00:00.2" Value="0"/>
        </DoubleAnimationUsingKeyFrames>
        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00.2" Storyboard.TargetName="back" Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleX)">
            <SplineDoubleKeyFrame KeyTime="00:00:00.4" Value="1"/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
</UserControl.Resources>

There are two animations here... the first one targets front, and goes from 00:00:00 to 00:00:00.2 (200 milliseconds), and in that time it will ScaleX from the current value down to 0.
The second one goes from 00:00:00.2 to 00:00:00.4 (also 2 milliseconds) and will expand back by scaling x wise from the current value to 1 (full size).

sbReverse looks exactly the same, except for that front and back are reversed.

In order to be able to configure the front image, back image and to be able to call Flip and Reverse from the app we can add the following methods and properties for the class  (note:  you need to add a using statement for System.Windows.Media.Imaging for the BitmapImage)

 

public partial class FlipImage : UserControl
{
    public bool Reversed = false;

    public string FrontImage
    {
        set
        {
            imgFront.Source = new BitmapImage(new Uri(value, UriKind.Relative));
        }
        get
        {
            return imgFront.Source.ToString();
        }
    }

    public string BackImage
    {
        set
        {
            imgBack.Source = new BitmapImage(new Uri(value, UriKind.Relative));
        }
        get
        {
            return imgBack.Source.ToString();
        }
    }

    public FlipImage()
    {
        InitializeComponent();
        sbFlip.Completed += new EventHandler(sbFlip_Completed);
        sbReverse.Completed += new EventHandler(sbReverse_Completed);
    }

    void sbReverse_Completed(object sender, EventArgs e)
    {
        Reversed = false;
    }

    void sbFlip_Completed(object sender, EventArgs e)
    {
        Reversed = true;
    }

    public void Flip()
    {
        if (!Reversed)
        {
            sbFlip.Begin();
        }
    }

    public void Reverse()
    {
        if (Reversed)
        {
            sbReverse.Begin();
        }
    }
}

If we want to create a menu similar to the one they use on the Microsoft Italy Student page, you can add the images to our application like this

 

<StackPanel x:Name="LayoutRoot" Orientation="Horizontal" Background="White">       
    <my:FlipImage x:Name="Home" Tag="http://blogs.msdn.com/tess/default.aspx" FrontImage="Images/Home.png" BackImage="Images/Description.png" RenderTransformOrigin="0.5,0.5" Margin="10,0,0,0">
        <my:FlipImage.RenderTransform>
            <RotateTransform Angle="5"/>
        </my:FlipImage.RenderTransform>
    </my:FlipImage>
    <my:FlipImage x:Name="Contact" Tag="http://blogs.msdn.com/tess/contact.aspx" FrontImage="Images/Contact.png" BackImage="Images/Description.png" RenderTransformOrigin="0.5,0.5">
        <my:FlipImage.RenderTransform>
            <RotateTransform Angle="-3"/>
        </my:FlipImage.RenderTransform>
    </my:FlipImage>
...

(In order to be able to use <my:… you need to add a reference to your assembly in the user control definition, eg. xmlns:my="clr-namespace:FlipMenu")

The result looks something like this:

I added a rotate transform to make the menu look pretty, and a Tag that we can use in the mouseleftbuttondown to navigate to the link. The tag is just a property of any control that can be used to store any data you want related to the control.

And the code for this menu page is then extremely simple... we just flip on mouseenter, reverse on mouseleave and navigate on mouseleftbutton down:

 

Public Page()
{
    InitializeComponent();
    Info.MouseEnter += new MouseEventHandler(MenuMouseEnter);
    Contact.MouseEnter += new MouseEventHandler(MenuMouseEnter);
    Home.MouseEnter +=new MouseEventHandler(MenuMouseEnter);

    Home.MouseLeave += new MouseEventHandler(MenuMouseLeave);
    Info.MouseLeave += new MouseEventHandler(MenuMouseLeave);
    Contact.MouseLeave += new MouseEventHandler(MenuMouseLeave);

    Home.MouseLeftButtonDown += new MouseButtonEventHandler(MenuMouseLeftButtonDown);
    Info.MouseLeftButtonDown += new MouseButtonEventHandler(MenuMouseLeftButtonDown);
    Contact.MouseLeftButtonDown += new MouseButtonEventHandler(MenuMouseLeftButtonDown);
}

void MenuMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    FlipImage mi = sender as FlipImage;
    HtmlPage.Window.Navigate(new Uri(mi.Tag.ToString()));
}

void MenuMouseLeave(object sender, MouseEventArgs e)
{
    FlipImage mi = sender as FlipImage;
    if(mi.Reversed)
        mi.Reverse();
}

void MenuMouseEnter(object sender, MouseEventArgs e)
{
    FlipImage mi = sender as FlipImage;
    if(!mi.Reversed)
        mi.Flip();
}

And that is all there is to it...

Btw, you need to check out Nikhils Silverlight.FX project with lots of UI goodies.  He has a control in there that does the flip shown above.

Until next time,
Tess

  • PingBack from http://www.clickandsolve.com/?p=23570

  • I was working on some Silverlight samples and needed an image that could flip over as in the example

  • Hmmm seems like the easiest solution is to use flash... or even a simple javascript...

  • There's a small hiccup.

    The 'Reversed' is set to true only when the Flip is completed, so if the mouse leaves the element before the animation is completed than it's stays fliped.

    I guess setting the 'Reversed' to true on the started event might fix it.

    Thanks for the example.

  • Good post, Tess.

    For those doing WPF, you can also check out Josh Smith's Thriple project (http://thriple.codeplex.com/) which does a similar thing, but in 3D.

  • Hi,

    Please do a mouse click (left button) and drag on those 3 images that you have drawn above and see the result. It will show your blog home page instead of those image.

    Thanks.

  • Hi,

    Please do a mouse click (left button) and drag on those 3 images that you have drawn above and see the result. It will show your blog home page instead of those images.

    Thanks.

  • <nit>

    0.2 seconds is 200 milliseconds, not 2.

    </nit>

  • "sbFlip.Begin(); "

    This is the big problem we have with silverlight.  *WHY* can you not trigger animations via bound properties?   According to the visual blend/design guru/etc mantra, the developer should NEVER know NOR care what the UI is doing or looking like.

    WHat if the designer removes that animation later?  Or adds three more animations?

    PROPER bound property trigger support (like WPF claims to have but doesn't actually work) would make this useful, but how is it a good thing to be tying 100% UI animations to  code?

    I won't say who I work for, but we had a MS UCD evangelist come speak to us for a full day.  He went on and on about how great XAML is because it lets your designers hand a base UI to developers to use, then refine it and add all the "bling" later.  While being able to change STYLES without "develper" intervention is nice, animations only being triggered by code is useless for the model MS themselves are advocating.

  • Thorntod,  

    True... changed

    Mandeep, thats the idea... normally you would refresh the whole page but since this is an embedded iframe it looks a little funky in the sample...

    And Aleho, yepp about the hickup, not exactly sure how to fix it.  The main point was to show the flip though...

  • You are voted (great) - Trackback from Web Development Community

  • Or you could have just looked at this =]

    http://www.mydotnetplayground.nl/menu/default.aspx

  • There's no such word as towards  ;-)

  • > not exactly sure how to fix it

    Add a second bool to remember the "wanted state".

    Then check the "current state" against the "wanted state" in the completion handler, and immediately re-start the animation is they don't match.

  • InteXX, there is now:)  just invented it

Page 1 of 2 (19 items) 12
Leave a Comment
  • Please add 6 and 5 and type the answer here:
  • Post