Preface: I know this is an unusual/improper way to do this. I can do this with a “real” ShowDialog(), background worker/thread, and so on. I’m not looking for help doing it that way; I am trying to do specifically what I describe here, even if it is ugly. If this is impossible for X reason, please let me know though.
I have created a fancy progress dialog for some of our long running operations. I need to have this dialog shown on a new thread while having processing continue on the calling (UI in most cases) thread.
This has 3 real requirements:
- Prevent user interaction with the calling form (similar to ShowDialog(this))
- Keep the progress dialog above the main window (it can fall behind now)
- Allow the main thread to continue processing
What I have looks like this (and works just fine so far, as far as running goes, except for those issues above):
Using ... ShowNewProgressDialogOnNewThread() ...
Logic
UpdateProgress() //static
Logic
UpdateProgress() //static, uses Invoke() to call dialog
...
End Using // destroys the form, etc
I have tried a few ways to do this:
- ShowDialog() on BackgroundWorker / Thread
- Action.BeginInvoke() which calls a function
- ProgressForm.BeginInvoke(… method that calls ShowDialog… )
- Wrapping main form in a class that implements IWin32Window so it can be called cross-threaded and passed to ShowDialog() – this one failed somewhere later one, but at least causes ShowDialog() to not barf immediately.
Any clues or wisdom on how to make this work?
Solution (For Now)
- The call to EnableWindow is what did what I was looking for.
- I do not experience any crashes at all
- Changed to use ManualResetEvent
- I set TopMost, because I couldn’t always guarantee the form would end up on top otherwise. Perhaps there is a better way.
- My progress form is like a splash screen (no sizing, no toolbar, etc), perhaps that accounts for the lack of crashes (mentioned in answer)
- Here is another thread on the EnableWindow topic (didn’t reference for this fix, tho)
Getting the progress window consistently displayed on top of the (dead) form is the difficult requirement. This is normally handled by using the Form.Show(owner) overload. It causes trouble in your case, WF isn’t going to appreciate the owner form belonging to another thread. That can be worked around by P/Invoking SetWindowLong() to set the owner.
But now a new problem emerges, the progress window goes belly-up as soon as it tries to send a message to its owner. Somewhat surprisingly, this problem kinda disappears when you use Invoke() instead of BeginInvoke() to update progress. Kinda, you can still trip the problem by moving the mouse over the border of the disabled owner. Realistically, you’ll have to use TopMost to nail down the Z-order. More realistically, Windows just doesn’t support what you are trying to do. You know the real fix, it is at the top of your question.
Here’s some code to experiment with. It assumes you progress form is called dlgProgress:
Sample usage: