WPF DoubleAnimationUsingKeyFrames skipping frames - c#

I am attempting to animate a slider using DoubleAnimationUsingKeyFrames and DiscreteDoubleKeyFrame to animate a slider for each key frame. However, it seems to be skipping frames when the storyboard is played, that is, the ValueChanged event is not firing for every key frame. The storyboard and animation are set up in the code-behind as follows:
DoubleAnimationUsingKeyFrames _timelineAnimation = new DoubleAnimationUsingKeyFrames();
Storyboard _timelineStoryboard = new Storyboard();
void SetupTimeline()
{
// set up timeline storyboard animation
_timelineAnimation.SpeedRatio = 1.0;
Storyboard.SetTarget(_timelineAnimation, timelineSlider);
Storyboard.SetTargetProperty(_timelineAnimation, new PropertyPath(Slider.ValueProperty));
_timelineStoryboard.Children.Add(_timelineAnimation);
timelineSlider.ValueChanged += TimelineSlider_ValueChanged;
}
void StartTimeline(List<double> times)
{
foreach (double time in times)
{
double value = time - timelineSlider.Value;
var keyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(value));
_timelineAnimation.KeyFrames.Add(new DiscreteDoubleKeyFrame(time, keyTime));
}
_timelineStoryboard.Begin(timelineSlider, true);
}
// this does not fire for every key frame
void TimelineSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
Debug.Print($"TimelineSlider_ValueChanged {e.NewValue}");
}
Example input data (in seconds):
136.224
136.238
136.244
136.2441
136.246
136.2461
136.264
136.274
136.294
136.2941
136.296
136.2961
I imagine that the problem may be that the data items are too close together. Is there any way to speed up the resolution of the animation timer? Or any other suggestions as to a solution?

In fact, when the interval between two frames is set below 30 milliseconds or so, some frames might be skipped.
This is much like a WPF DispatcherTimer. A WPF DispatcherTimer, no matter how small its Interval is, only ticks once in every (about) 30 milliseconds.
To produce animation effect, WPF uses a time manager to signal an update periodically. When it ticks, the animated properties (in this case, the Value property of the Slider) are re-evaluated and the UI is updated. And it apparently picks the “most recent” frame in the timeline and discard those are already outdated.
The time manager "ticks" many times per second; the actual number of ticks that occur each second varies depending on available system resources.
Even if you manage to speed up the resolution timer - I guess it is not possible for a UI timer, say, make it tick every 1 millisecond, human eyes can't perceive such a high frequency in the animation, and, monitors display at only 50-60 Hz.

Related

Picture box moves with timer, but it lags behind?

I'm not sure how to explain this. I have the code set up so when the timer ticks, it checks to see if the picture box is at a certain location. If it isn't, then it should move a little bit closer. It works in moving, but each time it moves, it generates a white field behind it, and it grows. I have the tick set to 750, so each time it ticks, the picture moves farther than it should, and each time it goes farther than the previous time. Here's the code.
private void ZombieTimer1_Tick(object sender, EventArgs e)
{
ZombieTimer1.Tick += new System.EventHandler(ZombieTimer1_Tick);
enemyNPC1.Show();
ZombieTimer1.Start();
if (enemyNPC1.Location.X < 280)
{
enemyNPC1.Left = enemyNPC1.Left + 1;
ZombieTimer1.Stop();
ZombieTimer1.Start();
}
}
Move the declaration of the event handler to the constructor for your window.
At the moment you get another event handler every time the event fires, which causes the event to fire twice the second time, and four times the next time, etc.

Only 1 of 2 progress bars gets updated in BackgroundWorker

I have a simple form with 2 progressbars and 1 backgroundworker on it. I have 2 loops (one within the other) and I'd like to report back the progress of each loop once incremented. Here's the code I have:
private void buttonStart_Click(object sender, EventArgs e)
{
workerCustomers.RunWorkerAsync();
}
private void workerCustomers_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
progressBar2.Value = (int)e.UserState;
}
private void workerCustomers_DoWork(object sender, DoWorkEventArgs e)
{
for (int customer = 0; customer < 50; customer++)
{
int customerPercentage = ++customer * 100 / 50;
workerCustomers.ReportProgress(customerPercentage, 0);
for (int location = 0; location < 500; location++)
{
int locationPercentage = ++location * 100 / 500;
workerCustomers.ReportProgress(customerPercentage, locationPercentage);
}
workerCustomers.ReportProgress(customerPercentage, 0);
}
}
When the program runs, progressbar1 gets updated just fine, but progressbar2 never moves. If I run it through the debugger, I can see the value of progressbar2 being changed, there's just no change graphically. Any ideas?
Yes, the 2nd progress bar will fail to update if you have Aero enabled on your desktop. Which provides the animated progress bar, the one that's green by default with the traveling highlight note.
This progress bar style is special, it interpolates intermediate values and always looks smooth, even if you assign course values to the Value property. In other words, you can assign 10, 20, 30, etcetera but the bar doesn't jump. It smoothly increases the bar length.
This has side-effects. To make this animation work, the progress bar must lag. In other words, it is always showing a value that's behind the programmed value. And some time must pass for it to smoothly increase the bar length to the programmed value.
One more detail you need to know: this animation does not happen when you decrease the Value property, that immediately jumps the bar length.
So the problem is that you are updating the Value property at a very high rate, much faster than the bar can interpolate. It can never catch up. Except when you restart at 0, that immediately jumps the bar. The rate is so high that the animation can never make it to 1.
You can see this by inserting this statement inside the inner loop:
System.Threading.Thread.Sleep(1);
Play with the argument value, you may need to increase it to 16 to see any difference. The higher you make it, the further the bar makes it by interpolation.
This is of course not a real problem, this happens because your worker isn't doing any real work. As soon as you start writing the real code this BGW is supposed to do then you'll also decrease the rate at which you call ProgressChanged.
The problem lies in the fact that your inner loop is so tight that the gui updates are posting too frequently for the gui to accurately respond to them. The solution is to add your real work to the inner loop, so it takes longer or if you want to simulate the GUI, you can just add a Thread.Sleep(100) to simulate the work taking some time.
Also ensure that your progress bar maximums are set to 100, as that is the maximum value you are returning for each.
Your main problem comes from a too fast inner loop which means you cant actually see the progress since the updates are too fast for the progressbar to handle.
Your line of code:
location * 100 / 500
will equal to 100 when you finish the inner loop, so having the progressbar max value set to 500 does not relate well to that number. It means your progressbar will never reach max value.

"Animating" a MapPolyLine in Silverlight

I need to animate a MapPolyLine such that on a given event, the start pin zips up to the end pin. The approach I am considerg is to animate frames such that I divide the MapPolyLine into n number of segments and decrease the TimeSpan ts between each frame along the logic of my chosing (to keep things simple, let's just say ts = ts / 2 after each cycle).
I know that one cannot animate the MapPolyLine, but one can change the appearance of the line by updating the latitude and longitude of the end position. My question concerns timing. My experience with multithreading is minimal, so I did not want to take the risk of a user running into a threading based error that may be difficult to diagnose. Should I:
use a simple DispatcherTimer and tick method
use a BackgroundWorker that reports progress every-time the TimeSpan has elapsed
use a dummy animation and attach an event handler to the rendering event
solution other than the above mentioned options?
Thank you in advance for your help!
Decided to use the DispatcherTimer considering the amount of time the animation is going to take - by creating a separate animation object that holds state and its own dispatch timer, it ended up being more efficient than using separate threads because the callback would mean interrupting the main UI thread and based on the requirements it was highly unlikely that there will be more than 2-3 of these animations occuring simultaneously with 95% or greater being only 1 animation at a time.

C# Executing timed commands with varying times

I have a timer control and a grid with a List of coordinates for the grid. I was wondering how I could use the timer control or any other control in order to execute code in the interval, coordinate.Time as it varies for each coordinate. Also, thread.sleep(time) is not an option for me.
foreach (Coordinate coordinate in this.Macro)
{
coordinate.Time;
coordinate.Initial;
coordinate.Final;
... executecode # coordinate.Time.
}
Just create a new Thread per each coordinate with a Timer set to the specified interval.

Form opacity animation in C# with a BackgroundWorker

With the help of the BackgroundWorker, I created an opacity animation for some form.
There's only one tiny issue with this approach but I can't understand where is the problem. The animation speed is configurable and even if the speed value is very high, sometimes the animations is very, very slow, for some odd reason...
The "slow animation" I'm talking about it's not stutter, the animation is actually very smooth, it just takes more time to perform the whole animation (from 0% to 100%, or vice-verse). This only happens from time to time. It seems (not sure) that it happens when the computer is doing some other, somewhat intensive, background action.
I need to fix that of course but I also would like to know if there's anyway you would improve this code or if you would do it differently and/or better.
Here's my code:
private const int TOGGLE_EFFECT_SPEED = 10;
private void blendWorker_DoWork(object sender, DoWorkEventArgs e) {
bool blendIn = (bool)e.Argument;
// Loop through all opacity values
for(double value = 1; value <= 100; value += 1) {
// Report the current progress on the worker
blendWorker.ReportProgress(0, blendIn ? value : 100 - value);
// Suspends the current thread by the specified blend speed
System.Threading.Thread.Sleep(11 - TOGGLE_EFFECT_SPEED);
}
// Set the worker result as the inverse tag value
e.Result = !blendIn;
}
private void blendWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) {
double opValue = (double)e.UserState;
// Show and repaint the whole main notes window?
if(opValue == 1.0) {
Show();
Invalidate(true);
}
// Set the main notes window opacity value
Opacity = (double)e.UserState / 100;
}
private void blendWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
bool tagFlag = (bool)e.Result;
// Hide the main notes window?
if(tagFlag) {
Hide();
}
// Set the main notes window tag value
Tag = tagFlag;
}
/*
THE FOLLOWING METHOD IS PART OF A DIFFERENT CLASS.
ACTUALLY, IT'S THE "PROGRAM" CLASS WHERE MAIN()
IS LOCATED. THIS METHOD IS CALLED TO SHOW/HIDE
THE MAIN APPLICATION FORM WITH AN OPACITY ANIMATION
*/
internal static void ToggleNotesWindow() {
// Get the tag value converted to boolean type
bool tagFlag = Convert.ToBoolean(NotesWindow.Tag, CultureInfo.InvariantCulture);
// Bring the main notes window to front?
if(tagFlag) Program.NotesWindow.BringToFront();
// Run the blend effect if it's not already running
if(!NotesWindow.blendWorker.IsBusy) {
NotesWindow.blendWorker.RunWorkerAsync(tagFlag);
}
// Activate and focus the main notes window?
if(tagFlag) Program.NotesWindow.Activate();
}
Each time you change the opacity of the form, Windows has to redraw all the windows below it, the window itself and then apply opacity (Vista does this much faster and buffered). Since you're stepping through each opacity state from 1...100 this process has to complete 100 times. Sometimes redrawing your window or a window below it will be slow.
The Thread.Sleep method with a value > 0 will sleep from 0...~10ms no matter what value you pass. The thread scheduler timer resolution on Windows is approx 10ms (Again, Vista and other OS change and optimize so it's not exact) so you can't schedule a time slice smaller than that. 100x10ms + the time to actually render might take 2 whole seconds to fade in/out.
The way to speed it up is to reduce the number of re-draws. Rather than stepping +1 for opacity, step +5, +10, etc. That reduces the total number of re-draws required and would result in a form fading in 100ms instead.
Overall I don't see much that would warrant a change at least at first glance. If you are seeing some performance bottlenecks you might try having a look at Ants Profiler or a similar code profiling tool to see if you can pinpoint any slow sections.

Categories