Tuesday, August 21, 2007

How to DoEvents in WPF?

From VB 5 (even 4) most advanced developers know little nice void method DoEvents. What is it? This is the great way to perform non-blocking wait. The method releases Windows messages pump, other words, performs execution loop. Why this good? Let's see. Following code (C# 1.1) just hangs until the loop will reach it's final value.

for(int i=0;i<1000;i++)
{
label1.Text = i.ToString();
}

How to force it to show values?

for(int i=0;i<1000;i++)
{
label1.Text = i.ToString();
DoEvents();
}

If you want to test this code and see something, put Thread.Sleep(1000); after label1.text... :) Just in case

But in WPF we have no DoEvents() method in application class? What to do? Well, we know, what Dispatcher is. We also know, that it use DispatcherFrame to pump messages, so, why not create our own DoEvents?

void DoEvents(){
DispatcherFrame f = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, 
(SendOrPostCallback)delegate(object arg) {
    DispatcherFrame fr =  arg as DispatcherFrame;
    fr.Continue=True;
}, f);
Dispatcher.PushFrame(frame);
}

Now, by using this method, we'll release message pump and make our long asynchronous methods not block dispatcher thread, but still wait for the end of execution. Here the example how to do it.

DispatcherOperation op = Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
(DispatcherOperationCallback)delegate {
    //DoSomethingReallyLong
    int res = 1;
    int pre = -1;
    for(int i=0;i<1000;++i) {
     int sum = res + pre;
    pre = res;
    res = sum;
     }
    return res;
},null);

while(op != DispatcherOperationStatus.Completed) {
   DoEvents();
}

Console.WriteLine(op.Result);

That's all, folks.

5 comments:

Marián said...

Another solution of this problem is described on http://nmarian.blogspot.com/2007/09/doevents-in-wpf.html

Kieryn said...

The doevents method should actually be like this:

(*note - the callback should set continue to false!)

static void DoEvents()
{
DispatcherFrame frame = new DispatcherFrame(true);
Dispatcher.CurrentDispatcher.BeginInvoke
(
DispatcherPriority.Background,
(SendOrPostCallback) delegate(object arg)
{
var f = arg as DispatcherFrame;
f.Continue = false;
},
frame
);
Dispatcher.PushFrame(frame);
}

Scaramanga said...

This works just as well btw

System.Windows.Forms.Application.DoEvents()

Just add a reference to System.Windows.Forms

Aju Peter said...

Brilliant! TAMIR!!

Though there are few corrections required, it is a wonderful piece of code. It worked after few corrections. Amazing!! Thanks.

Developer said...

This's my solution.

Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new System.Threading.ThreadStart(()=>System.Threading.Thread.Sleep(1)));