Home Course Index << Prev Next >> PDF Version of this Page

Course 2D_SL: 2D-Computer Graphics with Silverlight
Chapter C5: The Complete Code of the PathAnimation Project


Copyright © by V. Miszalok, last update: 15-07-2009

Mail me...
Let me know
what you think
  Preliminaries
  Page.XAML
  Page.xaml.cs

Install 1) Visual Web Developer 2010 Express Edition English
and    2) Silverlight 4 Tools for Visual Studio 2010.

 

Preliminaries

Guidance for Visual Web Developer 2010 Express:

1) Main Menu after start of VWD Express: Tools → Options → check lower left checkbox: Show all Settings →
→ Projects and Solutions → Projects location: → C:\temp.
→ Text Editor (double click) → All Languages (double click) → Tabs →
  Indenting: None → Tab size: 2 → Insert spaces
.
→ Text Editor (double click) → C# (double click) → Formatting → uncheck all three check boxes → OK.
→ Text Editor (double click) → XAML (double click) → Tabs →
  Indenting: None → Tab size: 1, Indent size: 1 → Insert spaces
.
→ Text Editor (double click) → XAML (double click) → Formatting → uncheck all Auto-Formatting Events → OK.

2) Main Menu after start of VWD Express: File → New Project... → Project types: Visual C# (double click) → Silverlight → Templates: Silverlight Application
Name: SL_path1 → Location: C:\temp\SLProjects → Create directory for solution:
switch off → OK.
An Add Silverlight Application-Window appears.
Choose "Automatically generate a test page to host Silverlight at build time" → OK.

In the Solution Explorer - SL_path1 click the branch Page.xaml. A split window will appear containing the default xaml code. Delete this code completely and replace it by the code of Page.xaml which follows.

 
 

Page.xaml

<UserControl
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	x:Class="path1.Page"
	Width="540" Height="225">
  <UserControl.Resources>
    <Storyboard x:Name="Storyboard1"/>
  </UserControl.Resources>

  <Border BorderBrush="Black" BorderThickness="2">
    <StackPanel Orientation="Vertical">
      <Canvas x:Name="LayoutRoot" Background="Cornsilk" Height="200">
        <Path Data ="M 10,50 h 10 v 100 h 25 v -100                            h 10
                     m 10, 0 h 10 l 12.5,100 l 12.5,-100                       h 10
                     m 10, 0 h 10 a 12.5,100 0 0 0  25,0                       h 10
                     m 10, 0 h 10 q 12.5,100,       25,0                       h 10
                     m 10, 0 h 10 c  0  ,100,25,100,25,0                       h 10
                     m 10, 0 h 10 c  0  ,100,25,100,25,0 c 0, 100,25, 100,25,0 h 10
                     m 10,50 h 10 c  0  ,100,25,100,25,0 c 0,-100,25,-100,25,0 h 10    
                     m 10, 0 h 10 c  0  ,100,25,100,25,0 s        25,-100,25,0 h 10"
              x:Name="PathThickBlack" Stroke="Black" StrokeThickness="5"/>
        <Path x:Name="PathThinGreen"  Stroke="Green" StrokeThickness="2" Loaded="PathThinGreenLoaded">
          <Path.Data>
            <PathGeometry>
              <PathGeometry.Figures>
                <PathFigure StartPoint=" 10  , 50">
                  <LineSegment   Point=" 20  , 50"/>
                  <LineSegment   Point=" 20  ,150"/>
                  <LineSegment   Point=" 45  ,150"/>
                  <LineSegment   Point=" 45  , 50"/>
                  <LineSegment   Point=" 55  , 50"/>
                </PathFigure>
                <PathFigure StartPoint=" 65  , 50">
                  <LineSegment   Point=" 75  , 50"/>
                  <LineSegment   Point=" 87.5,150"/>
                  <LineSegment   Point="100  , 50"/>
                  <LineSegment   Point="110  , 50"/>
                </PathFigure>
                <PathFigure StartPoint="120  , 50">
                  <LineSegment   Point="130  , 50"/>
                  <ArcSegment    Point="155  , 50" Size="12.5,100"/>
                  <LineSegment   Point="165  , 50"/>
                </PathFigure>
                <PathFigure StartPoint="175  , 50">
                  <LineSegment   Point="185  , 50"/>
                  <QuadraticBezierSegment Point1="197.5,150" Point2="210,50"/>
                  <LineSegment   Point="220  , 50"/>
                </PathFigure>
                <PathFigure StartPoint ="230, 50">
                  <LineSegment   Point ="240, 50"/>
                  <BezierSegment Point1="240,150" Point2="265,150" Point3="265,50"/>
                  <LineSegment   Point ="275, 50"/>
                </PathFigure>
                <PathFigure StartPoint ="285, 50">
                  <LineSegment   Point ="295, 50"/>
                  <BezierSegment Point1="295,150" Point2="320,150" Point3="320,50"/>
                  <BezierSegment Point1="320,150" Point2="345,150" Point3="345,50"/>
                  <LineSegment   Point ="355, 50"/>
                </PathFigure>
                <PathFigure StartPoint ="365,100">
                  <LineSegment   Point ="375,100"/>
                  <BezierSegment Point1="375,200" Point2="400,200" Point3="400,100"/>
                  <BezierSegment Point1="400,  0" Point2="425,  0" Point3="425,100"/>
                  <LineSegment   Point ="435,100"/>
                </PathFigure>
                <PathFigure     StartPoint ="445,100">
                  <LineSegment       Point ="455,100"/>
                  <PolyBezierSegment Points="455,200 480,200 480,100 
                                             480,  0 505,  0 505,100"/>
                  <LineSegment       Point ="515,100"/>
                </PathFigure>
              </PathGeometry.Figures>
            </PathGeometry>
          </Path.Data>
        </Path>
        <Path x:Name="PathDottedRed" Stroke="Red" StrokeThickness="5" Loaded="PathDottedRedLoaded">
          <Path.Data><PathGeometry/></Path.Data>
        </Path>
      </Canvas>
      <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
        <Button Content="Start" Click="start_Button_Click"/>
        <Button Content="Stop"  Click="stop_Button_Click" />
        <TextBlock Text="Velocity: "   VerticalAlignment="Center" Margin="20,0,0,0"/>
        <Slider   x:Name="velocity_slider" Width="80"
                  Minimum="100" Maximum="4000" IsDirectionReversed="True" Value="1000" 
                  ValueChanged="VelocitySliderValueChanged"/>
        <CheckBox x:Name="Red_Dots"    VerticalAlignment="Center" Margin="20,0,0,0"
                  Content  ="Red Dots" 
                  Checked  ="ShowDottedRedPath" 
                  Unchecked="HideDottedRedPath"/>
        <CheckBox x:Name="BlackCurves" VerticalAlignment="Center" Margin="20,0,0,0"
                  Content  ="Black Curves" 
                  Checked  ="ShowThickBlackPath" 
                  Unchecked="HideThickBlackPath"/>
      </StackPanel>
    </StackPanel>
  </Border>
</UserControl>
 

Page.xaml.cs

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace path1
{	public partial class Page : UserControl
	{ PointCollection pp = new PointCollection();
    public Page()
		{ InitializeComponent();
      PathThickBlack.Visibility = Visibility.Collapsed;
      PathDottedRed .Visibility = Visibility.Collapsed;
		}

    //This initial event occurs just once.
    //It copies all 53 vertices to the intermediary dynamic array pp and 
    //animates 7 of the 29 segments of PathThinGreen 
    //by calling the subroutine add_points_to_pp_and_to_Storyboard1(..) 
    private void PathThinGreenLoaded( object sender, EventArgs e ) 
    { PathGeometry PG = (PathGeometry)PathThinGreen.Data;
      //read curves from XAML and buffer all vertices to pp
      for ( int i=0; i < PG.Figures.Count; i++ )
      { PathFigure PF = PG.Figures[i];
        pp.Add( PF.StartPoint );
        for ( int j=0; j < PF.Segments.Count; j++ )
          add_points_to_pp_and_to_Storyboard1( PF.Segments[j], j, i );
      } 
    }

    //This initial event occurs just once.
    //It creates and animates the tiny red points of PathDottedRed
    //It copies any vertex into PathDottedRed and animates the lower vertices by
    //calling the subroutine add_a_PointAnimation_to_Storyboard1(...) 
    private void PathDottedRedLoaded( object sender, EventArgs e )
    { PathGeometry PG = (PathGeometry)PathDottedRed.Data;
      //All red "points" are packed inside a Path named PathDottedRed
      //of PathFigures each carrying one tiny horizontal LineSegment 
      foreach ( Point p in pp )
      { PathFigure  PF = new PathFigure();
        LineSegment LS = new LineSegment();
        PF.StartPoint = new Point( p.X-2, p.Y );
        LS.Point      = new Point( p.X+2, p.Y );
        PF.Segments.Add( LS );
        PG.Figures .Add( PF  );
      }
      //This loop animates the lower "red points"
      //Storyboard1 will contain such a lower point triply:
      //1. as animated vertex of PathThinGreen (done by the first initial event handler)
      //2. as animated left  border of a 5-pixel horizontal line of PathDottedRed
      //3. as animated right border of a 5-pixel horizontal line of PathDottedRed
      for ( int i=0; i < pp.Count; i++ )
      {  if ( pp[i].Y > 100 )
         { //The LineSegments have a start and an end point with a horizontal distance = 5
           Point p_left  = new Point( pp[i].X-2, pp[i].Y ); 
           Point p_right = new Point( pp[i].X+2, pp[i].Y );
           String s1 = "(Path.Data).(PathGeometry.Figures)[" +
                        i.ToString() + "].(PathFigure.StartPoint)";
           String s2 = "(Path.Data).(PathGeometry.Figures)[" +
                        i.ToString() + "].(PathFigure.Segments)[0].(LineSegment.Point)";
           add_a_PointAnimation_to_Storyboard1( PathDottedRed, p_left , s1 );
           add_a_PointAnimation_to_Storyboard1( PathDottedRed, p_right, s2 );
        }
      }
    }

    //This subroutine is called from the initial event handler PathThinGreen
    //It adds any vertex to the intermediary dynamic array pp and 
    //animates 7 of the 29 segments of PathThinGreen  
    private void add_points_to_pp_and_to_Storyboard1( PathSegment PS, int NoOfPS, int NoOfPF )
    { String s1 = "(Path.Data).(PathGeometry.Figures)[" + NoOfPF.ToString() + 
                             "].(PathFigure.Segments)[" + NoOfPS.ToString() + "].";
      String s; //complete PropertyPath-string aimed to feed Storyboard.SetTargetProperty(...)
      Point  p; 
      if ( PS.GetType() == typeof(LineSegment) )
      { p = ((LineSegment)PS).Point; pp.Add( p );
        if ( p.Y > 100 )
        { s = s1 + "(LineSegment.Point)";
          add_a_PointAnimation_to_Storyboard1( PathThinGreen, p, s );
        }
      }
      else if ( PS.GetType() == typeof(ArcSegment) )
      { ArcSegment AS = ((ArcSegment)PS);
        p = new Point( AS.Point.X-AS.Size.Width, AS.Point.Y+AS.Size.Height ); 
        pp.Add( p );
        //The Size.Height property of an ArcSegment cannot be animated 
      }
      else if ( PS.GetType() == typeof(QuadraticBezierSegment) )
      { p = ((QuadraticBezierSegment)PS).Point1; pp.Add( p );
        if ( p.Y > 100 )
        { s = s1 + "(QuadraticBezierSegment.Point1)";
          add_a_PointAnimation_to_Storyboard1( PathThinGreen, p, s );
        }
        p = ((QuadraticBezierSegment)PS).Point2; pp.Add( p );
      }
      else if ( PS.GetType() == typeof(BezierSegment) )
      { p = ((BezierSegment)PS).Point1; pp.Add( p );
        if ( p.Y > 100 )
        { s = s1 + "(BezierSegment.Point1)";
          add_a_PointAnimation_to_Storyboard1( PathThinGreen, p, s );
        }
        p = ((BezierSegment)PS).Point2; pp.Add( p );
        if ( p.Y > 100 )
        { s = s1 + "(BezierSegment.Point2)";
          add_a_PointAnimation_to_Storyboard1( PathThinGreen, p, s );
        }
        p = ((BezierSegment)PS).Point3; pp.Add( p );
      }
      else if ( PS.GetType() == typeof(PolyBezierSegment) )
        foreach ( Point point in ((PolyBezierSegment)PS).Points ) pp.Add( point );
        //Points inside a PolyBezierSegment cannot be animated 
    }

    //This subroutine is called from add_points_to_pp_and_to_Storyboard1(...) and
    //directly from the second initial event handler PathDottedRedLoaded(...)
    //It sets the common properties that all 42 animations share.
    private void add_a_PointAnimation_to_Storyboard1( Path path, Point p, string s )
    { PointAnimation PA = new PointAnimation();
      PA.To = new Point ( p.X, p.Y-150 );
      PA.Duration = TimeSpan.FromMilliseconds( 1000 );
      PA.AutoReverse = true;
      PA.RepeatBehavior = RepeatBehavior.Forever;
      Storyboard.SetTarget        ( PA, path );
      Storyboard.SetTargetProperty( PA, new PropertyPath( s ) );
      Storyboard1.Children.Add    ( PA );
    }

    private void start_Button_Click( object sender, EventArgs e )
    { Storyboard1.Begin(); }

    private void stop_Button_Click( object sender, EventArgs e )
    { Storyboard1.Stop(); }

    private void ShowDottedRedPath( object sender, EventArgs e )
    { PathDottedRed.Visibility = Visibility.Visible; }

    private void HideDottedRedPath( object sender, EventArgs e )
    { PathDottedRed.Visibility = Visibility.Collapsed; }

    private void ShowThickBlackPath( object sender, EventArgs e )
    { PathThickBlack.Visibility = Visibility.Visible; }

    private void HideThickBlackPath( object sender, EventArgs e )
    { PathThickBlack.Visibility = Visibility.Collapsed; }

    private void VelocitySliderValueChanged( object sender, EventArgs e )
    { double millisec = 1000;
      try { millisec = velocity_slider.Value; } catch { return; }
      foreach ( PointAnimation PA in Storyboard1.Children )
        PA.Duration = TimeSpan.FromMilliseconds( millisec );
    } //end of private void on_velocity_slider_value_changed( ... )
  }   //end of class Page
}     //end of namespace
 

Test

Get Microsoft Silverlight
top of page: