Stopping a programmatically instantiated storyboard - c#

I have an EventTrigger that will stop a few storyboards that are defined in my XAML ... but now i need to stop a storyboard that I'm starting programmatically.
<UserControl.Resources>
<Storyboard x:Key="FadeIn"> ... Fade In Definition </StoryBoard>
<Storyboard x:Key="FadeOut"> ... Fade In Definition </StoryBoard>
</UserControl>
<UserControl.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseLeave">
<BeginStoryboard="{StaticResource FadeIn}" x:Name="FadeIn_BeginStoryboard" />
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<StopStoryboard BeginStoryboardName="FadeIn_BeginStoryboard"/>
<StopStoryboard BeginStoryboardName="FadeOut_BeginStoryboard"/>
</EventTrigger>
</UserControl.Triggers>
Storyboard FadeOutStoryboard;
public void StopFadeOut() {
FadeIn_BeginStoryboard.Storyboard.Stop();
FadeOut_BeginStoryboard.Storyboard.Stop();
FadeOutStoryboard = (Storyboard) FindResource("FadeOut");
FadeOutStoryboard.Name="FadeOutStoryboard";
FadeOutStoryboard.Begin();
}
When I put a < StopStoryboard BeginStoryboardName="FadeOutStoryboard" /> it tells me it can't find FadeOutStoryboard. I'm fairly new to WPF programming, so there might be a better way to do this and I'm open to that. The StopFadeOut() method is being invoked by a parent that creates an instance of my usercontrol.

Move these lines to the constructor. You only need to run them once.
FadeOutStoryboard = (Storyboard)FindResource("FadeOut");
FadeOutStoryboard.Name = "FadeOutStoryboard";
Then add this line right after them.
RegisterName("FadeOutStoryboard", FadeOutStoryboard);

Related

WPF - Stop Animation based on duration

I have a storyboard declared in XAML just like this.
<Window.Resources>
<Storyboard x:Key="Storyboard1">
<DoubleAnimationUsingPath Duration="0:0:60" BeginTime="0:0:0 " Source="X" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)" Storyboard.TargetName="object_to_move">
<DoubleAnimationUsingPath.PathGeometry >
<PathGeometry Figures="M-1.75,-21 L6.75,-53.25 L29.75,-91.25 L49.75,-114.75 L56.75,-119.75"/>
</DoubleAnimationUsingPath.PathGeometry>
</DoubleAnimationUsingPath>
<DoubleAnimationUsingPath Duration="0:0:60" BeginTime="0:0:0" Source="Y" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)" Storyboard.TargetName="object_to_move">
<DoubleAnimationUsingPath.PathGeometry>
<PathGeometry Figures="M-1.75,-21 L6.75,-53.25 L29.75,-91.25 L49.75,-114.75 L56.75,-119.75"/>
</DoubleAnimationUsingPath.PathGeometry>
</DoubleAnimationUsingPath>
</Storyboard>
</Window.Resources>
What I am tryign to achieve is more control over the animation. So, first I call a reference of the storyboard in the back-end just like this.
// This works just fine.
var sub = FindResource("Storyboard1") as Storyboard;
sub.begin() // or Do something else
But I want also be able to stop the animation using the duration and specifically using milliseconds. For Instance, when object_to_move is moving for 0:0:35 milliseconds, I want it to stop when it hits the 0:0:50 mark.
What is the best way to achieve this?

Button begins storyboard animation

** Edit Question * *
Storyboard s = (Storyboard)TryFindResource("kupmove");
s.Stop();
This doesnt stop the ongoing animation, how can i stop it ?
How can i begin the storyboard with a button click event in visual c# ?
Here is the code i am using to begin the storyboard :
Storyboard s = (Storyboard)TryFindResource("kupmove");
Storyboard.SetTargetName(s, "geometryModel3D");
s.Begin();
The animation does not begin and i get the following error :
'Children' property value in the path '(0).(1)[2].(2)' points to immutable instance of 'System.Windows.Media.Media3D.Transform3DCollection'
Here is my xaml storyboard :
<Storyboard x:Key="kupmove"
RepeatBehavior="Forever"
AutoReverse="True">
<Rotation3DAnimationUsingKeyFrames Storyboard.TargetProperty="(Model3D.Transform).(Transform3DGroup.Children)[2].(RotateTransform3D.Rotation)"
Storyboard.TargetName="geometryModel3D">
<EasingRotation3DKeyFrame KeyTime="0">
<EasingRotation3DKeyFrame.EasingFunction>
<CubicEase EasingMode="EaseInOut" />
</EasingRotation3DKeyFrame.EasingFunction>
<EasingRotation3DKeyFrame.Value>
<AxisAngleRotation3D Axis="0,1,0"
Angle="0" />
</EasingRotation3DKeyFrame.Value>
</EasingRotation3DKeyFrame>
<EasingRotation3DKeyFrame KeyTime="0:0:1">
<EasingRotation3DKeyFrame.EasingFunction>
<CubicEase EasingMode="EaseInOut" />
</EasingRotation3DKeyFrame.EasingFunction>
<EasingRotation3DKeyFrame.Value>
<AxisAngleRotation3D Axis="0,1,0"
Angle="179" />
</EasingRotation3DKeyFrame.Value>
</EasingRotation3DKeyFrame>
<EasingRotation3DKeyFrame KeyTime="0:0:2">
<EasingRotation3DKeyFrame.EasingFunction>
<CubicEase EasingMode="EaseInOut" />
</EasingRotation3DKeyFrame.EasingFunction>
<EasingRotation3DKeyFrame.Value>
<AxisAngleRotation3D Axis="0,-1,0"
Angle="1" />
</EasingRotation3DKeyFrame.Value>
</EasingRotation3DKeyFrame>
<EasingRotation3DKeyFrame KeyTime="0:0:3">
<EasingRotation3DKeyFrame.EasingFunction>
<CubicEase EasingMode="EaseInOut" />
</EasingRotation3DKeyFrame.EasingFunction>
<EasingRotation3DKeyFrame.Value>
<AxisAngleRotation3D Axis="0.009,-0.014,1"
Angle="90.005" />
</EasingRotation3DKeyFrame.Value>
</EasingRotation3DKeyFrame>
</Rotation3DAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(Model3D.Transform).(Transform3DGroup.Children)[4].(TranslateTransform3D.OffsetX)"
Storyboard.TargetName="geometryModel3D">
<EasingDoubleKeyFrame KeyTime="0:0:2"
Value="0">
<EasingDoubleKeyFrame.EasingFunction>
<CubicEase EasingMode="EaseInOut" />
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
<EasingDoubleKeyFrame KeyTime="0:0:3"
Value="0">
<EasingDoubleKeyFrame.EasingFunction>
<CubicEase EasingMode="EaseInOut" />
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
* EDIT *
Suggested solutions doesnt work, actually i dont have an error with the previous code which triggers the storyboard, it works but I think because there is already an animation which has been initiated with the following code, causes the exception. Tell me a way to stop it when it has already been running :
> DoubleAnimation myAnimation = new DoubleAnimation();
> myAnimation.From = 1;
> myAnimation.To = 361;
> myAnimation.Duration = new >Duration(TimeSpan.FromMilliseconds(1000));
> myAnimation.RepeatBehavior = RepeatBehavior.Forever;
> rotateTransform.Rotation.BeginAnimation(AxisAngleRotation3D.AngleProperty, myAnimation);
> geometryModel3D.Transform = rotateTransform;
I think i must stop this first stop this --> geometryModel3D.Transform = rotateTransform;
Because the animation i want to trigger has already been running with this code. How can i stop this before re-starting my storyboard with its own parameters which are declared in xaml code.
Stopping the geometryModel3D.Transform with a second parameter null value with BeginAnimation command, didnt help. First exception still occurs.
I think the problem initiates here :
Storyboard s = (Storyboard)TryFindResource("kupmove");
s.Stop();
This command doesnt stop the ongoing animation. So there must be something wrong thats why s.Begin() doesnt already trigger.
use x:Name instead of x:Key and call the Begin() method on that storyboard instance.
A moment ago, a gentleman posted an answer that I read that displayed:
"Try calling the storyboard by it's key, i.e., not using TryFindResource()"
Maybe you could try calling it with it's instantiated name, kupmove
I merely read this, and the user deleted his answer. Sorry if it doesn't help.
Also, as another user posted, above, use x:Name rather than key, to create a name for the storyboard, then call it using that initialized name.

How to animate at periodic intervals?

I have a storyboard which animates a minute hand to slide 6 degrees. Now i want the minute hand to slide at every 59th second forever. Are there any properties of storyboard or any other way that i can do it?
My storyboard
<Storyboard
x:Name="myStoryboard2">
<DoubleAnimation
x:Name="minuteAnimation"
Storyboard.TargetName="minHandTransform"
Storyboard.TargetProperty="Angle"
Duration="0:0:1"
From="{Binding Time, Converter={StaticResource minuteHandTransform}}"
To="{Binding Time, Converter={StaticResource minuteHandTransform}}"
RepeatBehavior="1x">
<DoubleAnimation.EasingFunction>
<SineEase
EasingMode="EaseOut" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
It doesn't sound like something you would want to rely on an animation to manage. Simply manage starting the animation from code behind every minute and you are done. It would be a lot easier to do it that way than using cryptic converters to control the From/To values. A Timeline such as a DoubleAnimation has a BeginTime property, but I have seen and verified reports of long animation durations (like 1 minute or above) hitting bugs in WinRT.
EDIT* (code samples)
Two simple ways I commonly use to trigger events at an interval are to use a DispatcherTimer with callback events or an async loop.
1. DispatcherTimer
var timer = new DispatcherTimer { Interval = TimeSpane.FromSeconds(1) };
timer.Tick += (s, e) => { /* do your stuff */ };
timer.Start();
2. async loop
RunMyLoop();
private async void RunMyLoop()
{
while (true)
{
/* do your stuff */
await Task.Delay(1000);
}
}
Try the following:
<Storyboard
x:Name="myStoryboard2">
<DoubleAnimation
x:Name="minuteAnimation"
Storyboard.TargetName="minHandTransform"
Storyboard.TargetProperty="Angle"
Duration="0:0:59"
From="{Binding Time, Converter={StaticResource minuteHandTransform}}"
To="{Binding Time, Converter={StaticResource minuteHandTransform}}"
RepeatBehavior="Forever">
<DoubleAnimation.EasingFunction>
<SineEase
EasingMode="EaseOut" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>

Simulate a series of button clicking in code and wait for effects to finish

I have series of buttons with EFFECTS that I'd like simulate clicking in code. The problem is that when I run "someFunction" the effects all trigger at about the same time. I'd like a button to click, then wait for the effect to finish then proceed on to the other buttons. I've tried using Thread.sleep but that doesn't work. Can anyone give me some pointers on how this can be accomplished? Also, is it possible to simulate clicking on multiple buttons at the same time?
Thanks for any help!
someFunction() {
button1.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
//System.Threading.Thread.Sleep(100); doesn't work wait for effect to finish
button2.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
//System.Threading.Thread.Sleep(100); doesn't work
button3.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
//System.Threading.Thread.Sleep(100); doesn't work
button4.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
}
<Button Name="buttonRed" Background="Red" BorderBrush="Black" BorderThickness="1"
Grid.Row="1" Grid.Column="0" Click="buttonRedClick">
<Button.BitmapEffect>
<BlurBitmapEffect x:Name="redBlur" Radius="0" />
</Button.BitmapEffect>
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="redBlur"
Storyboard.TargetProperty="Radius"
From="0" To="40" Duration="0:0:0.3"
AutoReverse="True" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
Try looking at this article by Laurent Bugnion.
From above article:
Note that it is also possible to use the BeginTime property of the
animation object to delay an animation, for example in order to
cascade animations. Another way is to use the Completed event, which
can be set in the XAML code, or in the code-behind. This event implies
that the corresponding method must be present in the code-behind, thus
it is not applicable for pure XAML applications.
Thread.sleep will not work as it defines the sys.val realization, so you can use the following code:
there's the error : <EventTrigger RoutedEvent="Button.Click">
instead it should be: <Event.Trigger.Sleep RoutedEvent="Button.Click">

StackOverflowException on InitializeComponent

I'm trying to follow this little tutorial, but I keep getting this exception.
The relevant XAML looks like this:
<StatusBar Margin="0,288,0,0" Name="statusBar" Height="23" VerticalAlignment="Bottom">
<StatusBar.DataContext>
<m:MainWindow />
</StatusBar.DataContext>
<TextBlock Name="statusText" Text="{Binding Path=StatusBarText, NotifyOnTargetUpdated=True}" DataContext="{Binding}">
<TextBlock.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.25" Value="1"/>
<EasingDoubleKeyFrame KeyTime="0:0:4" Value="1"/>
<EasingDoubleKeyFrame KeyTime="0:0:5" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</TextBlock.Triggers>
</TextBlock>
</StatusBar>
I'm guessing I'm getting the StackOverflowException because I'm trying to use MainWindow as the DataContext. I want to use the MainWindow because it seems like a logical place to put my StatusBarText property,
public partial class MainWindow : Window
{
public string StatusBarText { get; set; }
It makes it easier to access in my code-behind event handlers.
What am I supposed to do then? Where am I supposed to put this property? Or is there a way to set the DataContext to "this" so that it doesn't create a new instance of MainWindow and just refers to itself?
I generally set my DataContext in code-behind, in the constructor (I usually use MVVM, but have used a window in small temp projects):
public MainWindow()
{
statusBar.DataContext = this;
}
Note that in your shown code example, you will only get your initial StatusBarText value, because you are not implementing INotifyPropertyChanged.
Ideally your properties which you will bind to should live within a ViewModel following the MVVM pattern, abstracting themselves away from the View. Since that is not your question however we shall move along...the DataContext is inherited from its parent. Therefore if the StatusBar lives in the Window, which I am pretty certain it does, it will already be inheriting the DataContext from the Window. You are essentially trying to bind a UI component using a UI component (Window) as your source for the DataContext. Far from ideal...here is an overview of the MVVM pattern...

Categories

Resources