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 RenderTargetBitmap
to grab a frame.
The code sits inside a handler for the ContentRendered
event of the window and is initialised like this:
1: public bool CreateThumbnail(string sourcePath, string targetPath)
2: {
3:
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:
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 signaled, 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.