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);
}
}
Related
I have an Image and I'm trying to give the user the ability to change the animation of the image from several options. The user can select a new animation but sometimes the previous selected animation won't stop and I have 2 or more storyboards running at the same time on the Image.
I have this code:
private void animateZoom1(object sender, EventArgs e)
{
effectSample.RenderTransform.BeginAnimation(ScaleTransform.ScaleYProperty, (FindResource("LogoStoryboard_Zoom") as Storyboard).Children[0] as DoubleAnimation);
}
private void animateZoom(Boolean mode)
{
if (mode)
{
effectSample.RenderTransform = new ScaleTransform();
effectSample.RenderTransform.BeginAnimation(ScaleTransform.ScaleYProperty, (FindResource("LogoStoryboard_Zoom") as Storyboard).Children[0] as DoubleAnimation);
}
else
{
effectSample.RenderTransform.BeginAnimation(ScaleTransform.ScaleYProperty, null);
cancelAnimation("LogoStoryboard_Zoom");
}
}
private void animateZoom2(object sender, EventArgs e)
{
effectSample.RenderTransform.BeginAnimation(ScaleTransform.ScaleYProperty, (FindResource("LogoStoryboard_Zoom") as Storyboard).Children[1] as DoubleAnimation);
}
private void animateOpacity(Boolean mode)
{
if (mode)
{
(FindResource("LogoStoryboard_Opacity") as Storyboard).Begin(this, true);
}
else
{
cancelAnimation("LogoStoryboard_Opacity");
}
}
private void animateScaleX2(object sender, EventArgs e)
{
effectSample.RenderTransform.BeginAnimation(ScaleTransform.ScaleXProperty, (FindResource("LogoStoryboard_ScaleX") as Storyboard).Children[1] as DoubleAnimation);
}
private void animateScaleX(object sender, EventArgs e)
{
effectSample.RenderTransform.BeginAnimation(ScaleTransform.ScaleXProperty, (FindResource("LogoStoryboard_ScaleX") as Storyboard).Children[0] as DoubleAnimation);
}
private void animateScaleX(Boolean mode)
{
if (mode)
{
effectSample.RenderTransform = new ScaleTransform();
effectSample.RenderTransform.BeginAnimation(ScaleTransform.ScaleXProperty, (FindResource("LogoStoryboard_ScaleX") as Storyboard).Children[0] as DoubleAnimation);
}
else
{
effectSample.RenderTransform.BeginAnimation(ScaleTransform.ScaleXProperty, null);
cancelAnimation("LogoStoryboard_ScaleX");
}
}
private void cancelAnimation(String storyBoardName)
{
(FindResource(storyBoardName) as Storyboard).Stop(this);
(FindResource(storyBoardName) as Storyboard).Remove(this);
}
and this
<Storyboard x:Key="LogoStoryboard_Zoom" RepeatBehavior="Forever">
<DoubleAnimation
Storyboard.TargetName="effectSample"
Storyboard.TargetProperty="(Image.RenderTransform).(ScaleTransform.ScaleY)"
By="0.5"
Duration="0:0:0.5"
BeginTime="0:0:0"
Completed="animateZoom2" />
<DoubleAnimation
Storyboard.TargetName="effectSample"
Storyboard.TargetProperty="(Image.RenderTransform).(ScaleTransform.ScaleY)"
By="-0.5"
Duration="0:0:0.5"
BeginTime="0:0:0"
Completed="animateZoom1" />
</Storyboard>
<Storyboard x:Key="LogoStoryboard_Opacity" RepeatBehavior="Forever">
<DoubleAnimation
Storyboard.TargetName="effectSample"
Storyboard.TargetProperty="Opacity"
From="1"
To="0"
Duration="0:0:8"
BeginTime="0:0:1"/>
<DoubleAnimation
Storyboard.TargetName="effectSample"
Storyboard.TargetProperty="Opacity"
From="0"
To="1"
Duration="0:0:8"
BeginTime="0:0:10"/>
</Storyboard>
<Storyboard x:Key="LogoStoryboard_ScaleX" RepeatBehavior="Forever">
<DoubleAnimation
Storyboard.TargetName="effectSample"
Storyboard.TargetProperty="(Image.RenderTransform).(ScaleTransform.ScaleX)"
To="0.5"
Duration="0:0:3"
BeginTime="0:0:0"
Completed="animateScaleX2"/>
<DoubleAnimation
Storyboard.TargetName="effectSample"
Storyboard.TargetProperty="(Image.RenderTransform).(ScaleTransform.ScaleX)"
To="1.5"
Duration="0:0:3"
BeginTime="0:0:0"
Completed="animateScaleX"/>
</Storyboard>
Please help me stop the old storyboard :)
I have this storyboard that starts when you hover over a grid:
<Storyboard x:Key="SB_MouseEnter">
<DoubleAnimation To="0" Storyboard.TargetName="gridNav"
Storyboard.TargetProperty="(UIElement.RenderTransform).
(TranslateTransform.Y)"
Duration="0:0:0.2"/>
</Storyboard>
I would like to hover over it for 500ms before it does its animation. How to accomplish this?
Specify BeginTime on your storyboard at which you want timeline to begin (0.5 secs in your case):
<DoubleAnimation To="0" Storyboard.TargetName="gridNav"
Storyboard.TargetProperty="(UIElement.RenderTransform).
(TranslateTransform.Y)"
Duration="0:0:0.2"
BeginTime="0:0:0.5"/>
You could use the DispatcherTimer class to wait for 500ms in combination with the UIElement.MouseEnter and UIElement.MouseLeave events, before manually starting the Storyboard with the Begin method. Try something like this:
private DispatcherTimer timer;
private void OnMouseEnterHandler(object sender, MouseEventArgs e)
{
timer = new DispatcherTimer();
timer.Interval = new TimeSpan(0, 0, 0, 0, 500);
timer.Tick += Timer_Tick;
timer.Start();
}
private void OnMouseLeaveHandler(object sender, MouseEventArgs e)
{
timer.Stop();
}
private void Timer_Tick(object sender, EventArgs e)
{
SB_MouseEnter.Begin();
timer.Stop();
}
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.
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();
}
}
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();
}