views:

497

answers:

3

We've got a Model-View-Presenter setup with our .NET Compact Framework app. A standard CF Form is implementing the view interface, and passed into the constructor of the presenter. the presenter tells the form to show itself by calling view.Run(); the view then does a Show() on itself, and the presenter takes over again, loading up data and populating it into the view.

problem is that the view does not finishing showing itself before control is returned to the presenter. since the presenter code blocks for a few seconds while loading data, the effect is that the form is not visible for a few seconds. it's not until after the presenter finishes it's data load that the form becomes visible, even though the form called .Show on itself before the presenter started it's data loading.

in a stanard windows environment, i could use the .Shown event on the form... but compact framework doesn't have this, and I can't find an equivalent.

does anyone know of an even, a pinvoke, or some other way to get my form to be fully visible before kicking off some code on the presenter? at this point, i'd be ok with the form calling to the presenter and telling the presenter to start it's data load.

FYI - we're trying to avoid multi-threading, to cut down on complexity and resource usage.

+1  A: 

The general rule is: never do anything blocking on the UI thread

The UI in Windows (and in Windows CE as well) has an asynchronous nature. Which means that most API calls do not necessarily do whatever they're supposed to do immediately. Instead, they generate a series of events, which are put into the event queue and later retrieved by the event pump, which is, essentially, an infinite loop running on the UI thread, picking events from the queue one by one, and handling them.

From the above, a conclusion can be drawn that if you continue to do something lengthy on the UI thread after requesting a certain action (i.e. showing the window in your case), the event pump cannot proceed with picking events (because you haven't returned control to it), and therefore, your requested action cannot complete.

The general approach is as follows: if you must do complex data transformation/loading/preparing/whatever, do it on a separate thread, and then use Control.BeginInvoke to inject a delegate into the UI thread, and touch the actual UI controls from inside that delegate.

Despite your irrational fear of "complexity" that multithreading brings with it, there is very little to be afraid of. Here's a little example to illustrate the point:

public void ShowUI()
{
    theForm = new MyForm();
    theForm.Show();

    // BeginInvoke() will take a new thread from the thread pool 
    // and invoke our delegate on that thread
    new Action( PrepareData ).BeginInvoke(null,null);
}

public void PrepareData()
{
    // Prepare your data, do complex computation, etc.

    // Control.BeginInvoke will put our delegate on the UI event queue
    // to be retrieved and executed on the UI thread
    theForm.BeginInvoke( new Action( PutDataInTheForm ) );
}

public void PutDataInTheForm()
{
    theForm.textBox1.Text = "data is ready!";
}

While you may play with alternative solutions, the general idea always remains the same: if you do anything lengthy on the UI thread, your UI will "freeze". It will not even redraw itself as you add new UI elements on the screen, because redrawing is also an asynchronous process.

Therefore, you must do all the complex and long stuff on a separate thread, and only do simple, small, guaranteed to run fast things on the UI thread. There is no other alternative, really.

Hope this helps.

Fyodor Soikin
I was optimistic about this being the solution! but BeginInvoke throws a NotSupportedException ".NET Compact Framework does not support invoking delegates asynchronously." :(
Derick Bailey
.NET Compact Framework does not support BeginInvoke -- however, you can use ThreadPool.QueueUserWorkItem to perform basically the same thing. If you want to make it look even nicer, you wrap the thread pool access into an extension method: public static IAsyncResult BeginInvoke<T>(this Func<T> function);
Travis Gockel
+1  A: 

If your key problem is that the form won't paint before your presenter data loading methods are completed, and you have a call to this.Show() in your Form_Load, try putting Application.DoEvents() directly after this.Show() to force/allow the form to paint.

protected void Form_Load(blah blah blah)
{
   this.Show();
   Application.DoEvents();

   ... data loading methods ...
}
Mark
as rediculous as this is, the Application.DoEvents() static method solves the problem without having to use any additional threads. I know i'm not "doing the right thing" and can cause UI freezing, etc. but this is sufficient for what we need.
Derick Bailey
A: 

No need to create another thread if you don't want to (although a couple of seconds have to be dealt with somehow). You can use the activated event. Because it will fire when the form is activated, you need a boolean local to the form to check wether or not the form has been created for the first time. Another option for you is to disconnect the event handler right after you finish presenting the form.