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

Immediate display of WinForms using the Shown() event

, 12 Feb 2010 CPOL
Rate this:
Please Sign up or sign in to vote.
How to cause a Winform (and all its child controls) to fully render before you do additional processing.

Introduction

One of my pet peeves is forms that don't instantly appear. You see it all the time: the user clicks a button, then waits a few seconds until the expected UI appears. This is typically due to the newly-created form performing some time-consuming (blocking) task in its Load() event handler (or, in the non-.NET world, the WM_INITDIALOG message handler). Aside from being poor UI design (never leave the user wondering what's happening!), it can have some really undesirable consequences: a button-click doesn't have an immediate effect, users will tend to click the button again, which can result in the action being invoked twice.

Since my earliest Windows programming, I've always implemented "instant feedback" in my Forms (f/k/a "dialog boxes"). My technique has evolved as Windows API has evolved; I'll discuss my older techniques (which can still be used in .NET), and end up with my current implementation using .NET WinForms APIs.

The Goal

Create a simple, reliable mechanism for doing post-form-load processing which ensures that the form is "fully rendered" before the post-load processing commences. This means that all the controls in the form itself as well as all child controls (in the dialog template) have been drawn as the user would expect to see them.

Background

This is a pretty basic technique; novice WinForms coders should be able to implement it.

An understanding of the startup sequence of a Form (a.k.a. window, a.k.a. dialog) is useful: http://msdn.microsoft.com/en-us/library/86faxx0d%28VS.80%29.aspx.

Most of these events have direct Windows message (WM_*) analogs. However, .NET adds the System.Windows.Forms.Form.Shown event - which does not have a corresponding Windows message - and that event is the basis for a relatively clean way to do post-Load() processing. (MSDN docs on the Shown() event: http://msdn.microsoft.com/en-us/library/system.windows.forms.form.shown.aspx).

Complicating the issue is the asynchronous, not-totally-predictable nature of messages. When a window is created (and thus its children are created), the exact sequence of the various windows' messages varies from instance to instance. Certainly, each window's messages occur in the same sequence every time, but the sequence of the parent/child messages is unpredictable, creating a race condition. The most unfortunate result of this being that sometimes a dialog will render properly, sometimes it won't. Removing this uncertainty - ensuring predictability - is a big part of this solution.

Finally, it's very important to perform your blocking processing after the form and all children are fully rendered. Blocking the UI thread (and message queue) mid-render, results in some pretty partial ugly UI that looks like your app crashed!:

PartiallyRenderedForm.png

Notice how the form's frame and unclipped client area have rendered but certain child controls' client areas still show the UI "below" the form. Also notice how the listbox has rendered but the other controls (GroupBoxes, ComboBoxes) have not (I haven't investigated why that is).

Here's how it should look fully rendered:

FullyRenderedForm.png

Solution: Before .NET

(If you wrote Windows code before .NET - with or without MFC - this should look familiar.)

Here's a simplified sequence of the messages generated upon window creation:

WM_CREATE (window) or WM_INITDIALOG (dialogbox)
...
WM_SHOWWINDOW
...
WM_ACTIVATE (wParam has WA_* state)

In those days, I did something like this:

#define WMUSER_FIRSTACTIVE  (WM_USER+1)
bool m_bSeenFirstActivation = false;
virtual void DoPostLoadProcessing();

LRESULT WndProc(msg, wparam, lparam)
{
    switch(msg)
    {
        case WM_ACTIVATE:
            if((wparam==WA_ACTIVE) && !m_bSeenFirstActivation)
            {
                m_bSeenFirstActivation = true;
                PostMessage(m_hWnd, WMUSER_FIRSTACTIVE);
            }
            break;
        case WMUSER_FIRSTACTIVE:
            DoPostLoadProcessing(); // Derived classes override this
            break;
    }
}

The theory was that, by posting a message to myself after receiving the initial activation message, I could be confident that:

  1. The dialog had been fully rendered, and
  2. The post-load processing is performed asynchronously and on the dialog's UI thread:
    • Using PostMessage() ensures it's asynchronous. The posted message is added to the end of the queue and gets processed after all pending (UI-related) messages are processed.
    • Doing stuff in the dialog's UI thread is important - many controls get very upset if you try to manipulate them from a thread other than the UI thread.

And, sometimes I did this:

#define IDT_FIRSTACTIVE  0x100
bool m_bSeenFirstActivation = false;
virtual void DoPostLoadProcessing();

LRESULT WndProc(msg, wparam, lparam)
{
    switch(msg)
    {
        case WM_ACTIVATE:
            if((wparam==WA_ACTIVE) && !m_bSeenFirstActivation)
            {
                m_bSeenFirstActivation = true;
                SetTimer(m_hWnd, IDT_FIRSTACTIVE, 50, NULL);
                PostMessage(m_hWnd, WMUSER_FIRSTACTIVE);
            }
            break;
        case WM_TIMER:
            if(wParam == IDT_FIRSTACTIVE)
            {
                KillTimer(m_hWnd, IDT_FIRSTACTIVE);
                DoPostLoadProcessing(); // Derived classes override this
            }
            break;
    }
}

The theory here is essentially the same: Set a quick (50ms) timer in the initial activation. The WM_TIMER message occurs asynchronously and with some extra delay. The timer is immediately killed (we only wanted the first timer message), then the derived class' DoPostLoadProcessing() is called. (Yeah, I know that timers < 55ms are useless on a PC.)

Both techniques work pretty well, and could still be used in .NET, though they're messy.

Solution: Doing it in .NET

The basics

.NET's addition of the Shown() event simplifies things greatly. Now, in theory, all you'd need to do is handle the Shown() event and do your processing there:

void MyForm_Shown(object sender, EventArgs e)
{
    // Do blocking stuff here
}

Simple, right? Well, almost. It turns out that the Shown() event gets fired before all the form's child controls have fully rendered, so you still have the race condition. My solution to that is to call Application.DoEvents() to clear out the message queue before doing any additional post-processing:

void MyForm_Shown(object sender, EventArgs e)
{
    Application.DoEvents();
    // Do blocking stuff here
}

A more complete solution

The problem with the basic solution above is that you must remember to call Application.DoEvents() in each form's Shown() event handler. While this is admittedly a minor nuisance, I've chosen to take the solution one step further by implementing a base dialog class that handles the Shown() event, calls DoEvents(), then invokes an event of its own:

public class BaseForm : Form
{
    public delegate void LoadCompletedEventHandler();
    public event LoadCompletedEventHandler LoadCompleted;

    public BaseForm()
    {
        this.Shown += new EventHandler(BaseForm_Shown);
    }

    void BaseForm_Shown(object sender, EventArgs e)
    {
        Application.DoEvents();
        if (LoadCompleted != null)
            LoadCompleted();
    }
}

And, derived forms simply implement a LoadCompleted() handler:

this.LoadCompleted += new 
      FormLoadCompletedDemo.BaseForm.LoadCompletedEventHandler(
      this.MyFormUsingBase_LoadCompleted);
    
    ...

private void MyFormUsingBase_LoadCompleted()
{
    // Do blocking stuff here
}

and... bonus!: LoadCompleted() appears in Visual Studio's Properties/Events pane:

LoadCompletedInPropertiesEventsPane.png

(I named the event LoadCompleted so that it would appear immediately after the Load event in the events pane, making it easier to find.)

With that, you can do all your long-duration stuff in LoadCompleted() and be confident that the basic UI will be fully rendered while the user waits. Of course, you should still follow good UI practices such as showing a WaitCursor, maybe disabling the controls until they're usable (a useful visual cue to the user), and perhaps even showing a progressbar for really long waits (e.g.: show a marquee progressbar during a SQL call that takes 5+ seconds).

Demo project

The attached VS2008/C# project demonstrates the behavior and the solution. It implements a form with four buttons:

DemoMainForm.png

All four buttons do the same thing, which is pop up a child dialog containing:

  • A listbox that gets populated with 50K items. This population blocks the UI thread for a couple of seconds, which allows the app to demonstrate the solutions to the problem.
  • A few other controls, principally ComboBoxes. For whatever reason, ComboBoxes are particularly prone to incomplete rendering due to blocking code.

Each child form sets the WaitCursor in its Load() event as a visual feedback that things are happening.

However, each button causes a different form-loading behavior:

  • Do processing in Load event - the listbox is loaded in the form's Load() event handler, resulting in the form not appearing until the listbox is fully loaded (bad).
  • Open Form Without DoEvents - the listbox is loaded in the form's Shown() event handler, but Application.DoEvents() is not called, resulting in the form appearing partially rendered until listbox-load completes (very bad).
  • Open Form *with* DoEvents - the listbox is loaded in the form's Shown() event handler and Application.DoEvents() is called, resulting in the form appearing fully rendered until listbox-load completes (good!!!).
  • Open Form derived from BaseForm - Same as Open Form *with* DoEvents, but implemented using a base class and custom event.

Conclusion

Like so many things in Windows (and in life!), the solution to this problem is pretty simple, but figuring it out takes a bit of trial and error. I hope that my solution outlined above saves you some time and helps you create more responsive UIs.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

DLChambers
Software Developer
United States United States
I can type "while" and "for" very quickly

Comments and Discussions

 
SuggestionWithout LoadCompleted [modified] Pinmemberkassiop12-Feb-13 2:09 
QuestionHow to 'inactivate' the Baseform? Pinmemberdherrmann30-Oct-12 7:00 
AnswerRe: How to 'inactivate' the Baseform? PinmemberDLChambers30-Oct-12 7:03 
GeneralRe: How to 'inactivate' the Baseform? Pinmemberdherrmann30-Oct-12 7:12 
GeneralMy vote of 5 PinmemberWühlmaus10-Sep-12 23:24 
GeneralMy vote of 5 Pinmemberjohnsathish28-May-12 18:58 
GeneralOnShown trouble PinmemberGonzalo Cao15-Jun-10 22:44 
GeneralGood article! Pinmemberdevendrad_debu3-May-10 7:09 
GeneralAn odd behavior (not linked to the article though) Pinmemberqyte6414-Mar-10 2:12 
AnswerRe: An odd behavior (not linked to the article though) PinmemberDLChambers14-Mar-10 7:12 
GeneralWell written and clear Pinmembergillindsay8-Mar-10 6:09 
GeneralThis works for me. Pinmembertonyt20-Feb-10 22:00 
GeneralRe: This works for me. PinmemberDLChambers21-Feb-10 0:13 
GeneralBackgroundWorker PinmemberBillB416-Feb-10 12:18 
AnswerRe: BackgroundWorker PinmemberDLChambers17-Feb-10 2:26 
GeneralRe: BackgroundWorker [modified] PinmemberBillB417-Feb-10 4:43 
QuestionHow about for UserControls? PinmemberBarryGilbert16-Feb-10 10:15 
AnswerRe: How about for UserControls? PinmemberDLChambers16-Feb-10 10:36 
GeneralRe: How about for UserControls? PinmemberBarryGilbert16-Feb-10 11:30 
QuestionDatabase filled combos and listbox Pinmemberabacrotto16-Feb-10 2:35 
AnswerRe: Database filled combos and listbox PinmemberDLChambers16-Feb-10 2:53 
GeneralThanks for writing this PinmemberEd Bouras16-Feb-10 1:58 
GeneralRe: Thanks for writing this PinmemberDLChambers16-Feb-10 3:20 
GeneralI LIke It PinmemberJohn Simmons / outlaw programmer15-Feb-10 23:03 
GeneralRe: I LIke It PinmemberDLChambers16-Feb-10 3:12 
GeneralImplementation Note PinmemberTom Spink15-Feb-10 22:16 
GeneralRe: Implementation Note Pinmemberc# beginner20-Feb-10 8:42 
Generalhrm... PinmemberDiamonddrake12-Feb-10 16:57 
GeneralRe: hrm... PinmemberDLChambers13-Feb-10 4:49 
GeneralRe: hrm... Pinmemberhground16-Feb-10 3:52 
QuestionHow about using Control.BeginInvoke in Form.Shown to enqueue the init routine from the load routine? Pinmembersupercat912-Feb-10 8:26 
AnswerRe: How about using Control.BeginInvoke in Form.Shown to enqueue the init routine from the load routine? PinmemberDLChambers12-Feb-10 8:43 
GeneralInteresting... PinmemberWilliam Winner12-Feb-10 8:21 
GeneralRe: Interesting... PinmemberDLChambers12-Feb-10 8:36 
GeneralCool!!Really simple and powerful!!! PinmemberZodraz12-Feb-10 5:24 
GeneralExcellent! Pinmemberrht34112-Feb-10 5:07 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.141022.2 | Last Updated 12 Feb 2010
Article Copyright 2010 by DLChambers
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid