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();
}
Related
I've these Load and Unload animations in App.xaml:
<Storyboard x:Key="Unload">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:3" Value="-800"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="Load">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)">
<EasingDoubleKeyFrame KeyTime="0" Value="800"/>
<EasingDoubleKeyFrame KeyTime="0:0:3" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
In my code I'm calling those on Button Clicks like these:
sb1 = FindResource("Unload") as Storyboard;
sb2 = FindResource("Load") as Storyboard;
void Button_Click(object sender, RoutedEventArgs e)
{
uc1.RenderTransform = GetTG();
uc2.RenderTransform = GetTG();
sb1.Begin(uc2);
sb2.Begin(uc1);
content.Content = uc1;
}
//and
void Button_Click_1(object sender, RoutedEventArgs e)
{
uc2.RenderTransform = GetTG();
uc1.RenderTransform = GetTG();
sb2.Begin(uc2);
sb1.Begin(uc1);
content.Content = uc2;
}
GetTG returns a TransformGroup
TransformGroup GetTG()
{
var tg = new TransformGroup();
tg.Children.Add(new ScaleTransform());
tg.Children.Add(new SkewTransform());
tg.Children.Add(new RotateTransform());
tg.Children.Add(new TranslateTransform());
return tg;
}
with these only the Load animation works. How to make both work at the same time?
Apply the animation to the ContentControl. This should slide out the current Content/UserControl to the left and slide in the new Content/UserControl from the right:
void Button_Click(object sender, RoutedEventArgs e)
{
UnLoadAndLoad(uc1);
}
void Button_Click_1(object sender, RoutedEventArgs e)
{
UnLoadAndLoad(uc2);
}
void UnLoadAndLoad(object ucToBeLoaded)
{
if (content.Content != ucToBeLoaded)
{
content.RenderTransform = GetTG();
EventHandler completed = null;
completed = (ss, ee) =>
{
sb1.Completed -= completed;
content.Content = ucToBeLoaded;
//load
sb2.Begin(content);
};
sb1.Completed += completed;
//unload
if (content.Content != null)
sb1.Begin(content);
else
completed(this, EventArgs.Empty);
}
}
I have a storyboard defined:
<Grid.Resources>
<Storyboard x:Key="MasterAnim" x:Name="MasterAnim" Duration="0:0:10" >
<DoubleAnimation x:Name="ANIMATABLE_WidthExp"
Storyboard.TargetName="ANIMELEMENT_SboardRect1"
Storyboard.TargetProperty="Width"
From="100"
To="800"
Duration="0:0:5" />
<DoubleAnimation x:Name="ANIMATABLE_HeightExp"
Storyboard.TargetName="ANIMELEMENT_SboardRect1"
Storyboard.TargetProperty="Height"
From="100"
To="800"
BeginTime="0:0:5"
Duration="0:0:5" />
</Storyboard>
</Grid.Resources>
Is there any way of capturing when each of the double animations are about to start? I need to call a method on the element that it's targeting before the animation starts but im not sure what the best way to go about it is.
There's no Started event, but you can handle the CurrentTimeInvalidated, CurrentStateInvalidated and Completed events on the DoubleAnimation. CurrentStateInvalidated is probably all you need.
private void DoubleAnimationCurrentTimeInvalidated(object sender, EventArgs e)
{
var clock = (AnimationClock) sender;
Debug.WriteLine(string.Format("CurrentTime: state={0}, progress={1}, time={2}", clock.CurrentState, clock.CurrentProgress, clock.CurrentTime));
}
private void DoubleAnimationCurrentStateInvalidated(object sender, EventArgs e)
{
var clock = (AnimationClock)sender;
Debug.WriteLine(string.Format("CurrentState: state={0}", clock.CurrentState));
}
private void DoubleAnimationCompleted(object sender, EventArgs e)
{
var clock = (AnimationClock) sender;
Debug.WriteLine(string.Format("Completed: state={0}", clock.CurrentState));
}
Ignoring the current time we get
CurrentState: state=Active
CurrentState: state=Filling
Completed: state=Filling
when the animation runs.
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. :)
so I have a contentcontrol that has a routedevent:
public class TestBlind : ContentControl
{
public static readonly RoutedEvent VisibilityVisibleEvent =
EventManager.RegisterRoutedEvent("VisibilityVisible", RoutingStrategy.Tunnel, typeof(Visibility), typeof(TestBlind));
public event RoutedEventHandler VisibilityVisible
{
add { AddHandler(VisibilityVisibleEvent, value); }
remove { RemoveHandler(VisibilityVisibleEvent, value); }
}
[Category("TestBlind")]
public bool IsContentVisible
{
get { return (bool)GetValue(IsContentVisibleProperty); }
set { SetValue(IsContentVisibleProperty, value); }
}
public static readonly DependencyProperty IsContentVisibleProperty = DependencyProperty.Register("IsContentVisible", typeof(bool), typeof(TestBlind),
new FrameworkPropertyMetadata(new PropertyChangedCallback(OnIsOverlayContentVisibleChanged)));
private static void OnIsOverlayContentVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TestBlind blind = d as TestBlind;
if (blind != null)
SetVisibility(blind);
}
private static void SetVisibility(TestBlind blind)
{
blind.Visibility = blind.IsContentVisible ? Visibility.Visible : Visibility.Hidden;
blind.RaiseEvent(blind.Visibility == Visibility.Visible ? new RoutedEventArgs(VisibilityVisibleEvent) : new RoutedEventArgs(VisibilityHiddenEvent));
}
}
this event is fired when a dependency property is changed. What I want is to be able to fire an animation when the event fires.
In my resource file for the control I have the following exert that (I thought) would call see the event and start the animation:
<Grid.Triggers>
<EventTrigger RoutedEvent="control:TestBlind.VisibilityVisible">
<!-- <EventTrigger RoutedEvent="ContentControl.Loaded">-->
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingKeyFrames Duration="00:00:02.25" BeginTime="00:00:00" Storyboard.TargetName="backdDropGlow" Storyboard.TargetProperty="Opacity">
<SplineDoubleKeyFrame KeyTime="00:00:00.00" Value="0"/>
<SplineDoubleKeyFrame KeyTime="00:00:02.25" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
The animation will work if I use the ContentControl.Loaded (only the first time the control's property is changed though), but if I try and register it for my event nothing happens.
Is this possible, am I going about it completely wrong? I hope this made sense.
Thanks
Why not trigger your animation when the DependnecyProperty changes instead of using an Event?
I have done this in the past by attaching an event to the DependencyPropertyDescriptor in the Constructor
public TestBlind()
{
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(TestBlind.VisibilityVisible, typeof(TestBlind));
if (dpd != null) dpd.AddValueChanged(this, delegate { IsVisibilityVisibleChanged(); });
}
private void IsVisibilityVisibleChanged()
{
bool isShown = GetVisibilityVisible(this);
if (isShown)
{
Storyboard animation = (Storyboard)this.FindResource("MyStoryboard");
animation.Begin();
}
}
I've been struggling with this for hours and I can't find out what I'm doing wrong. Please help me find my mistake.
I created a user control with one custom dependency property and I want to animate this property.
Here is my class:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public long Val
{
get { return (long)GetValue(ValProperty); }
set {
SetValue(ValProperty, value);
// Update a text block for debug
((Class1)this.Resources["class1"]).Val = value;
}
}
public static readonly DependencyProperty ValProperty =
DependencyProperty.Register("Val", typeof(long), typeof(UserControl1), new UIPropertyMetadata(0L));
}
Here is the code that is supposed to animate it (there's an instance of UserControl1 called usercontrol11):
Int64Animation myAnimation = new Int64Animation();
myAnimation.From = 100;
myAnimation.To = 200;
myAnimation.Duration = new Duration(TimeSpan.FromSeconds(1));
Storyboard.SetTargetName(myAnimation, userControl11.Name);
Storyboard.SetTargetProperty(myAnimation, new PropertyPath(UserControl1.ValProperty));
Storyboard myStoryboard = new Storyboard();
myStoryboard.Children.Add(myAnimation);
myStoryboard.Begin(this);
I also tried the XAML approach, but it didn't work either (in the following XAML, the Width animation works fine, but the Val doesn't):
<my:UserControl1 HorizontalAlignment="Left" Width="150" Margin="72,45,0,0" x:Name="userControl11" VerticalAlignment="Top" Background="#FFFFD100">
<my:UserControl1.Triggers>
<EventTrigger RoutedEvent="MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<Int64Animation To="300" Duration="0:0:1"
Storyboard.TargetProperty="Val" />
<DoubleAnimation To="300" Duration="0:0:1"
Storyboard.TargetProperty="Width" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</my:UserControl1.Triggers>
</my:UserControl1>
Any help will be much appreciated!
Well, it's actually animated. You're just checking in the wrong place, animation won't call Val property set accessor. You can, for example, override OnPropertyChanged method to see changes:
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if(e.Property == ValProperty)
{
MessageBox.Show(e.NewValue.ToString());
}
}