I've seen some examples of code where StoryBoard is used as Timer, such as:
void Update()
{
if (_sb == null)
{
_sb = new Storyboard();
_sb.Completed += _sb_Completed;
_sb.Duration = new Duration(TimeSpan.FromSeconds(1));
}
if (_sb_completed)
{
_sb.Begin();
_sb_completed = false;
}
}
void _sb_Completed(object sender, EventArgs e)
{
PerformUpdate();
_sb_completed = true;
}
Is Storyboard at some point better than Timer? Why do people use it?
P.S. Question is related to Silverlight and/or WPF.
A Storyboard, like a DispatcherTimer, runs on the UI thread. So in either case you will never see a cross thread exception.
You can use a Storyboard over a DispatcherTimer because the Storyboard has a higher priority.
But im not sure about the Timer object itself as I've never really used it in favor of the Storyboard or DispatcherTimer.
Using a Storyboard is different from using a standard System.Threading.Timer. The Storybroad operates on the main thread hence the Completed even can manipulate the UI elements without getting cross thread exceptions. The standard Timer callback doesn't run on the UI thread and will therefore need additional help to manipulate UI elements.
However as Mark points out if all that is really needed is a delay then a DispatcherTimer would be the more intuative choice. The difference with DispatcherTimer is that its designed to invoke its Tick event regularly whereas the Storyboard will only call Completed at most once for each call to Begin. A DispatcherTimer can be used in this way by calling its Stop method in the first Tick event occurance.
Related
The gist of the code is
Storyboard story = new Storyboard();
DoubleAnimation anim = new DoubleAnimation();
anim.Completed += anim_Completed(object sender, EventArgs e);
...
story.Children.Add(anim);
story.Completed += story_Completed(object sender, EventArgs e);
story.Begin(control, true);
return;
In another method I have:
// Finish the Storyboard now
story.SkipToFill(control);
// I want it to get back to me here after the Completed events have run.
The problem is that the Completed events don't run until the next pass of the WPF dispatcher message loop which is no good to me because they update some state. I also tried
story.Stop(control);
but then the Completed handlers don't get run at all it seems. Is there a way to get the Completed handlers to fire immediately?
To avoid running your storyboard on another ui thread and handling locks or signals you can try the following:
Action emptyDelegate = delegate() { };
control.Dispatcher.Invoke(DispatcherPriority.Render, emptyDelegate);
...to force a render pass and hence pick up the completed events that don't seem to fire when you need them to during processing in the UI's code-behind.
You may see some artefacts come into and out of existence though as any updated dependency property values will, of course, be rendered.
I have a storyboard which I reuse to animate some pictures, I wanna perform some operation after each animation, which includes some calculations, and then running another animation, so I believe I should be using the StoryBoard's Completed Event MyStoryboard.Completed += storyboard_Completed;
What I'm curious about is, should I start the next animation in the current StoryBoard's Storyboard_Completed Event? And, are there any implications if I started the first animation in a separate thread using the Application.Current.Dispatcher Object?
If I called a StoryBoard.Begin() in a separate thread using the Application.Current.Dispatcher, does the Storyboard_Completed Event also get invoked in the UI thread? In this case, do I still need to wrap the Next Animation within another Dispatcher Invoke?
private void Story_Completed(object sender, EventArgs e)
{
Application.Current.Dispatcher.Invoke((Action)delegate()
{
SomeNewStoryBoardAnimation.Begin();
}
}
Is this correct? Or is there a better way to check if a storyboard has ended and start the next set of calculations & storyboard animation right after that?
I've thought of using a single background worker to handler all animations and calculations in sequence, but I'm also wondering how to "wait" for the animation to complete before starting on the next set of calculations and animations. Is it normal for a BackGroundWorker to have Thread.sleep while waiting for animation to complete?
You could wrap the Storyboard in a Task object and await its completion.
Here is an excellent bit of sample code illustrating how to do just that, taken from a blog post by Morten Nielsen:
public static class StoryboardExtensions
{
public static Task BeginAsync(this Storyboard storyboard)
{
System.Threading.Tasks.TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
if (storyboard == null)
tcs.SetException(new ArgumentNullException());
else
{
EventHandler<object> onComplete = null;
onComplete = (s, e) => {
storyboard.Completed -= onComplete;
tcs.SetResult(true);
};
storyboard.Completed += onComplete;
storyboard.Begin();
}
return tcs.Task;
}
}
Essentially you're creating an extension method, which returns a Task object signalling the completion of the Storyboard. In this way, you get some nice fluid syntax like this:
//Start the storyboard and asynchronously await completion...
await myStoryboard.BeginAsync();
//Do my other stuff here, after the storyboard completes...
Using the Storyboard.Completed event should work for your purposes. The Storyboard.Completed event handler should fire on the UI thread, so you should not need to call Application.Current.Dispatcher.Invoke to fire off the second Storyboard.
There should be no implications if you call the original Storyboard.Begin using Application.Current.Dispatcher.Invoke. This won't launch the storyboard animation on a new thread. It will asynchronously invoke the animation on the main UI thread. Whether you call Begin on the UI thread yourself or whether you use Application.Current.Dispatcher.Invoke to do it, the final result should be the same. Your completed event handler will fire when the storyboard finishes, and you can perform your calculations and fire off the next storyboard.
See the following question for some discussion of storyboard having being used in the past as a timer because of the fact that it runs on the UI thread:
What is the point of using Storyboard as timer?
Also, this is probably overkill for the specific case you are describing, but if you need to orchestrate a bunch of sequential, asynchronous operations, you could use Reactive Extensions:
http://msdn.microsoft.com/en-us/data/gg577609.aspx
The following article includes a sequential storyboard example (though the article is old enough that the syntax has probably changed):
http://www.wintellect.com/cs/blogs/jlikness/archive/2010/08/22/coroutines-for-asynchronous-sequential-workflows-using-reactive-extensions-rx.aspx
I have a helper class I've written which can be used to run a long running task on my GUI. What it does is use styles to display a "working" animation and fades out the content so while the task is running, the user can see that something is in progress.
My problem is that when the long running task completes, it fades the content back in and hides the working animation - which is what it should do, but because I am using MVVM and primarily data binding for all my content display, the updates to the GUI components happen separately to the long running task. ie the data binding OnPropertyChanged("") events fire and then these are picked up by the GUI thread AFTER the long running task completes. But the problem is the Worker Animation closes when the long running task completes, but BEFORE the data bindings update.
So the end result is you get the worker animation displaying as expected while the task runs, but the data binding update takes a good 4-5 seconds or even longer for large datasets for all the tree data and during this time, the application is not in "working animation mode" and just freezes.
Is there a way I can have my worker animation continue to run not only for the Long running Method, but for the associated data binding updates from OnPropertyChanged as well?
Consider using BusyIndicator from Extended WPF toolkit. It should provide functionality you described. It has IsBusy property which you can bind to property in your ViewModel and set it to False after all work is done.
You can always change the style of BusyIndicator same way as you do with other controls. In my solutions I always use this control along with BackgroundWorker class from System.ComponentModel and I usually set IsBusy=false at the end of RunWorkerCompleted
private void LongRunningMethod()
{
this.IsBusy = true;
var worker = new BackgroundWorker();
worker.DoWork += this.LongMethodDoWork;
worker.RunWorkerCompleted += this.RunWorkerCompleted;
worker.RunWorkerAsync();
}
private void LongMethodDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
{
...
}
private void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs runWorkerCompletedEventArgs)
{
...
this.IsBusy = false;
}
Thanks all for the answers. I've actually come across a solution that may be a bit controversial as some would construe it is a little bit of a hack, but it does exactly what I want it to do and there seems to be no other way to do it, so to me that is a code solution, not a hack.
I'm using the WPFBackgroundProgressIndicator open source project I downloaded from codeproject (I think) which has the option to show the busy indicator in the main content with or without a fade out, or as a popup and it runs as a background thread which is ideal and why I chose it.
The problem was that when you run a long running method, the code execution completes synchronously but all the binding OnPropertyChanged("") updates run asychronously and queue on the Dispatcher thread, so your work method completes before the WPF controls have a chance to call the Getters of the dependency properties, to retrieve the new value. What you need to do is effectively "block" until all the Dispatcher events have completed and that is why not everyone will like this solution as it "blocks", but then that is exactly what I am trying to do. I WANT to block the application until the full update has completed as I dont want the user to be able to do anything visually while data is still rendering, so that is my requirement. Clean blocking is preferable to messy interaction.
So the solution, believe it or not, is a single line of code just after the work method call. It is as follows.
Application.Current.Dispatcher.Invoke(new Action(() => { }), DispatcherPriority.ContextIdle, null);
Which as you can see effectively queues a new task on the Dispatcher thread and blocks current code execution until it finishes, but as you give it the lowest priority, this call will wait until all OTHER dispatcher execution finishes, ie all rendering completes. Once render is complete, this line will be executed and you will exit with all rendering complete. The full method I have used it in context is below. I welcome your thoughts and discussion on this approach.
public void LongRunningTaskWithFade(BusyDecorator busy, Action longTask)
{
if (loading) return;
loading = true;
busy.FadeTime = TimeSpan.Zero;
busy.IsBusyIndicatorShowing = true;
// in order for setting the opacity to take effect, you have to delay the task slightly to ensure WPF has time to process the updated visual
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
try
{
longTask();
Application.Current.Dispatcher.Invoke(new Action(() => { }), DispatcherPriority.ContextIdle, null);
}
finally
{
HideBusyDisplay(busy);
}
}), DispatcherPriority.Background);
}
Is there any way I can detect a long touch over a TextBlock (or a Label)?
It is possible to do that in an awaitable fashion. Create a timer with specific interval. Start it when user tapped and return the method when timer elapsed. If user release the hand, return the method with false flag.
public static Task<bool> TouchHold(this FrameworkElement element, TimeSpan duration)
{
DispatcherTimer timer = new DispatcherTimer();
TaskCompletionSource<bool> task = new TaskCompletionSource<bool>();
timer.Interval = duration;
MouseButtonEventHandler touchUpHandler = delegate
{
timer.Stop();
if (task.Task.Status == TaskStatus.Running)
{
task.SetResult(false);
}
};
element.PreviewMouseUp += touchUpHandler;
timer.Tick += delegate
{
element.PreviewMouseUp -= touchUpHandler;
timer.Stop();
task.SetResult(true);
};
timer.Start();
return task.Task;
}
For more information, read this post.
As far as I know there is no built in way so you would have to do something like this
• Capture the start time on the TouchDown event of the control
• Compare this to the release time in the TouchUup event
• If the two are different by X then run your long touch code
There might be a few things you have to code around but that is the basic idea
There is an event called TouchAndHoldGesture and PreviewTouchHoldGesture
Long touch, or press and hold as I think it's formally named, can be detected through the right click event.
It may be that, if you are using a Surface Window, that the right click event is disabled.
There is a ContactHoldGesture event on all Surface controls that you can use. But, and I say this as the guy responsible for creating this feature during my time at Microsoft, this event is very poorly designed and should not be used. It doesn't tell you when the system has decided that a finger has moved too much to count as a "hold" and it doesn't give you information needed to draw an animation telling the user that a "hold" is underway. Your much better off doing what #Kevin suggested and building your own implementation.
I'm trying to do smooth animation in procedural code. For this (in Silverlight at least), it's recommended to use the Storyboard timer rather than a DispatcherTimer.
So I use something like this:
Storyboard _LoopTimer = new Storyboard();
public void StartAnimation()
{
_LoopTimer.Duration = TimeSpan.FromMilliseconds(0);
_LoopTimer.Completed += new EventHandler(MainLoop);
_LoopTimer.Begin();
}
void MainLoop(object sender, EventArgs e)
{
// Do animation stuff here
// Continue storyboard timer
_LoopTimer.Begin();
}
And in Silverlight, this works fine. But in WPF, I only hit MainLoop() once. Setting RepeatBehaviour to Forever doesn't help, either.
So what's the right way to do this in WPF with a Storyboard?
Thanks very much.
I thought that using a Storyboard in Silverlight was just a hack to make up for there being no DispatcherTimer in the early versions.
In any case, you need to restart your Storyboard once it has finished. It will not automatically restart itself.