Wahlin on .NET

Animating Clipping Paths in Silverlight 2

Dan walks through building a "sports score scroller" to show clipping paths in action.

Clipping paths provide a way to hide sections of an object based on different geometries. You can apply rectangles, ellipses or even create your own custom paths to a target object and hide specific areas from view.

I enjoy experimenting with things from time to time and decided to try creating a sports score scroller -- similar to this one found at the top of espn.com -- to see how much could be done with pure XAML. It allows a user to scroll a set of scores left or right by moving their mouse over corresponding arrows.

While building the prototype, I realized that I needed a rectangle animation to move a rectangle around to show and hide different parts of score objects. As the user scrolls left, I would adjust the rectangle's position to show the hidden area and then hide that area again as they scroll right.

WPF provides a RectAnimation object but, unfortunately, it's not available in Silverlight. That meant I had to use points instead and then use a PointAnimation to animate them (the points are shown below in the LineSegment elements). By doing that, I can simulate a rectangle animation. Here's an example of the main XAML for the sports score scroller.

<Canvas x:Name="cScroller" Height="45" Width="600" 
  Background="#efefef" Visibility="Collapsed">
    <Image x:Name="imgLeft" Source="/LeftArrow.jpg" 
     Canvas.ZIndex="10"  Canvas.Left="0" 
     MouseEnter="imgLeft_MouseEnter" 
     MouseLeave="imgLeft_MouseLeave" />
    <Canvas x:Name="cScrollerItems" Height="45" 
     Canvas.Left="10" Width="898">
        <Image Source="/Scores1.jpg" Canvas.Left="0" />
        <Image Source="/Scores2.jpg" Canvas.Left="596" />
        <Canvas.Clip>
                <PathGeometry>
                    <PathFigure IsClosed="true">
                        <LineSegment x:Name="PathSeg1" 
                              Point="0,0"/>
                        <LineSegment x:Name="PathSeg2" 
                              Point="596,0"/>
                        <LineSegment x:Name="PathSeg3" 
                              Point="596,45"/>
                        <LineSegment x:Name="PathSeg4"
                              Point="0,45"/>
                    </PathFigure>
                </PathGeometry>
            </Canvas.Clip>
        </Canvas>
    <Image x:Name="imgRight" Source="/RightArrow.jpg" 
     Canvas.ZIndex="10" Canvas.Left="605"
     MouseEnter="imgRight_MouseEnter" 
     MouseLeave="imgRight_MouseLeave"/>
</Canvas>

This XAML displays two JPG images containing scores and two JPG images representing arrows -- yes, all of this could be done with XAML instead of images but my goal was to get something simple working quickly -- but only shows 596 pixels total for the width of the score images. Anything to the right of that number is clipped due to the PathFigure that's defined and corresponding LineSegment elements. If you're new to PathGeomery and clipping, just imagine a rectangle starting at 0,0 that has a height of 45 and a width of 596. Everything to the right of the rectangle is clipped when the sports scroller first loads (in the image, that means everything to the right of the red right arrow is clipped).

As users mouse over one of the arrows, I need to move the Canvas holding the sports score images (cScrollerItems) to the right or left. However, I also need to move the right line of the clipping rectangle (the line made from 596,0 to 596,45) right or left to show the hidden area of the image. Fortunately, this is easy to do with a Storyboard element and a PointAnimation. Here are the animations that are called as the user mouses over the left or right arrows.

<Storyboard x:Name="sbScrollerLeft">
    <DoubleAnimation Storyboard.TargetName="cScrollerItems" 
     Storyboard.TargetProperty="(Canvas.Left)"
     Duration="00:00:3" To="-293" />
    <PointAnimation Storyboard.TargetName="PathSeg2" 
     Storyboard.TargetProperty="Point" To="898,0" 
     Duration="00:00:3" />
    <PointAnimation Storyboard.TargetName="PathSeg3" 
     Storyboard.TargetProperty="Point" To="898,45" 
     Duration="00:00:3" />
</Storyboard>
<Storyboard x:Name="sbScrollerRight">
    <DoubleAnimation Storyboard.TargetName="cScrollerItems" 
     Storyboard.TargetProperty="(Canvas.Left)"
     Duration="00:00:3" To="10" />
    <PointAnimation Storyboard.TargetName="PathSeg2" 
     Storyboard.TargetProperty="Point" To="605,0" 
     Duration="00:00:3" />
    <PointAnimation Storyboard.TargetName="PathSeg3" 
     Storyboard.TargetProperty="Point" To="605,45" 
     Duration="00:00:3" />
</Storyboard>

The code shone here is called to begin, pause and stop animations as the MouseEnter and MouseLeave events are fired:

private void imgLeft_MouseEnter(object sender, 
   MouseEventArgs e)
{
    this.sbScrollerLeft.Begin();
}

private void imgLeft_MouseLeave(object sender, 
   MouseEventArgs e)
{
    this.sbScrollerLeft.Pause();
}

private void imgRight_MouseEnter(object sender, 
   MouseEventArgs e)
{
    decimal left = GetItemsCanvasLeft();
    Debug.WriteLine(left.ToString());
    //Don't move anything if the ticker has 
    //never been moved left at this point
    if (left == _ItemsCanvasInitialLeftValue) return;
    this.sbScrollerRight.Begin();
}

private void imgRight_MouseLeave(object sender, 
   MouseEventArgs e)
{
    this.sbScrollerRight.Pause();
}

private decimal GetItemsCanvasLeft()
{
    return decimal.Parse(
      this.cScrollerItems.GetValue(Canvas.
      LeftProperty).ToString());
}

All of this works great from a prototype perspective but you can see that specific points are hard-coded into the animation elements. My goal was to do as much as possible in XAML without writing C# code, but with a little extra code it would be fairly straightforward to dynamically change the widths defined in the XAML based on the size of the images being displayed (or the width of text if that was used instead).

A lot more could be done to enhance the scroller, but it's nice to be able to create something like this with minimal code in a short period of time. An example of the scroller in action can be seen here (WMV video).

About the Author

Dan Wahlin (Microsoft MVP for ASP.NET and XML Web Services) is the founder of The Wahlin Group which specializes in .NET and SharePoint onsite, online and video training and consulting solutions. Dan also founded the XML for ASP.NET Developers Web site, which focuses on using ASP.NET, XML, AJAX, Silverlight and Web Services in Microsoft's .NET platform. He's also on the INETA Speaker's Bureau and speaks at conferences and user groups around the world. Dan has written several books on .NET including "Professional Silverlight 2 for ASP.NET Developers," "Professional ASP.NET 3.5 AJAX, ASP.NET 2.0 MVP Hacks and Tips," and "XML for ASP.NET Developers." Read Dan's blog here.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube