Derek Lakin

Application.DoEvents in WPF

Recently I’ve been trying to make some thumbnail generation code synchronous. I’m trying to grab the first frame from a Shockwave Flash (SWF) or Flash Video (FLV) file and save it as a thumbnail, but in order to do that I needed to create a Window to host an ActiveX control that renders the Flash file so that I can then use a RenderTargetBitmap to grab a frame.

The code sits inside a handler for the ContentRendered event of the Window and is initialised something like this:

1: publicbool CreateThumbnail(string sourcePath, string targetPath)

2: {

3: // NOTE: Loads of code missed for brevity.

4: ManualResetEvent waitHandle = new ManualResetEvent(false);

5: bool generated = false;

6:

7: var win = new Window()

8: {

9: AllowsTransparency = true,

10: Opacity = 0.0,

11: Left = 0,

12: Top = 0,

13: SizeToContent = SizeToContent.WidthAndHeight,

14: ShowInTaskbar = false,

15: WindowStyle = WindowStyle.None

16: };

17: win.ContentRendered += (sender, e) =>

18: {

19: // Use RenderTargetBitmap to get the frame and save it.

20: generated = true;

21: waitHandle.Set();

22: }

23:

24: win.Show();

25: waitHandle.WaitOne(5000);

26: return generated;

27: }

The problem is that the ContentRendered event doesn’t fire until after the WaitOne call on the ManualResetEvent, effectively meaning that thumbnail generation fails all the time. Basically, the UI thread is blocked waiting for the wait handle to be signalled, so it doesn’t get round to rendering the Window until after this method has called.

For VB programmers out there, you may be thinking that you simply call Application.DoEvents() to free up the message loop and unfortunately WPF doesn’t provide the same API. However, there is a way to do this in WPF. By pushing a nested message loop we can cause this nested message loop to be processed immediately, allowing the Window’s content to be rendered and our thumbnail to be generated. I wouldn’t recommend using nested message loops and neither would many other people, but in this case it works for me. All I needed to do was to add the following line of code between the win.Show() call and the waitHandle.WaitOne(5000) call above:

Application.Current.Dispatcher.Invoke( DispatcherPriority.Background, new ThreadStart(delegate { }));

Thanks to Zhou Young’s post for the simplest implementation of DoEvents in WPF that I’ve seen. As you can see, he’s put it in his Application class, but personally I think that’s just encouraging you to use this approach and in most cases I think there’s a better solution for you.