I have a WPF storyboard in my codebehind which runs with several color animations chained together. It works just fine, but in some scenarios I would like to be able to start the animation in the middle (e.g. 5 seconds into colorAnimation2) rather than always at the beginning. How would I go about this?
StoryBoard storyBoard = new StoryBoard;
// each animation runs for 10 seconds and starts 10 seconds after the previous one.
storyboard.Children.Add(colorAnimation1);
storyboard.Children.Add(colorAnimation2);
storyboard.Children.Add(colorAnimation3);
storyBoard.Begin();
Call the Seek() method just after calling storyBoard.Begin() to jump along the timeline by the given timespan.
storyBoard.Seek(TimeSpan.FromSeconds(15));
Related
What is my actual problem?
I want to stop Storyboard animation but keep current value of animated properties and unfreeze those properties.
What I have tried?
I tried storyboard.Pause() but this will keep the animated properties frozen. (changing values has no effect) I want to be able to release all properties from storyboard.
storyboard.Stop() doesn't work because it either sets value of animated properties to end or start of the animation depending on the FillBehavior. Although it will unfreeze the properties.
I tried clearing storyboard.Childern but it throws exception unless storyboard is fully stopped.
I tried to make new storyboard with duration 0 with same animations, this will stop previous animations and storyboard it self will finish in no time, but here are the problems
I should know what animations were added in last storyboard (I already solved this by storing dependency objects with property path in a List)
I have to retrieve current value of animated properties from property path.
I am stuck at the last one because this does not exist in UWP
string propertyPath = "(UIElement.Projection).(PlaneProjection.RotationY)";
// all animated properties are double
double value = (double)dependencyObject.GetValue(???);
GetValue requires DependencyProperty but i have a string.
Note: I just want a solution to my actual problem (top of this post).
I was able to solve this problem using following timeline
new DoubleAnimation { By = 0, Duration = TimeSpan.Zero }
With this timeline i don't have to know value of the properties. new storyboard on same elements and same property path's with duration 0 with this timeline will stop previous storyboard and keep their values. the new storyboard will also stop in no time and unfreeze the animated properties.
So I have rectangle shaped custom controls being animated on a path, 35 in total.
When a control stops, for what ever reason, I detect if any other controls overlap/intersect with it. If they do, I stop them.
To detect a collision, when a controls stops it triggers a DispatcherTimer to start. When the tick even triggers it does this.
Pause all the storyboards that are currently running.
Loop through every control and see if they intersect with the stopped control.
If they do, then that control is also marked as stopped.
Once loop is complete, any control that is not marked as stopped, I continue the storyboard.
Hopefully that logic sounds ok for the basics. This is the code for the detection part.
foreach (var ucPallet in Paellets)
{
if(ucPallet.IsMoving)
ucPallet.Storyboard.Pause(this);
}
for (var j = 0; j < Paellets.Count; j++)
{
var movingBounds = Paellets[j].BoundsRelativeTo(AnimationCanvas);
var stoppedBounds = stoppedPallet.BoundsRelativeTo(AnimationCanvas);
if (stoppedBounds.IntersectsWith(movingBounds))
{
var pallet = movingPaellet[j]; // Breakpoint Here
pallet.IsMoving = false;
}
}
foreach (var ucPallet in Paellets)
{
if(ucPallet.IsMoving) // Only continue if not set to stop
ucPallet.Storyboard.Resume(this);
}
For test purposes, I just made a simple path between 2 points, so there is a start and end. I create the controls in a loop, with a gap of about 250ms.
What should happen is that when the first control stops (Storyboard Complete), the others should stack up behind it. A Bit like cars coming to a stop at a traffic light.
The problem.
Visually, they are not stacking behind eachother they are all going to the end.
If I put a break point on the detection of an intersection (see Breakpoint Here in the above code), I can see the details of the bounds and it looks like the intersect is about 5 pixels.
With the assumption that the storyboard has paused, and I do not continue it, why does it visually not look the same as what the bounds say? i.e. I expect to see 1 control overlap the other by about 5 pixels, instead I see it is the same location and 1 control covers the other.
UPDATE 1:
Just a thought after submitting the question. I put a Console.WriteLine in every Storyboard.Complete EventHandler. In theory I should only see 1 (the first control) as the others are paused when they intersect. When I run I see they all complete. How can this happen if I have Paused them?
UPDATE 2:
Simple Project link to show the issue.
PalletTest.Zip
To Pause a StoryBoard, you have to Begin it like this:
myStoryboard.Begin(this, true);
Did you? See How to: Control a Storyboard After It Starts
Update after looking at the project:
The pallets are moving too fast. I've played around with your animations parms Setting the animation's Duration to 15 seconds results in a "perfect stack" as you expected and only one completed animation. Looks like with 2 seconds one animated step is too big, so the last step executed reahces the end of the path.
So there's nothing wrong with your code. So I just have a tiny suggestion for improvement: You can reduce the three for/foreach-loops to a single, where you set IsMoving and pause the animation. Here is a sample, that uses LINQ:
var stoppedPallets = this.Pallets.Where(p => !p.IsMoving).ToList();
var movingPallets = this.Pallets.Where(p => p.IsMoving).ToList();
foreach (var movingPallet in movingPallets)
{
var movingBounds = movingPallet.BoundsRelativeTo(this.AnimationGrid);
if (stoppedPallets.Any(
p => p.BoundsRelativeTo(this.AnimationGrid).IntersectsWith(movingBounds)))
{
movingPallet.Storyboard.Pause(this);
movingPallet.IsMoving = false;
}
}
There is still some room for optimization, as the bounds of the stopped pallets are calculated for every moving pallet (this is true for the above and for your code).
Additionally, I would recommend moving as much of the pallet-initializing as possible to the ucPallet class.
So I searched for a solution and I didn't find something...
I have an application that use Storyboard for animations, and a button that create the animation and start it. looks like this:
DoubleAnimation animation = new DoubleAnimation
{
//properties
};
sb = new Storyboard();//sb is the Storyboard
sb.Children.Add(animation);
Storyboard.SetTargetName(animation, myControl.Name);
Storyboard.SetTargetProperty(animation, new PropertyPath(Canvas.TopProperty));
sb.Begin(this);
The problem is that when I click the button while the animation still running, it restart the animation, and I want it to "add it to the order" so when the animation finished it starts again over and over, the amount of times I clicked the button.
Instead of trying to change a storyboard that's already running I suggest you instead use just a list of animations.
Or storyboards if you need some functionality in storyboard like animating several things at once.
You can then add them to a list ( or queue even ).
Subscribe to the completed event of each.
Keep a currentIndex pointer so you know which one is running.
When the completed event is hit, unhook your event handler from the one just finished, hook the next one. Begin the next storyboard.
in my form, I have a small media player, it's pretty basic, it has the basic functionalists
nest, previous, play, stop, volume increasing and a track bar.
I was able to seek the song by dragging the track bar via this code::
(Assuming that the maximum value of the track bar is the total number of seconds of the sound file)
private void SeekBar_Scroll(object sender, EventArgs e)
{
Player.Position = TimeSpan.FromSeconds(SeekBar.Value);
}
But I wasn't able to make the bar move forward by itself when the music is playing ..
I came up with this idea:
let's say we have a 90 secs duration song.
now the track bar maximum value is 100
so:
100 --> 90
1 --> X
X = 0.9 sec
this is the interval between the ticks of the bar and the value that the track bar value should be increased in every tick. Here is my code:
while (SeekBar.Value < 100)
{
System.Windows.Duration duration = Player.NaturalDuration;
SeekBar.Value += duration.TimeSpan.Seconds / 100;
Thread.Sleep(duration.TimeSpan.Seconds * 10);
}
Now I think this should be run in a separate thread right ?
but the problem, when i do that, I get the annoying message of saying that the TrackBar is being used in a thread other than the thread it was created on ..
how can i get around that ?
I wanted to put this code in both the MouseLeave and MouseEnter events, but it's pointless, cuz it will be on the same thread and the app will freeze..
How can i make the bar move by it self ?
You should create a WinForms Timer with an interval or 1,000 milliseconds, and update the trackbar in its Tick event.
It sounds like you have the hardest part done (mapping the length of the scroll bar to the length of the audio file). The rest is easy.
You can tackle this with a Timer. Set the interval to some value (such as 1000ms or maybe 500ms, and in Tick, update the scrollbar (call SeekBar_Scroll). For the exact interval, test and see what looks the most natural.
A while-loop will probably eat unnecessary CPU, not to mention lock up the rest of your application.
To get around the control updating, you need to check this SO question. You need to check control.InvokeRequired and set it via the Invoke method if that's the case.
I have a situation where I am animating part of my XAML application, and I need to wait for the animation AND rendering to complete before I can move on in my code. So far the tail end of my function looks like:
ProcExpandCollapse.Begin();
while (ProcExpandCollapse.GetCurrentState() != ClockState.Stopped) { }
}
Which, in theory, will wait until the animation is finished. But it will not wait until the rendering is finished - the thread drawing the application might still not have re-drawn the animation.
The animation is expanding a UIElement, and then the next part of my code uses it's rendered size to do some things. My question then is, how do I wait until my UI Element is re-rendered before moving on?
I finally found an answer (although I had to pay for consulting services)! Essentially what I ended up doing was putting the bit of code which uses the rendered controls on the control dispatcher's queue with very low priority, so that it naturally renders before handling that task. For example:
mycontrol.Dispatcher.BeginInvoke((Action)delegate{textbox1.Text = "Grazie";});
mycontrol.Dispatcher.Invoke((Action)delegate{GetScreenshot();}, DispatcherPriority.Background);
The code will then procede after GetScreenshot is called and finished, which will be after the rendering is completed (because rendering has higher priority than background).
The Storyboard object has a 'Completed' event you can subscribe to. It will be called when the animation is done. This what you are looking for?
Basically I think you want "ProcExpandCollapse.Completed +=" (then hit tab a view times, VS will generate a snippet for you)