Storyboard.Begin() occasionally blocked or delayed - c#

Our application uses an animation to fade out a message window when it's closed. To do this, we override OnClosing, and if we haven't completed our animation, we cancel the close and start the animation. When the animation completes, we close the window. This seems to be a fairly common pattern based on what I've seen online, since Closing and Closed aren't routable events.
The problem is, we occasionally see a window get "stuck" - the animation doesn't seem to occur, so the "finished" flag never gets set and the window just sits there, canceling any close events. Sometimes, the animation eventually kicks off and the window closes, but other times it seems like its permanently stuck (though obviously you can't tell for sure...halting problem and all that...).
Does anyone have any thoughts as to why the Storyboard won't get kicked off even after calling Begin()? Without having been able to dig in really deep into the Storyboard code/data structures, it feels like it's waiting for something to happen before it actually kicks off, that never happens.
Here are some of the possibilities that I've ruled out so far:
incorrect threading (all windows, animations, storyboard, etc. are created/handled/accessed on the GUI thread)
GUI thread blocked (clicking the X still fires the Closing event, and the GUI thread responds to events in other windows, system tray, etc.)
GUI thread busy (we're only doing UI work on the GUI thread, and we generally only have one window open and at most one "close" animation running at a time)
That said, here are some things that might be contributing:
Windows can close each other (in some cases if one is already open, the new one will close the existing one)
We also have a "fade in" animation bound to the Loaded event via XAML, but that seems to complete correctly.
We recently removed our "main" UI window, so the application's main window is now a dummy hidden window.
I suspect that last one most strongly because it was added most recently, and this behavior was never reported/noticed before that change (though I can't say definitively it never happened before that change). But other GUI/window-related events still all work, and the fade-out animation logic works most of the time.
Finally, here's the meat of our code for doing the closing animation:
public class MyWindow : Window {
private bool _storyBoardCompleted;
private Storyboard _closingStoryBoard;
protected Storyboard GetClosingStoryBoard()
{
Grid mainGrid = (Grid)FindName("MainGrid");
DoubleAnimation closingAnimation = new DoubleAnimation();
closingAnimation.From = 1;
closingAnimation.To = 0;
closingAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(500));
Storyboard.SetTarget(closingAnimation, mainGrid);
Storyboard.SetTargetProperty(closingAnimation, new PropertyPath(OpacityProperty));
Storyboard storyboard = new Storyboard();
storyboard.Children.Add(closingAnimation);
storyboard.Completed += StoryboardCompleted;
return storyboard;
}
private void StoryboardCompleted(object sender, EventArgs e)
{
_storyBoardCompleted = true;
Close();
}
protected override void OnClosing(CancelEventArgs e)
{
if (_closingStoryBoard == null)
{
_closingStoryBoard = GetClosingStoryBoard();
_closingStoryBoard.Begin();
}
if (!_storyBoardCompleted)
{
e.Cancel = true;
}
}
}
EDIT:
The issue seems to pop up whenever we open two of these windows very close together in time, and close the first before showing the second. Essentially:
window1.Show()
window1.Close()
window2.Show()
At this point, if we try to close window2, it will cancel the close as in the code above, but the animation won't begin (hence the problem). If we later on call window2.Close() followed closely by window3.Show() (i.e. show a third window), then window2's animation finishes and the window closes - but then window3 is stuck in the same state. So the behavior almost seems to get "passed on" from window to window once we are in this state.
EDIT:
I've ruled out another possibility - I was wondering whether the "fade-in" storyboard of the second window was somehow conflicting with the "fade-out" storyboard of the first, and somehow affecting the second window's fade-out. But the problem occurs even after removing the fade-in storyboard.
Furthermore, the problem goes away entirely if I replace the fade-out storyboard with just a half-second dispatch timer (i.e. just a delayed close, no animation). Just a sanity check that it definitely has something to do with the animation...

Did you ever try adding parameters to the Begin method:
_closingStoryBoard.Begin(this);

I never could quite pin down specifically why this was happening, but it seemed to be related to the fact that I was showing the second window while the first one was still animating. I still don't understand why this caused problems, but I was able to work around it by waiting for the first one to close completely before showing the second (basically hooking onto the first window's Closed event).
Would still love to know why this behaves this way, in case the workaround of "wait for the first window to close" ever stops being sufficient...

Related

Loading Second Window W/o Halting First

I have a button that opens a second window but the second window has a bit of processing to do before displaying. Rather than have the initial window die until the second screen appears I would like to inform the user that processing is happening. My thoughts on how to do this;
1) Call the second window in a second thread, have the UI in the first window display "Loading" etc, once the second window has completed pass an indicator to the first window to stop displaying "Loading".
I have read a bit about dispatcher & beginInvoke but still struggling a bit to get this to work. I was trying this out and in my Window1 initializer I have a thread.Sleep just to test out that my mainWindow is still working but it is not. Any help would be greatly appreciated.
You cannot open windows and do other UI tasks on worker threads. All these activities have to be performed on the main thread. You can do that by invoking on the dispatcher from the background thread:
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
// First do the work
Thread.Sleep(...);
// Then show the window
Application.Current.Dispatcher.Invoke(() => window1.Show());
}
Using this simple approach where you grab the dispatcher in event handlers can quickly get out of hand and as your application grows you might want to switch to a more strict programming model using for instance a MVVM framework of sorts.
From a UX perspective it might be better to show the second window immediately and then display a progress bar or spinner indicating that work is being performed. Then when the work is done hide this indicator and show the result of the work. The principle of updating the UI from the background thread using the dispatcher still applies.

Clearing mouse buffer in C#

I have a problem while doing this in the code-behind file of a winform :
// Waiting Cursor + disabling form
Cursor = Cursors.WaitCursor;
this.Enabled = false;
// Synchronous method
SomeWork();
// Re-enabling form
Cursor = Cursors.Default;
this.Enabled = true;
Current Behaviour
Clicking on a button for example during Somework() will execute the method associated to the button after re-enabling the form.
Expected Behaviour
I don't expect from the form to store the clicking events of the user while the form is disabled.
Question
Is there a way to empty the Clicking cache of the form (So that I'd do it before re-enabling the form) ?
IMPORTANT EDIT
A possible easy solution would be implementing the IMessageFilter interface in the code behind of the form. Disabling the left seems easy using this PreFilterMessage :
public bool PreFilterMessage(ref Message m)
{
// Blocks all the messages relating to the left mouse button.
return (m.Msg >= 513 && m.Msg <= 515) ;
}
But once again, disabling and re-enabling the mouse's left clicks DOES NOT EMPTY THE MOUSE BUFFER ...
The problem is that the process is running in the same thread, so the form doesn't actually get disabled before the process starts running. The easy thing to do would be use Application.DoEvents() to force it to set everything to disabled before starting the process, but the more professional (and probably safer) method is to run the time-consuming process in another thread.
NOTE: After running into another hitch in my own programming I found that you may have to run Application.DoEvents() before enabling everything again--it will fire any clicks the user made on the disabled controls, instead of waiting for the process to complete--enabling the controls--and THEN firing the click.
Obviously DoEvents is messy and I should be using threads.

XNA Game does not update while "dragging" window, any event handlers?

I'm new with XNA and C#, and I've come to the point in my XNA project where I need event handlers to predict when the game loses focus so that I can sync music and visuals once it gains focus again. But I got one problem; the game does not "update" while being dragged, but I can't seem to find an suitable event listener for this. I've tried:
System.Windows.Forms.Control.FromHandle(Window.Handle).Move += new EventHandler(DeactivateGame);
This one calls "DeactivateGame" tons of times while moving the window. But even if it works, despite the fact it calls the function more than once, I can't see a event handler that calls a function when the window handle is released so that the game can resume again by calling "ActivateGame"
A sidenote (if it helps);
this.Activated += new EventHandler<EventArgs>(NotifyActivated);
this.Deactivated += new EventHandler<EventArgs>(NotifyDeactivated);
These event handlers works fine when minimizing the window or putting focus on something else than the game window, but it does not register the window being dragged. Maybe obvious for the one used to programming, but I just want to make sure I've given enough information
EDIT:
The function I want to add as the result of the event handler is a DateTime/TimeSpan that gets called when the window is out of focus or dragged. When dropped or gets focus again, this will compare the current time with the time set when the window lost focus to calculate the lost time in between.
For detecting when the XNA window is being dragged, you were on the right track using the Window.Handle with Windows Forms. You can simply listen to the ResizeBegin and ResizeEnd events to know when the user starts moving the window and when they release it.
var xnaWinForm = (System.Windows.Forms.Control.FromHandle(Window.Handle) as System.Windows.Forms.Form);
if (xnaWinForm != null)
{
xnaWinForm.ResizeBegin += new EventHandler(xnaWinForm_ResizeBegin);
xnaWinForm.ResizeEnd += new EventHandler(xnaWinForm_ResizeEnd);
}
And here's what the event handlers look like.
void xnaWinForm_ResizeBegin(object sender, EventArgs e)
{
// XNA window is starting to be moved.
}
void xnaWinForm_ResizeEnd(object sender, EventArgs e)
{
// XNA window was released and is no longer being moved.
}
Then just combine this with the other events you mentioned for determining when the window is minimized/restored/active to determine how long the window has been "inactive" for.

How to show a dialog form while main app works?

I want to have a loading form showing up while the main app connects to db and fetching initial data. Because this can take up 5-10 secs I want to inform the user that something is happening.
This is what I have come up with:
frmStart _frmStart = new frmStart();
Thread t = new Thread(() => _frmStart.ShowDialog());
t.Start();
objDAL = new DBManager();
objDAL.conSTR = Properties.Settings.Default.conDEFAULT;
PrepareForm();
t.Abort();
Is this the best way?
No, this doesn't solve the frozen UI problem, it merely papers it over. Imperfectly at that, the dialog's parent is the desktop and can easily appear underneath the frozen window and thus not be visible at all. And the dialog isn't modal to the regular windows since it runs on another thread. The failure mode when the user starts clicking on the non-responsive window is very ugly, those clicks all get dispatched when the UI thread comes back alive.
There are workarounds for that (you'd have to pinvoke SetParent and disable the main windows yourself) but that's just solving the wrong problem. You should never let the UI thread block for more than a second. Use a BackgroundWorker to do the heavy lifting, update the form with the query results in the RunWorkerCompleted event handler.
If you are using WinForms you may want to take a look at this A Pretty Good Splash Screen in C#. If you are using WPF you could take a look at this Implement Splash Screen with WPF.

WPF Main application stops responding if a child window is being moved around

We have a application written in C#, using WPF.
It has a timer based activation event which leads to some drawing in a DirectX context.
All seems well, until we open child window, and move it around the screen. The timing seems to coincide with the timer getting fired, but at that moment, the entire screen (even other applications) seem to freeze, and the user is unable to click anywhere.
The normal operation resumes from the exact same point where it froze if one presses ALT+TAB key combination. During the frozen state, there is no rise in CPU/memory utilization which leads me to suspect some kind of blocking on the main thread.
Normally, if my application hangs in the middle of some operation, I'd go, press pause from Visual Studio, and see the thread view in the debugger. This gives me sufficient idea on which call is the culprit.
But in this case, if I press ALT+TAB to switch to IDE, my application resumes it's normal execution. If I place my IDE on secondary screen and try to click (without the need to press ALT+TAB), it appears to be frozen as well (As I previously mentioned, entire desktop seems to be frozen to mouse clicks. Mouse movement however is normal)
Any one faced/aware of a similar problem, and on how can I go on debugging it ?
Try using a BackgroundWorker process to run your timer in the background. I was having the exact same issue, and I used the BackgroundWorker and it fixed my issue. Here is some sample code to get you started:
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.RunWorkerAsync();
bw.Dispose();
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
//Do Stuff Here
return;
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//Do an action when complete
}
Just wrap that in your timers tick event and everything should function as needed.
The problem is your using a single thread. You need to offload the work to another thread. ie: ThreadPool.QueueUserWorkItem or use the newer Task model, or the Dispatcher.Invoke ...
Just make sure you marshal any UI back to the UI thread when your done or it will GPF on you.

Categories