Introduction
Back in the days when I used FoxPro, we would always pop up a wait window while running any long process. Come .NET, there is no built in wait window, so you have to roll your own. This has become an increasing issue as we issue calls to web services that may be fast or slow. In the mean time, our users need to know the application has not crashed. They should also not be able to click on any other part of the application until the process has finished, in the same manner as the old FoxPro wait window.
Background
There are a few details that need to be covered for a good wait window.
During our process, we want to provide an animation on the wait window to ensure the user believes the application has not crashed. For the purposes of this article, I have used a ProgessBar
, but you could equally use an animated GIF, or some custom animation scheme of your own devising. The side effect is that the worker method must execute on a secondary thread or it will block the GUI updating the animation.
Also, we cannot use a BackgroundWorker
for this as we want the execution to occur inline. For example, we want to call the wait window specifying the method to execute, then expect it to have finished by the time we hit the next line of code. Therefore we will have to roll our own threading code.
Using the Code
I have built the sample code with the wait window in a separate assembly so you can just reference the assembly if you want. However, I expect most people will copy the classes into their own project structure, which is fine.
Before anyone asks, the sample code includes the project in C# and in VB.NET, and while I have compiled it against the 3.5 framework, it should be fine built against any .NET Framework version, though you would have to manually copy the code into a Visual Studio 2005 project if you wanted to use the older solution format.
First, you will need to write your method to execute. Remember this is running on a separate thread, so pass everything in and return the results, or ensure any calls you make are guaranteed thread safe!
To make things easier, the event args for the method include a collection of arguments that you can pass in, and a reference to the WaitWindow
object so that the message displayed can be updated during the process. The code below shows most of the things you may want to do. For more examples, see the demo project.
private void WorkerMethod(object sender, Jacksonsoft.WaitWindowEventArgs e){
for (int progress = 1; progress <= 100; progress++){
System.Threading.Thread.Sleep(20);
e.Window.Message = string.Format
("Please wait ... {0}%", progress.ToString().PadLeft(3));
}
if (e.Arguments.Count> 0){
e.Result = e.Arguments[0].ToString();
} else {
e.Result = "Hello World";
}
}
Next, you need to call the WaitWindow
with the method you have created. The example below just uses the default, "Please wait..." message, but you can also pass in the message to display, and any arguments for the worker method. Again, there are more examples in the demo project.
object result = WaitWindow.Show(this.WorkerMethod);
There are two methods exposed by the WaitWindow
object that can be called from within the worker method. These are the methods, Cancel()
and the property, Message
. These cancel the execution immediately, and update the message displayed in the WaitWindow
respectively.
Points of Interest
In the first release, I did some clever stuff to make a modeless window appear modal, however it was pointed out that I did not actually need this at all.
If you do want to do this for some other reason, you have to capture the mouse and then capture all the windows messages and ensure that only the ones you want get passed on to the rest of the application.
We pass the Form
into the AddMessageFilter
method to hook up the form to receive all the windows messages first.
System.Windows.Forms.Application.AddMessageFilter(this._GUI);
Inside the code for the window, we can now implement the IMessageFilter
interface giving us a PreFilterMessage
method. All that remains is to return true
for the mouse and keyboard events, rather than the default false
and the window will now act as if it were modeless when in fact it is modal.
A shame that I ditched that rather neat piece of code because it was a pointless waste of time, but that's development for you, a constant learning process.
History
- First release - March 2010
- Release 2 - Updated for simpler threading model (Many thanks to John Brett for his input)
- Release 3 - Updated to support cancellation and better exception handling