This article is obsolete. Some functionalities might not work anymore. Comments are disabled.
Ok, as the latest of three steps which have seen the growth of this Tweening API for Silverlight 2.0,
I can claim that the one described in this article is some sort of basic/ultimate version,
up to this point:
- Tweener can target any DependencyProperty of any DependencyObject, with the limitation that the underlying
property type must be a numeric type, a Color or a Point.
As you'll see this "limitation" groups all the DependencyProperties that, in Silverlight, can sensibly pretend to be tweened.
- Tweener has now a centralized handling of the Clock and its logic: that means there's just one DispatcherTimer
for the entire application which takes care to trigger each changing step of each
interpolated object; and it even stops by itself when there's ...nothing to do! (In my
previous version
one timer per type of DependencyProperty needed to be instantiated). To obtain this I think I've been coding the best polymorphic object of my developer's «career»; that was fun!
- Tweener can handle Stop, Pause, Resume and Rewind actions. That gives perfect grip to the developer upon each tweened object.
Specific handlers can be attached to listen to the key events that a tween broadcasts (MotionChanged is likely the most important and suggestive one).
New poster of the Tweening system is pictured below (please compare it to the one posted here to see how the project grew since its very beginning).
...and yes! There's also a demo available:
I prepared a dummy sample showing how the Tweener works (sample that I will likely improve sooner or later); you've just to click on the following screenshot to start it, but
let me introduce and describe it first:
- there's a rotating canvas with an embedded path geometry that has my baby face as its ImageBrush background. (!)
Tween is applied, with bounce easing, to this canvas so that it rotates by 360 degrees back and forth alternatively (Yoyo FinishedBehavior is applied, it automatically
rewinds the motion as it finishes). You can toggle Play and Pause statuses for this tween using the provided button on top of the UI.
- The Stroke of the path geometry is a SolidColorBrush whose ColorProperty can be tweened just by selecting
the apposite listbox on the top left corner. You'll see the color of the Stroke blending linearly from the actual value to the selected one.
- One of the points that delimitate the path geometry can be tweened in its X an Y components by triggering
the tiny sliders provided. With this third feature, the Tweener(Of Point) capabilities are also demonstrated.
Let us take a sneak peek to the full XAML and C# code used in this demo App.
XAML:
(Please note that a MatrixTransform is the starting RenderTransform value for the Canvas cnv, just to demonstrate that no informations
about the original status of an object get lost as a tween motion gets applied).
<UserControl x:Class="TestSilverlightApplication.Page"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="600" Height="600" Loaded="UserControl_Loaded">
<Grid x:Name="LayoutRoot" Background="#ffffffff" MouseLeftButtonUp="LayoutRoot_MouseLeftButtonUp">
<Border x:Name="brd" CornerRadius="10,10,10,10" BorderThickness="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
>
<Border.Background>
<LinearGradientBrush EndPoint="0.5,0.5" StartPoint="1,0.5" SpreadMethod="Reflect">
<GradientStop Color="#FF3d3d3d" Offset="0"/>
<GradientStop Color="#ff8A9378" Offset="1"/>
</LinearGradientBrush>
</Border.Background>
<Border.BorderBrush>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0" SpreadMethod="Reflect">
<GradientStop Color="#FFD8DFD7"/>
<GradientStop Color="#FF262B2E" Offset="1"/>
</LinearGradientBrush>
</Border.BorderBrush>
<Canvas x:Name="cnv" VerticalAlignment="Center" HorizontalAlignment="Center" Background="#ffc58263">
<Canvas.RenderTransform>
<MatrixTransform Matrix=".5,.5,-.866,.866,-5,-90" />
</Canvas.RenderTransform><Path Stroke="#FFc0c0c0" StrokeThickness="4" x:Name="path">
<Path.Fill>
<ImageBrush ImageSource="images/CM.jpg" TileMode="None" Stretch="Fill" />
</Path.Fill>
<Path.Data>
<PathGeometry>
<PathFigure StartPoint="310,50">
<LineSegment Point="0,50" />
<LineSegment Point="0,180" />
<LineSegment Point="150,180" x:Name="seg" />
<LineSegment Point="310,50" />
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
</Canvas>
</Border>
<TextBlock x:Name="txt" TextWrapping="NoWrap" Visibility="Collapsed"
Text="debug:" Width="Auto" Foreground="Red" FontSize="11" FontFamily="Courier New" />
<StackPanel Orientation="Horizontal" Margin="10,10,0,0">
<StackPanel Orientation="Vertical" Margin="0,0,10,0">
<TextBlock Text="Border Color" Foreground="White" FontSize="11" />
<ListBox x:Name="ddl" Width="100" SelectionChanged="ddl_SelectionChanged"
HorizontalAlignment="Left" VerticalAlignment="Top" MinHeight="32">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Key}" FontSize="11" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox></StackPanel>
<StackPanel Orientation="Vertical" Margin="0,0,10,0">
<TextBlock Text="Corner Point (X, Y)" Foreground="White" FontSize="11" />
<Slider Minimum="1" Maximum="410" x:Name="sldX" Value="150" ValueChanged="sld_ValueChanged"></Slider>
<Slider Minimum="1" Maximum="200" x:Name="sldY" Value="180" ValueChanged="sld_ValueChanged"></Slider>
</StackPanel>
<StackPanel Orientation="Vertical" Margin="0,0,10,0">
<TextBlock Text="Yoyo Rotation" Foreground="White" FontSize="11" />
<Button Content="pause" Click="Button_Click" />
</StackPanel>
</StackPanel>
</Grid>
</UserControl>
C#:
public partial class Page : UserControl
{
public Page()
{
InitializeComponent();
ddl.ItemsSource = this.ColorList;
}
Tween<double> tween = null;
Tween<Color> tweenColor = null;
Tween<Point> tweenPoint = null;
bool resumed = false;
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
tween = Tweener.CreateTween(cnv, TweenProperty.Angle, Bounce.EaseOut, 0, 360, TimeSpan.FromSeconds(3D));
tween.FinishedBehavior = ClockFinishedBehavior.Yoyo;
}
void tween5_MotionFinished(object sender, EventArgs e)
{
resumed = true;
Tweener<double>.Resume(tween);
}
void tween_MotionChanged(object o, TweenMotionChangedEventArgs<double> e)
{
txt.Text += string.Format("\n[{0:T}] value {1}", e.Elapsed, e.CurrentPosition);
Tween<double> sender = (Tween<double>)o;
if (!resumed && .5D * sender.Duration.TotalSeconds < e.Elapsed.TotalSeconds) Tweener<double>.Pause(sender);
}
void tween_MotionFinished(object sender, EventArgs e)
{
txt.Text = string.Format("\n[{0:T}] Finished!\ntrans children: {1}", DateTime.Now, ((TransformGroup)((UIElement)((ITween<double>)sender).Object).RenderTransform).Children.Count);
}
void tween_MotionStarted(object sender, EventArgs e)
{
txt.Text += string.Format("\n[{0:T}] Started!", DateTime.Now);
}
private void LayoutRoot_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
List<Transform> coll = ((TransformGroup)cnv.RenderTransform).Children.ToList();
txt.Text = string.Format("\ntrans children: {0}", coll.Count);
coll.ForEach(delegate(Transform item)
{
txt.Text += string.Format("\n type: {0}", item.GetType());
});
}
private void ddl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (tweenColor != null) Tweener<Color>.Stop(tweenColor, true);
tweenColor = Tweener<Color>.CreateTween((SolidColorBrush)path.Stroke, SolidColorBrush.ColorProperty, Linear.EaseNone,
((SolidColorBrush)path.Stroke).Color, GetSelectedColor(), TimeSpan.FromSeconds(3D));
}
private Color GetSelectedColor()
{
int j = 0;
foreach (KeyValuePair<string, Color> kvp in ColorList)
{
if (j == ddl.SelectedIndex) return kvp.Value;
j++;
}
return new Color();
}
private Dictionary<string, Color> _List = null;
private Dictionary<string, Color> ColorList
{
get
{
if (_List == null){
_List = new Dictionary<string,Color>(5);
_List.Add("Gray", Colors.Gray);
_List.Add("Red", Colors.Red);
_List.Add("Blue", Colors.Blue);
_List.Add("Green", Colors.Green);
_List.Add("Yellow", Colors.Yellow);
}
return _List;
}
}
private void sld_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
Point tget = new Point(sldX.Value, sldY.Value);
if (tweenPoint != null) Tweener<Point>.Stop(tweenPoint, true);
tweenPoint = Tweener<Point>.CreateTween(seg, LineSegment.PointProperty, Expo.EaseOut, seg.Point, tget, TimeSpan.FromSeconds(2.8D), TimeSpan.FromSeconds(1D));
}
private void Button_Click(object o, RoutedEventArgs e)
{
Button sender = (Button)o;
if (tween.Status == ClockStatus.Paused)
{
Tweener<double>.Resume(tween);
sender.Content = "pause";
}
else
{
Tweener<double>.Pause(tween);
sender.Content = "play";
}
}
}
Last, but obviously not least, keep in mind that these Tweening classes can be easily copied and pasted into a big-daddy WPF
class library (already done). I'm now about to experience what can be done when you unleash'em onto, ...let's say a
ModelVisual3D object!
...my mouth's foaming...
Stuff available for download:
- Visual Studio 2008 Projects (Tweener code + sample application).
- DLL library accompanied with Help documentation built using Sandcastle Help File Builder (great tool btw!).
« download source code
« download DLL + CHM
Take care. Bye.