This project brings that cool AJAX-style asynchronous indication behaviour to WinForms.
Introduction
My company develops a lot of rich-client applications, and we have forever wanted a nice "slick" way to indicate to the user when an activity is occurring in the background. Rarely do we know how long an operation will take (web services, remoting calls, etc.), so we always used to just stick a little barber pole type animation in the top-right corner of the main application window. This was never a perfect solution though because we still had to do all the nasty "locking" of the Control
/Form
to make sure they couldn't queue up another action. Moreover, setting Enabled = False
on many WinForms controls can look quite ugly and inconsistent, especially if the Form
has got a variety of different controls.
In recent years, AJAX on the web has actually pioneered some interesting GUI concepts. I've always liked it when web sites pop-up with a central window that turns the background slightly darker and then ask you for some input. Then you type that input in and press an OK button, and then you get a nice little barber pole animation to indicate it has gone off back to the server and is waiting for a reply for the next step (if any).
That's basically what this project is about. Bringing that "cool" AJAX-style asynchronous indication behaviour to WinForms.
Background
The project consists of several fundamental concepts:
- Capturing/snap-shooting the current appearance of a
Form
in a reliable and consistent way.
Note: Control.DrawToBitmap()
was not used because it has weird behaviour with some controls like RichTextBox
. - Manipulating the captured bitmap to either blur or grayscale it in some way, in a similar way that most AJAX web sites do.
- A barber pole type animation in the center of the
Form
. In this case, I used the excellent "Loading Circle" control by Martin Gagne - so thank you Martin for that :~) - From the outset, I ensured that whatever I developed would work on both normal
Form
s and MDI child's. This was crucial to me because many of our products use MDI user interfaces. Secondly, this ruled out the possibility of using Win2000-onwards composited layered translucent windows (which I experimented with initially).
Using the Code
To use the base class, simply modify your Form
to derive from my AsyncBaseDialog
instead of the default System.Windows.Forms.Form
. You then just call RunAsyncOperation()
and pass in your delegate method as its parameter. This method handles all the nitty-gritty work of scheduling your work on a background thread.
Alternatively, if you want better control over things, then you can use BeginAsyncIndication()
and EndAsyncIndication()
.
Internally, Begin/EndAsyncIndication()
use a reference count so that you can call them multiple times in a stack-like fashion and still get the expected behaviour.
public partial class MyForm : AsyncBaseDialog {
public ModalDlg() {
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e) {
AsyncProcessDelegate d = delegate() {
System.Threading.Thread.Sleep(3000);
};
RunAsyncOperation(d);
}
private void button2_Click(object sender, EventArgs e) {
BeginAsyncIndication();
}
}
How It Works
The Form
is snapshot by opening up its DC (device context) and then copying its contents to a Bitmap
. This bitmap is then manipulated using Martin Gagne's methods to grayscale it.
The snapshot of the Form
was probably the hardest bit as I haven't done Win32 API for years! By the way, if you are having teething issues with Control.DrawToBitmap()
, then I recommend you look at this. Here it is:
IntPtr srcDc = GetDC(this.Handle);
Bitmap bmp = new Bitmap(this.ClientRectangle.Width, this.ClientRectangle.Height);
using (Graphics g = Graphics.FromImage(bmp)) {
IntPtr bmpDc = g.GetHdc();
BitBlt(bmpDc, 0, 0, bmp.Width, bmp.Height, srcDc, 0, 0, 0x00CC0020 );
ReleaseDC(this.Handle, srcDc);
g.ReleaseHdc(bmpDc);
Grayscale(bmp);
g.FillRectangle(fillBrush, 0, 0, bmp.Width, bmp.Height);
}
There were a couple issues I had surrounding the user resizing, maximising, minimising, restoring, or double-clicking the title bar of the Form
whilst the async. indication was active. Basically, these were redraw issues - particularly on pre-Vista Aero Glass machines. After weighing up possible solutions, I decided that the chances of a user wanting to resize/min/maximise the Form
whilst the async. indication was active was pretty small and the annoyance to them would probably be very small. Therefore, I wrote some WndProc
filters, as below:
protected override void WndProc(ref Message m) {
if (IsAsyncBusy) {
if (m.Msg == 0x112 ) {
int w = m.WParam.ToInt32();
if (w == 0xf120 || w == 0xf030
|| w == 0xf020
)
return;
} else if (m.Msg == 0xa3 )
return;
}
base.WndProc(ref m);
}
Thank you
Thanks for reading and I hope you like the control.
If you make any modifications/bug fixes/enhancements to this control, please post in the comments section with your source snippets and/or ideas.
History
- 2nd March, 2008 - Initial release