Click here to Skip to main content
Click here to Skip to main content

AJAX-style Asynchronous Progress Dialog for WinForms

By , 2 Mar 2008
 

asyncdialog-src

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 use 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 Forms 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() {
         //
         // Do your long-duration work here
         // and remove the placeholder Sleep() below
         //
         System.Threading.Thread.Sleep(3000);
      };

      RunAsyncOperation(d);
   }

   private void button2_Click(object sender, EventArgs e) {
      //
      // Alternatively if you don't want to use the RunAsyncOperation() wrapper...
      // You can use BeginAsyncIndication() and EndAsyncIndication() explicitly.
      //
      BeginAsyncIndication();
   }

}//class

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 have having teething issues with Control.DrawToBitmap(), then I recommend you look at this. Here it is:

//
// Get DC of the form...
IntPtr srcDc = GetDC(this.Handle);

//
// Create bitmap to store image of form...
Bitmap bmp = new Bitmap(this.ClientRectangle.Width, this.ClientRectangle.Height);

//
// Create a GDI+ context from the created bitmap...
using (Graphics g = Graphics.FromImage(bmp)) {
   //
   // Copy image of form into bitmap...
   IntPtr bmpDc = g.GetHdc();
   BitBlt(bmpDc, 0, 0, bmp.Width, bmp.Height, srcDc, 0, 0, 0x00CC0020 /* SRCCOPY */);

   //
   // Release resources...
   ReleaseDC(this.Handle, srcDc);
   g.ReleaseHdc(bmpDc);

   //
   // Blur/grayscale it...
   Grayscale(bmp);

   //
   // Apply translucent overlay... fillBrush has an alpha-channel.
   g.FillRectangle(fillBrush, 0, 0, bmp.Width, bmp.Height);
}//using

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 /* WM_SYSCOMMAND */) {
         int w = m.WParam.ToInt32();

         if (w == 0xf120 /* SC_RESTORE */ || w == 0xf030 
                         /* SC_MAXIMIZE */ || w == 0xf020 
                         /* SC_MINIMIZE */)
            return; // short circuit

      } else if (m.Msg == 0xa3 /* WM_NCLBUTTONDBLCLK */)
         return; // short circuit
   }

   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

  • 02/March/2008 - initial release.

License

This article, along with any associated source code and files, is licensed under The Mozilla Public License 1.1 (MPL 1.1)

About the Author

Nathan Evans
Software Developer (Senior)
United Kingdom United Kingdom
Member
I am the lead developer of numerous .NET-based networking and communication server systems for Windows, for a company based in Cambridge. Including SMS/SMPP, VOIP and VoiceXML technologies.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
BugI found bug on ur project!memberYashar VC++18 Feb '13 - 20:45 
When animation is in run and close the window,the bug comes up
(Excuse me for bad english Unsure | :~ )
GeneralMy vote of 4memberIan Gallagher10 Jun '11 - 6:53 
This worked great and helped me save hours of painstaking coding. Thank you Nathan! I do have a few comments on some humps I got over when implementing into my project.
GeneralGreat Sample!memberJason Turan15 Oct '10 - 13:55 
Thanks for posting this solution. I was playing with Flash and Picturebox controls but wasn't able to get anything as clean looking as this. I was able to get it to work on my form in only a couple of ours so thanks for saving me a lot of time.
Generalfound problem working with this in C# 2.0 versionmemberTridip Bhattacharjee20 May '10 - 1:38 
in c# 2.0 it is giving a error called WndProc no suitable method found to override. can u please tell me how to fix it.
tbhattacharjee

QuestionNice work but one problem found??memberTridip Bhattacharjee19 May '10 - 23:35 
when busy animation is showing and if i close the form then a error occur and the error thrown from this method
 
protected void RunAsyncOperation(AsyncProcessDelegate callback)
{
WaitCallback d = delegate(object not_used)
{
try
{
Invoke(new MethodInvoker(delegate()
{
Update();
BeginAsyncIndication();
}));
callback.Invoke();
 
}
finally
{
this.Invoke(new SafeWinFormsThreadDelegate(EndAsyncIndication));
}
};
 
ThreadPool.QueueUserWorkItem(d);
}
 
basically this line generate error "this.Invoke(new SafeWinFormsThreadDelegate(EndAsyncIndication));"
 
please fix it if possible.
 
one very important thing is missing that is custom text like "loading....".
tbhattacharjee

GeneralBeen looking for something like this for a while...memberGareth Parris6 May '10 - 8:16 
Great stuff.
 
Is it possible to overlay a control on a form instead of the whole form? I'm thinking specifically about a grid control. I'd like your popup to appear over just the grid part of the form instead of the entire form.
GeneralCross Thread operationmemberkasi1244 Jan '10 - 6:48 
Hello,
 
I get a "Cross Thread operation not allowed" exception. Anybody else experience it? Any fixes?
 
Thanks
GeneralRe: Cross Thread operationmemberzosopat24 Jun '10 - 21:45 
I'm experiencing the same when attempting to manipulate a control on the base form from within the AsyncProcessDelegate code. Any help would be greatly appreciated.
plus ça change, plus c'est la même chose

GeneralEvents not calledmemberHolms24 Jul '09 - 20:31 
hi
 
great job, however for some reason, when I derive a Form from AsyncBaseDialog then my form does not raise any events such as OnLoad, OnShown, etc..
 
Can you check on this please?
 
Thx
GeneralRe: Events not calledmemberMember 40207131 Dec '09 - 23:21 
I faced the same issue and solved it by adding base.OnLoad(e); in the AsyncBaseDialog::OnLoad code.
GeneralRe: Events not calledmemberMember 84631925 Dec '11 - 22:12 
I'm having the same problem. Tried your solution, didn't work.
Not of my form events such as the form load are fired.
I love the author's solution but I can't use it because of this and it's a shame.
 
I somehow was able to call the form load function from the initialize components function.
It fills in the lists on the form but when it comes to re-positioning the controls, it doesn't work.
I guess it's because they weren't "painted" yet.
Generalapply to user controlmemberaldo hexosa14 Apr '09 - 17:19 
how to apply AsyncBaseDialog class to user control?
Thank you
GeneralSmall change in RunAsyncOperation methodmemberCozyRoc8 Apr '09 - 16:57 
I would like to suggest to change RunAsyncOperation method to this:
 
        protected void RunAsyncOperation(AsyncProcessDelegate callback) {
            WaitCallback d = delegate(object not_used) {
                try {
					Invoke( new MethodInvoker( delegate () {
						Update();
						BeginAsyncIndication();
					} ) );
					callback.Invoke();
 
                } finally {
                    this.Invoke(new SafeWinFormsThreadDelegate(EndAsyncIndication));
                }
            };
 
            ThreadPool.QueueUserWorkItem(d);
        }
 
This will make possible to use the progress dialog in Form's Load event handler. The original code is not able to grab proper screen shot of the dialog.
 
Regards,
Ivan

GeneralRe: Small change in RunAsyncOperation methodmemberMember 38053787 Apr '11 - 8:16 
Awesome! Thanks CozyRoc Big Grin | :-D
Questionhow can avoid unsafe code ???memberalhambra-eidos23 Feb '09 - 21:12 
If I want not use unsafe compilation,...any suggestions ??
 
Thanks misters.
 
AE

AnswerRe: how can avoid unsafe code ???memberwebxet7 Oct '11 - 5:29 
-goto project properties
-build tab
-check allow unsafe code
 
or call program from command line with parameter: /unsafe
Questionany updates ?memberalhambra-eidos23 Feb '09 - 3:23 
thanks !! great job !!
 
AE

QuestionCan you place a cancel button ?memberPankajkumar Nikam19 Jan '09 - 2:50 
Suppose the user wants to cancel the current process, can you put a cancel button above the animation so that when he clicks cancel the animation must go away and the process should stop ?
 
Always Keep Smiling.
Yours Pankaj Nikam

GeneralIt's very very good, but it can not cover the toolstripmemberjavasleepless26 Aug '08 - 18:12 
I am trying to solve this problem.
GeneralRe: It's very very good, but it can not cover the toolstripmemberjavasleepless26 Aug '08 - 18:25 
Martin456 sovled this issue. Thanks a lot.
 
Form with Dock = DockStyle.Fill member Martin456 9:03 28 May '08

Hi, nice work,
but I have small problem . I have some control on my Form with Dock = DockStyle.Fill and AsyncPanel was painted only over this control instead over whole Form. I found this solution:
replace line "asyncPanel.Dock = DockStyle.Fill"
with
"asyncPanel.SetBounds( 0, 0, this.ClientRectangle.Width, this.ClientRectangle.Height );
asyncPanel.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom;
"
General[Message Removed]membernompel20 Sep '08 - 14:54 
Spam message removed
QuestionVS 2005?memberC#GIS9 Jul '08 - 11:50 
This looks awesome! I only have VS 2005 and it will not open your source code. Do you have a 2005 version of the code?
 
Thanks!
AnswerRe: VS 2005?membersonny_z26 Aug '08 - 10:36 
Just open the .csproj file
GeneralOne rectification is required for this controlmemberTridip Bhattacharjee15 Jun '08 - 21:35 
when it is showing a busy image then there should be a provision for displaying a message like loading... which user will provide. please incorporate this user driven message utility in it......so far it is very nice but very resource hungry.
 
tbhattacharjee

GeneralForm with Dock = DockStyle.FillmemberMartin45628 May '08 - 6:03 
Hi, nice work,
but I have small problem . I have some control on my Form with Dock = DockStyle.Fill and AsyncPanel was painted only over this control instead over whole Form. I found this solution:
replace line "asyncPanel.Dock = DockStyle.Fill"
with
"asyncPanel.SetBounds( 0, 0, this.ClientRectangle.Width, this.ClientRectangle.Height );
asyncPanel.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom;
"

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130516.1 | Last Updated 2 Mar 2008
Article Copyright 2008 by Nathan Evans
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid