How do I create a translation / move storyboard dynamically through code? - silverlight

I am trying to create the following storyboard in code:
<Storyboard x:Name="m_activateIdentityStoryboard">
<DoubleAnimationUsingKeyFrames
Storyboard.TargetProperty=
"(UIElement.RenderTransform).(CompositeTransform.TranslateY)"
Storyboard.TargetName="image">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="-22"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
I have tried the following:
Storyboard board = new Storyboard();
Storyboard.SetTarget(board, view);
Storyboard.SetTargetProperty(board,
new PropertyPath(CompositeTransform.TranslateYProperty));
DoubleAnimation upAnim = new DoubleAnimation()
{
Duration = new Duration(TimeSpan.FromMilliseconds(200)),
From = 0,
To = -22,
RepeatBehavior = new RepeatBehavior(1)
};
board.Children.Add(upAnim);
But it does nothing. I'm pretty sure I specifying the wrong PropertyPath, but I don't know what I'm supposed to put in it, or even how I am supposed to research what to put in it. I also dont understand what "(UIElement.RenderTransform).(CompositeTransform.TranslateY)" means and how to translate that into c#.
Thanks!
swine

The correct c# code for your animation should be something like this,
// initialize a new instance of the CompositeTransform which allows you
// apply multiple different transforms to your image
this.image.RenderTransform = new CompositeTransform();
// create the timeline
var animation = new DoubleAnimationUsingKeyFrames();
// add key frames to the timeline
animation.KeyFrames.Add(new EasingDoubleKeyFrame { KeyTime = TimeSpan.Zero, Value = 0 });
animation.KeyFrames.Add(new EasingDoubleKeyFrame { KeyTime = TimeSpan.FromMilliseconds(200), Value = -22 });
// notice the first parameter takes a timeline object not the storyboard itself
Storyboard.SetTargetProperty(animation, new PropertyPath("(UIElement.RenderTransform).(CompositeTransform.TranslateY)"));
Storyboard.SetTarget(animation, image);
// create the storyboard
var storyboard = new Storyboard() { RepeatBehavior = RepeatBehavior.Forever };
// add the timeline to your storyboard
storyboard.Children.Add(animation);
// start the annimation
storyboard.Begin();
I have put in some comments and hopefully they make sense to you. :)

Related

Custom Routed Event in F#

I'm trying to translate this C# code. My attempt so far:
type MyButtonSimple() as self =
inherit Button()
static let TapEvent =
EventManager.RegisterRoutedEvent
( "Tap", RoutingStrategy.Bubble,
typeof<RoutedEventHandler>, typeof<MyButtonSimple>)
let tapEvent = new Event<RoutedEventHandler, RoutedEventArgs>()
let raiseTapEvent() =
let newEventArgs = new RoutedEventArgs(TapEvent)
tapEvent.Trigger(self, newEventArgs)
[<CLIEvent>]
member x.Tap = tapEvent.Publish
// For demonstration purposes we raise the event when clicked
override self.OnClick() =
raiseTapEvent()
MainWindow.xaml
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:funk="clr-namespace:funk;assembly=appWPF"
Title="Routed" Height="300" Width="400">
<StackPanel Background="LightGray">
<funk:MyButtonSimple Content="Spin" Background="#808080" Foreground="LightGray">
<funk:MyButtonSimple.RenderTransform>
<TransformGroup>
<RotateTransform x:Name="rotate"/>
</TransformGroup>
</funk:MyButtonSimple.RenderTransform>
<funk:MyButtonSimple.Triggers>
<EventTrigger RoutedEvent="funk:MyButtonSimple.Tap">
<BeginStoryboard>
<Storyboard RepeatBehavior="Forever">
<DoubleAnimation
Storyboard.TargetName="rotate"
Storyboard.TargetProperty="Angle"
From="0" To="90" Duration="0:0:2"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</funk:MyButtonSimple.Triggers>
</funk:MyButtonSimple>
</StackPanel>
</Window>
Clicking the button doesn't start the animation.
Changing the code RoutedEvent="funk:MyButtonSimple.Tap" to RoutedEvent="GotFocus" gives a working example (Button.Click is overridden).
Any ideas what I'm doing wrong?
This is tricky because it's doing quite a bit of things that aren't standard in F#.
A translation of that C# code would be:
type MyButtonSimple() as self =
inherit Button()
static let tapEvent =
EventManager.RegisterRoutedEvent
( "Tap", RoutingStrategy.Bubble,
typeof<RoutedEventHandler>, typeof<MyButtonSimple>)
// Create a custom event so you can override AddHandler/RemoveHandler behavior
let tapEvent =
{ new IDelegateEvent<RoutedEventHandler> with
member this.AddHandler del = self.AddHandler(MyButtonSimple.TapEvent, del)
member this.RemoveHandler del = self.RemoveHandler(MyButtonSimple.TapEvent, del) }
// Raise via routed eventing strategy
let raiseTapEvent() =
let newEventArgs = new RoutedEventArgs(MyButtonSimple.TapEvent)
self.RaiseEvent newEventArgs
// This isn't exactly the same, but public static fields aren't allowed in F#, and
// this works for WPF
static member TapEvent with get() = tapEvent
[<CLIEvent>]
member x.Tap = tapEvent
// For demonstration purposes we raise the event when clicked
override self.OnClick() =
raiseTapEvent()
The main "gotchas" here are that you need to override the standard event behavior, which requires implementing IDelegateEvent yourself instead of using the normal F# event management. Also, you can't do public static readonly fields in F#, so this wraps the event into a property. WPF seems to be fine with this, even though it's not the "standard" way to implement routed events.

Animate color property with different brushes

I have the following in a ControlTemplate.Resources:
<ColorAnimation
Storyboard.TargetName="border"
Storyboard.TargetProperty="Background.(SolidColorBrush.Color)"
To="Orange"
Duration="0:0:0.2" />
It works all right if the original background that I wanted to change to orange was a solid color. But I'd also want to have this work when the original background is a LinearGradientBrush. In this second case, the animation tries to change the property in vain, nothing happens.
How can I specify an animation that replaces the background no matter what type it was earlier?
If your Background is LinearGradientBrush, then you will have to animate each GradientStop to the Color you want i.e. Orange in this case:
<Storyboard x:Key="Storyboard1">
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="Background.(GradientBrush.GradientStops)[0].(GradientStop.Color)" Storyboard.TargetName="border">
<EasingColorKeyFrame KeyTime="0:0:2" Value="Orange"/>
</ColorAnimationUsingKeyFrames>
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="Background.(GradientBrush.GradientStops)[1].(GradientStop.Color)" Storyboard.TargetName="border">
<EasingColorKeyFrame KeyTime="0:0:2" Value="Orange"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
But if you want to Animate the whole Brush irrespective of its type then you will have to create your own Animation. I have created my own BrushAnimation class to animate the Brush
public class BrushAnimation : AnimationTimeline
{
static BrushAnimation()
{
FromProperty = DependencyProperty.Register("From", typeof(Brush),
typeof(BrushAnimation),new PropertyMetadata(new SolidColorBrush()));
ToProperty = DependencyProperty.Register("To", typeof(Brush),
typeof(BrushAnimation), new PropertyMetadata(new SolidColorBrush()));
}
public override Type TargetPropertyType
{
get
{
return typeof(Brush);
}
}
protected override System.Windows.Freezable CreateInstanceCore()
{
return new BrushAnimation();
}
public static readonly DependencyProperty FromProperty;
public Brush From
{
get
{
return (Brush)GetValue(BrushAnimation.FromProperty);
}
set
{
SetValue(BrushAnimation.FromProperty, value);
}
}
public static readonly DependencyProperty ToProperty;
public Brush To
{
get
{
return (Brush)GetValue(BrushAnimation.ToProperty);
}
set
{
SetValue(BrushAnimation.ToProperty, value);
}
}
public override object GetCurrentValue(object defaultOriginValue,
object defaultDestinationValue, AnimationClock animationClock)
{
Brush fromVal = ((Brush)GetValue(BrushAnimation.FromProperty));
Brush toVal = ((Brush)GetValue(BrushAnimation.ToProperty));
SolidColorBrush solid = toVal as SolidColorBrush;
if(fromVal is LinearGradientBrush)
{
LinearGradientBrush brush = fromVal as LinearGradientBrush;
LinearGradientBrush newBrush = new LinearGradientBrush();
foreach(var stop in brush.GradientStops)
{
ColorAnimation animation = new ColorAnimation(stop.Color,solid.Color,this.Duration);
Color color = animation.GetCurrentValue(stop.Color, solid.Color, animationClock);
newBrush.GradientStops.Add(new GradientStop(color,stop.Offset));
}
return newBrush;
}
else
{
SolidColorBrush brush = fromVal as SolidColorBrush;
SolidColorBrush newsolid = new SolidColorBrush();
ColorAnimation solidAnimation = new ColorAnimation(brush.Color, solid.Color, this.Duration);
newsolid.Color = solidAnimation.GetCurrentValue(brush.Color, solid.Color, animationClock);
return newsolid;
}
}
and I am using this Animation to animate Canvas.Background on my window
<Storyboard x:Key="MyStoryBoard" RepeatBehavior="Forever" AutoReverse="True">
<local:BrushAnimation Storyboard.TargetName="Canvas1"
Storyboard.TargetProperty = "(Canvas.Background)"
To="Orange" Duration="0:0:5"/>
</Storyboard>
and you can set From property of animation using the StaticResource or set it to the Background of Control in your codebehind like:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
((BrushAnimation) ((Storyboard) Resources["SolidStoryBoard"]).Children[0]).From = Canvas1.Background;
}
}

Unable to find child element from XAML

I have a UserControl and trying to find (using FindResource) of DoubleAnimation element in code behind.
Examp:-
<UserControl ....
<Canvas Width="400" Height="400" Loaded="Canvas_Loaded">
<Canvas.Resources>
<Storyboard x:Key="sd" x:Name="sBoard ">
<DoubleAnimation x:Name="SomeAnimation" ...
I am trying to find “SomeAnimation” in Canvas_Loaded method.
Please help
FindResource method expect a resource key which SomeAnimation doesn't have.
You can use it to find the Storyboard resource with using the sd key and find your animation from there.
private void Canvas_Loaded(object sender, RoutedEventArgs e)
{
var canvas = sender as Canvas;
var storyboard = canvas.FindResource("sd") as Storyboard;
var someAnimation = storyboard.Children.First() as DoubleAnimation;
}
If you do that in order to activate the animation you can do it using BeginStoryboard method
var storyboard = canvas.FindResource("sd") as Storyboard;
canvas.BeginStoryboard(storyboard);
or simply
storyboard.Begin();
Hope this helps

How to animate an Image in a button to shake every 30 seconds in WPF?

I not good when it comes to dealing with anything with styles and animations.
I was hoping to be able to get some help on making an Image that is the only content of a Button shake every 30 seconds when ever the buttons Visibility is set to Visibility.Visible.
It is to get the users attention to encourage them to click the button.
I would prefer to do this as an attached behavior on Image, or if possible even UIControl, to make it easily reusable instead of messing with the style, as I am already using a style from my control vendor, and I don't want to edit it.
Here is the solution I used derived from the marked answer
This is the Attached Behavior that can be applied to any System.Windows.Controls.Image.
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Interactivity;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace SampleShakeBehavior
{
public class ShakeBehavior : Behavior<Image>
{
private const double DefaultRepeatInterval = 10.0;
private const double DefaultSpeedRatio = 1.0;
private const string RepeatIntervalName = "RepeatInterval";
private const string SpeedRatioName = "SpeedRatio";
public static readonly DependencyProperty RepeatIntervalProperty =
DependencyProperty.Register(RepeatIntervalName,
typeof(double),
typeof(ShakeBehavior),
new PropertyMetadata(DefaultRepeatInterval));
public static readonly DependencyProperty SpeedRatioProperty =
DependencyProperty.Register(SpeedRatioName,
typeof(double),
typeof(ShakeBehavior),
new PropertyMetadata(DefaultSpeedRatio));
/// <summary>
/// Gets or sets the time interval in in seconds between each shake.
/// </summary>
/// <value>
/// The time interval in in seconds between each shake.
/// </value>
/// <remarks>
/// If interval is less than total shake time, then it will shake
/// constantly without pause. If this is your intention, simply set
/// interval to 0.
/// </remarks>
public double RepeatInterval
{
get { return (double)GetValue(RepeatIntervalProperty); }
set { SetValue(RepeatIntervalProperty, value); }
}
/// <summary>
/// Gets or sets the ratio at which time progresses on the Shakes
/// Timeline, relative to its parent.
/// </summary>
/// <value>
/// The ratio at which time progresses on the Shakes Timeline, relative
/// to its parent.
/// </value>
/// <remarks>
/// If Acceleration or Deceleration are specified, this ratio is the
/// average ratio over the natural length of the Shake's Timeline. This
/// property has a default value of 1.0. If set to zero or less it
/// will be reset back to th default value.
/// </remarks>
public double SpeedRatio
{
get { return (double)GetValue(SpeedRatioProperty); }
set { SetValue(SpeedRatioProperty, value); }
}
private Style _orignalStyle;
protected override void OnAttached()
{
_orignalStyle = AssociatedObject.Style;
AssociatedObject.Style = CreateShakeStyle();
}
protected override void OnDetaching()
{
AssociatedObject.Style = _orignalStyle;
}
private Style CreateShakeStyle()
{
Style newStyle = new Style(AssociatedObject.GetType(), AssociatedObject.Style);
/**
* The following will replace/override any existing RenderTransform
* and RenderTransformOrigin properties on the FrameworkElement
* once the the new Style is applied to it.
*/
newStyle.Setters.Add(new Setter(UIElement.RenderTransformProperty, new RotateTransform(0)));
newStyle.Setters.Add(new Setter(UIElement.RenderTransformOriginProperty, new Point(0.5, 0.5)));
newStyle.Triggers.Add(CreateTrigger());
return newStyle;
}
private DataTrigger CreateTrigger()
{
DataTrigger trigger = new DataTrigger
{
Binding = new Binding
{
RelativeSource = new RelativeSource
{
Mode = RelativeSourceMode.FindAncestor,
AncestorType = typeof(UIElement)
},
Path = new PropertyPath(UIElement.IsVisibleProperty)
},
Value = true,
};
trigger.EnterActions.Add(new BeginStoryboard { Storyboard = CreateStoryboard() });
return trigger;
}
private Storyboard CreateStoryboard()
{
double speedRatio = SpeedRatio;
// Must be greater than zero
if (speedRatio <= 0.0)
SpeedRatio = DefaultSpeedRatio;
Storyboard storyboard = new Storyboard
{
RepeatBehavior = RepeatBehavior.Forever,
SpeedRatio = speedRatio
};
storyboard.Children.Add(CreateAnimationTimeline());
return storyboard;
}
private Timeline CreateAnimationTimeline()
{
DoubleAnimationUsingKeyFrames animation = new DoubleAnimationUsingKeyFrames();
animation.SetValue(Storyboard.TargetPropertyProperty, new PropertyPath("(0).(1)", UIElement.RenderTransformProperty, RotateTransform.AngleProperty));
int keyFrameCount = 8;
double timeOffsetInSeconds = 0.25;
double totalAnimationLength = keyFrameCount * timeOffsetInSeconds;
double repeatInterval = RepeatInterval;
// Can't be less than zero and pointless to be less than total length
if (repeatInterval < totalAnimationLength)
repeatInterval = totalAnimationLength;
animation.Duration = new Duration(TimeSpan.FromSeconds(repeatInterval));
int targetValue = 12;
for (int i = 0; i < keyFrameCount; i++)
animation.KeyFrames.Add(new LinearDoubleKeyFrame(i % 2 == 0 ? targetValue : -targetValue, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(i * timeOffsetInSeconds))));
animation.KeyFrames.Add(new LinearDoubleKeyFrame(0, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(totalAnimationLength))));
return animation;
}
}
}
Here is how to use it in Xaml.
<Button>
<Image Source="myImage.png">
<i:Interaction.Behaviors>
<local:ShakeBehavior RepeatInterval="30" SpeedRatio="3.0"/>
</i:Interaction.Behaviors>
</Image>
</Button>
For a clear definition of an Attached Behavior you can look at the System.Windows.Interactivity.Behavior class remarks. Behaviors can optionally have Attached Properties with them as well make them very useful.
For a clear definition of an Attached Property you can read the Attached Properties Overview from MSDN. Attached properties can do anything, and they can be thought of as attached behaviors because they can trigger an action to occur causing an effective behavior, however technically they are still just an attached property.
Since an Attached Property can act like a behavior people have come to also call those types of Attached Properties an Attached Behavior, when in fact it is not really an Attached Behavior unless you derive from Behavior and at it to the attached property Interaction.Behaviors collection.
Blend is not required for any Attached Behavior or Attached Property, as with most things in WPF/Silverlight.
Here it is an attached behaviour. Just be careful as it may be destructive if your control has existing render transforms
public class Wibble
{
public static bool GetWobble(DependencyObject obj)
{
return (bool)obj.GetValue(WobbleProperty);
}
public static void SetWobble(DependencyObject obj, bool value)
{
obj.SetValue(WobbleProperty, value);
}
public static readonly DependencyProperty WobbleProperty = DependencyProperty.RegisterAttached("Wobble", typeof(bool), typeof(Wibble), new UIPropertyMetadata(false, new PropertyChangedCallback(OnWobbleChanged)));
private static void OnWobbleChanged(object sender, DependencyPropertyChangedEventArgs args)
{
var image = sender as Image;
if (image == null)
throw new InvalidOperationException("only images can wobble!");
// don't really need this check (the find ancestor binding would still find the button), but the spec said the image should be the only child of the button
var button = LogicalTreeHelper.GetParent(image) as Button;
if (button == null)
throw new InvalidOperationException("only images that are the only child of a button can wobble!");
var previousStyle = image.Style;
var newStyle = new Style(image.GetType(), previousStyle);
// this will override any existing render transform + origin on the button, hope they didn't already have one (and I'm too lazy to check)
newStyle.Setters.Add(new Setter(Image.RenderTransformProperty, new RotateTransform(0)));
newStyle.Setters.Add(new Setter(Image.RenderTransformOriginProperty, new Point(0.5, 0.5)));
var trigger = new DataTrigger();
var binding = new Binding();
var relativeSource = new RelativeSource();
relativeSource.Mode = RelativeSourceMode.FindAncestor;
relativeSource.AncestorType = typeof(Button);
binding.RelativeSource = relativeSource;
binding.Path = new PropertyPath(Button.VisibilityProperty);
trigger.Binding = binding;
trigger.Value = Visibility.Visible;
var storyboard = new Storyboard();
var animation = new DoubleAnimationUsingKeyFrames();
animation.SetValue(Storyboard.TargetPropertyProperty, new PropertyPath("(0).(1)", Image.RenderTransformProperty, RotateTransform.AngleProperty));
animation.Duration = new Duration(TimeSpan.FromSeconds(5)); // spec said 30, but i wanted to actually see it happen!
animation.KeyFrames.Add(new LinearDoubleKeyFrame(-12, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0.2))));
animation.KeyFrames.Add(new LinearDoubleKeyFrame(12, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0.4))));
animation.KeyFrames.Add(new LinearDoubleKeyFrame(0, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0.5))));
storyboard.Children.Add(animation);
storyboard.RepeatBehavior = RepeatBehavior.Forever;
var beginStoryboard = new BeginStoryboard();
beginStoryboard.Storyboard = storyboard;
beginStoryboard.Name = "its_wobble_time"; // it is
trigger.EnterActions.Add(beginStoryboard);
var removeStoryboard = new RemoveStoryboard();
removeStoryboard.BeginStoryboardName = beginStoryboard.Name;
trigger.ExitActions.Add(removeStoryboard);
newStyle.Triggers.Add(trigger);
image.Style = newStyle;
}
}
here is how it would be used:
<Button Width="100" Height="25" >
<Image Source="Untitled.png" xmlns:local="clr-namespace:WpfApplication17" local:Wibble.Wobble="True" />
</Button>
Create a WPF custom control by adding a new item in VS and then navigating to the WPF templates. This will allow you to select "Custom Control (WPF)". Name it "ShakyImageControl". This will create a Themes folder with a generic.xaml in it and a "ShakyImageControl.cs" class file. In the generic.xaml replace the existing style with the following:
<Style TargetType="{x:Type local:ShakyImageControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ShakyImageControl}">
<Image x:Name="image" Source="{TemplateBinding ImageSource}" RenderTransformOrigin="0.5,0.5">
<Image.RenderTransform>
<TransformGroup>
<RotateTransform x:Name="Rotaty"/>
</TransformGroup>
</Image.RenderTransform>
</Image>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Button}, Path=Visibility}" Value="Visible">
<DataTrigger.EnterActions>
<BeginStoryboard Name="fred">
<Storyboard AutoReverse="False" RepeatBehavior="Forever" Duration="0:0:30" Storyboard.TargetName="Rotaty" Storyboard.TargetProperty="Angle">
<DoubleAnimationUsingKeyFrames>
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="-12.0"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="12.0"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="fred"/>
</DataTrigger.ExitActions>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
In the ShakyImageControl class add a dependency property as follows:
static ShakyImageControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ShakyImageControl), new FrameworkPropertyMetadata(typeof(ShakyImageControl)));
}
public ImageSource ImageSource
{
get { return (ImageSource)GetValue(ImageSourceProperty); }
set { SetValue(ImageSourceProperty, value); }
}
// Using a DependencyProperty as the backing store for ImageSource. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ImageSourceProperty =
DependencyProperty.Register("ImageSource", typeof(ImageSource), typeof(ShakyImageControl), new UIPropertyMetadata(null));
To use the shakyImage in a button just do:
<Button Height="50" Width="500" Name="showy" Visibility="Collapsed">
<local:ShakyImageControl ImageSource="\Expand.png"/>
</Button>
local is an xml namespace like "xmlns:local="clr-namespace:WpfApplication6"
NB: your custom control can be in a seperate assembly if you want

Stopwatch for silverlight?

There is no StopWatch for Silverlight.
What would you use instead of it?
I saw some posts about people saying to create an empty animation and call GetCurrentTime() from the Storyboard class, I couldn't get it to work...
What would you do?
This is what I did. Its simple and worked very well for me:
long before = DateTime.Now.Ticks;
DoTheTaskThatNeedsMeasurement();
long after = DateTime.Now.Ticks;
TimeSpan elapsedTime = new TimeSpan(after - before);
MessageBox.Show(string.Format("Task took {0} milliseconds",
elapsedTime.TotalMilliseconds));
All you needed to do was keep one long variable holding the total ticks before beginning the target task.
See my watch at here. Source code is here. And two articles about it are here and here. The Silverlight animation model lends itself well to a stopwatch.
alt text http://xmldocs.net/ball2/background.png
<Storyboard x:Name="Run" RepeatBehavior="Forever">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="SecondHand" x:Name="SecondAnimation"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)" RepeatBehavior="Forever">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<SplineDoubleKeyFrame KeyTime="00:01:00" Value="360"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="MinuteHand"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)" RepeatBehavior="Forever">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<SplineDoubleKeyFrame KeyTime="01:00:00" Value="360"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="HourHand"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)" RepeatBehavior="Forever">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<SplineDoubleKeyFrame KeyTime="12:00:00" Value="360"/>
and the code to run that storyboard is here:
private void RunWatch()
{
var time = DateTime.Now;
Run.Begin();
Run.Seek(new TimeSpan(time.Hour, time.Minute, time.Second));
}
Here is a Stopwatch replacement class that you can add to your project.
There's a few things you could do with this. The fist is use something like the Environment.TickCount like the person here. However, something that I think may work better is to make use of a DispatcherTimer.
To set up a DispatcherTimer to work like a stopwatch we'll also need an associated TimeSpan representing the time it is run. We can instantiate the DispatcherTimer and set the interval that it times, and the handler for the Tick event.
DispatcherTimer _timer;
TimeSpan _time;
public Page()
{
InitializeComponent();
_timer = new DispatcherTimer();
_timer.Interval = new TimeSpan(0, 0, 0, 0, 10);
_timer.Tick += new EventHandler(OnTimerTick);
}
In the UI we can create something simple to start and stop our timer, as well as display the stopwatch data:
<StackPanel>
<Button Content="Start" x:Name="uiStart" Click="OnStartClick" />
<Button Content="Stop" x:Name="uiStop" Click="OnStopClick" />
<TextBlock x:Name="uiDisplay"/>
</StackPanel>
Now, all that is left is the event handlers.
The OnTimerTick handler will incrementing and display our stopwatch data.
Our Start handler will take care of initalizing/reinitalizing our TimeSpan, while the Stop handler will just stop the DispatcherTimer.
void OnTimerTick(object sender, EventArgs e)
{
_time = _time.Add(new TimeSpan(0, 0, 0, 0, 10));
display.Text = _time.ToString();
}
private void OnStartClick(object sender, RoutedEventArgs e)
{
_time = new TimeSpan(0,0,0,0,0);
_timer.Start();
}
private void OnStopClick(object sender, RoutedEventArgs e)
{
_timer.Stop();
}

Resources