Storyboard as timer in WPF - wpf

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.

Related

WPF: What is the correct way to wait for a storyboard animation to complete before performing an operation

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_Com​pleted;
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

Continuously updating an OpenTK GLControl in WindowsFormsHost in WPF - How?

I have an OpenTK GLControl embedden in a WindowsFormsHost in my WPF application.
I want to continuously update and render it.
In Winforms a solution would be to attach the UpdateAndRender method to the Application.Idle event, but there is no such thing in WPF.
So what would be the best way to do (60FPS) updating of my scene and GLControl ?
You can use a System.Timers.Timer to control how often your render code is called. In your window containing the GLControl-in-WindowsFormsHost, declare a private System.Timers.Timer _timer;, then when you're ready to start the rendering loop, set the timer interval and it's event handler, then start it up, as in the following example:
private void btnLoadModel_Click(object sender, RoutedEventArgs e)
{
LoadModel(); // do whatever you need to do to prepare your scene for rendering
_timer = new System.Timers.Timer(10.0); // in milliseconds - you might have to play with this value to throttle your framerate depending on how involved your update and render code is
_timer.Elapsed += TimerElapsed;
_timer.Start();
}
private void TimerElapsed(object sender, ElapsedEventArgs e)
{
UpdateModel(); // this is where you'd do whatever you need to do to update your model per frame
// Invalidate will cause the Paint event on your GLControl to fire
_glControl.Invalidate(); // _glControl is obviously a private reference to the GLControl
}
You'll clearly need to add using System.Timers to your usings.
You can use Invalidate() for it. This causes the GLControl to redraw it's content.
If you call it at the end of Paint() you may blocking some UI rendering of the other WPF controls.
WPF provides a per frame render event: CompositionTarget.Rendering. This event is called before WPF wants to render the content. Subscribe from it and call Invalidate:
public YourConstructor()
{
//...
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
void CompositionTarget_Rendering(object sender, EventArgs e)
{
_yourControl.Invalidate();
}
You need to unsubscribe if you don't use it anymore (to avoid memory leaks).
Here is a How to: Render on a Per Frame Interval Using CompositionTarget from MSDN.
I use GLControl with that method and it works fine. I did not checked how much FPS I have but it feels smooth.
You may also have a look on this: Why is Frame Rate in WPF Irregular and Not Limited To Monitor Refresh?

idle state detection silverlight 4 application

What's the best way to detect idle state for a silverlight application? I have read quite a few articles on the net by now and usually they are either for wpf/mobile apps etc.
I have created a DispatcherTimer which locks the screen after 5 minutes and it seems that I will have to go to every widget in every screen(my application has around 4-5 screens) and add a mousebuttondown or mouseenter eventhandler to reset this timer. This doesn't seem to be efficient but just adding the handler to the layroot is not helping either.
Any helpful suggestions?
Thanks
You don't need to modify every control. If you add the following code on startup:
Application.Current.RootVisual.MouseMove += new MouseEventHandler(RootVisual_MouseMove);
Application.Current.RootVisual.KeyDown += new KeyEventHandler(RootVisual_KeyDown);
With the following event handlers:
private void RootVisual_KeyDown(object sender, KeyEventArgs e)
{
idle = false;
}
private void RootVisual_MouseMove(object sender, MouseEventArgs e)
{
idle = false;
}
Where idle is the variable you use in your DispatcherTimer Tick event to check if things are happening or not.
As events bubble up the tree this should work for all your controls.
Handled events will not bubble up to root control. Instead you should use the AddHandler method with handledEventsToo = true.

What is the point of using Storyboard as timer?

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.

WPF ComboBox DropDown part appears in the wrong place

I put several ComboBoxes on a XAML window. When I expand any of them, the DropDown part appears on the upper left corner of the screen.
I use Visual Studio 2008 C# Express. I don't remember this phenomenon when I used Visual Studio 2008 (Trial Version), though I use the same FrameWork (3.5).
It seems to be a bug.
Workaround:
Use Window.Show() instead with a custom logic to simulate the ShowDialog() behavior.
This appears to be a bug in WPF. In my case, I was trying to open a window in the Loaded event of another window. To get around this, I set a timer up to fire, then used a delegate to open the window (cannot open the window in a timer event because the calling thread that opens a window must be STA).
Edit - timer isn't necessary - didn't see the answer above just queue it on the dispatcher...
private delegate void DelegateOpenWindow();
private DelegateOpenWindow m_DelegateOpenWindow;
private Timer loginTimer = new Timer(200);
private void MainWindow1_Loaded(object sender, RoutedEventArgs e)
{
// create delegate used for asynchronous call
m_DelegateOpenWindow= new DelegateOpenWindow(this.OpenWindow);
// start a timer to fire off the open window.
loginTimer.Elapsed += loginTimer_Elapsed;
loginTimer.Enabled = true;
}
void loginTimer_Elapsed(object sender, ElapsedEventArgs e)
{
loginTimer.Enabled = false;
this.Dispatcher.BeginInvoke(m_DelegateOpenWindow);
}
void OpenWindow()
{
MyWindow w = new MyWindow();
w.Owner = this;
w.ShowDialog();
}
I started observing this (and other strange behavioral quirks) yesterday when I tried to "tweak" window sizes, shapes, colors, and invoke a log-on dialog from the Window.Loaded event handler. I had been doing this just fine in each of a dozen+ individual "MVVM" pattern apps. Yesterday, I decided to move this from each app's code behind into a consolidated code-behind base class, since the pre-processing had become common in all those apps. When I did, the drop-downs in two ComboBoxes in the log-in dialog suddenly appeared in the upper left corner of my screen. I seem to have "solved" it by using the following technique (your mileage may vary):
protected void WindowBaseLoadedHandler(object sender, RoutedEventArgs e)
{
...non-essential lines of code removed...
if (DataContext != null)
{
Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
{
/*----------------------------------------------------------------------
* Do we have a View Model? If so, perform standard VM Initialization...
*---------------------------------------------------------------------*/
this.IsEnabled = false;
LoginDlg loginDlg = new LoginDlg();
loginDlg.ShowDialog();
if (!loginDlg.Success)
{
/*-----------------------------------
* Log on failed -- terminate app...
*----------------------------------*/
...termination logic removed...
}
this.IsEnabled = true;
}));
}
WindowBaseLoadedHandler is the Loaded event handler. LoginDlg is a WPF app with a dialog containing two ComboBoxes.
Recap: After I consolidated the code into the Loaded event handler of the base class the ComboBox's drop down lists appeared in the upper left corner of my screen. Once I wrapped the logic into the Dispatcher.BeginInvoke call, the appropriate ComboBox behavior returned with lists below the current item.
I suspect WPF needs the application to return from the Loaded event to complete the layout system's initialization. That doesn't fully explain why it worked before, but I'll have to queue up my desire to hunt that "why" down for some rainy day in the future and celebrate overcoming the latest obstacle for today.
In any event, I hope someone finds this of use.
I'm using the latest .Net 4.5 and WPF framework and I still have this problem. One thing I noticed is that it only happen when there's an attached debugger. When the debugger is not attached, everything works fine.
I had the same problem on Visual Studio 2019.
Using window.Show() can help but it can ruin your design.
The solution is to open the window asynchronously.
var yourDialog= new YourDialog();
yourDialog.Owner = this;
TaskCompletionSource<bool?> completion = new TaskCompletionSource<bool?>();
this.Dispatcher.BeginInvoke(new Action(() =>
completion.SetResult(yourDialog.ShowDialog())));
bool? result = await completion.Task;
You can also create a more elegant solution by making the extension method:
public static class AsyncWindowExtension
{
public static Task<bool?> ShowDialogAsync(this Window self)
{
if (self == null) throw new ArgumentNullException("self");
TaskCompletionSource<bool?> completion = new TaskCompletionSource<bool?>();
self.Dispatcher.BeginInvoke(new Action(() => completion.SetResult(self.ShowDialog())));
return completion.Task;
}
}
And you can use it like this:
await dlgReview.ShowDialogAsync();
It’s a bug in WPF (not the only one, I'm afraid). It happened when I opened another window in the Loaded Event, something like:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Window selectionWindow = new SelectionWindow();
bool? result = selectionWindow.ShowDialog();
if (result == true)
RecordChanged();
}
I already found a workabout.

Resources