Migrating a great big hwnd (and comparing WPF to GDI+)

A couple people have asked me, "my application has one great big hwnd, so I can't use HwndSource/HwndHost -- is there another way to incrementally move to WPF?"

Another option is to port your great big hwnd to WPF rendering -- a line for line port where you go through your rendering code and find the nearest WPF equivalent. This is a halfway step to full WPF -- you're only using a subset of WPF APIs (because only have one great big element), so you don't get all the functionality of WPF, but you get WPF rendering and in particular the WPF airspace. This approach is definitely not for everyone, but if you have a really complicated hwnd, it's worth considering.

Okay, what functionality doing don't you get? Basically, you're using WPF as a better GDI, one with animations, hardware accelerated gradients, resolution independence, antialiased rendering, richer text support, and more imaging formats and features. But you don't get the "framework" features of WPF, which include xaml, styling and templating, localization support, accessibility support, data binding, and flow documents. Doesn't mean you can't create great stuff (including localized and accessible), but does mean you have to do more of the work, just like Win32.

What's involved in such a port? It's broadly similar to porting a Win32 control to Windows Forms and GDI+ (only without some of the GDI+ gotchas). You need to convert all your input logic to WPF -- which for one great big control, is about the same functionality just different names and syntaxes, i.e., WM_LBUTTONDOWN message becomes a MouseLeftButtonDown event. Similarly, for rendering, WM_PAINT becomes OnRender().

There are some significant changes in rendering between GDI and WPF. The most obvious is that raster ops are not supported in WPF (does terrible things to performance with modern video cards). Less obvious is that the same text with the same font at the same point size won't render the same size in WPF as in GDI -- WPF uses a resolution-independent algorithm for rasterizing and positioning glyphs, so it won't get exactly the same line length as GDI. And in general, you should expect to spend a certain amount of time tracking down various small differences in how things are rendered, particularly off by one issues and unwanted aliasing effects.

Another issue that causes a bit of code restructuring is that GDI has the notion of current pen and current brush, whereas WPF as you pass in the appropriate pen/brush with every drawing call. As you port, you may need to spend a bit of time trying to figure out where the current pen/brush came from.

A lot of Win32 painting code deals with minimizing the region to repaint, it's usually best to just throw this logic out. WPF doesn't have the same APIs around during region management, and since WPF is retained-mode and double buffered (at the video cards level), you rarely need them.

Back when I was on the Windows Forms team, I spent a good deal of time moving code from GDI to GDI+, which as I said has a lot of the same considerations as GDI to WPF. Ballpark estimates, I figure a substantial control like PropertyGrid is a few days effort to port the rendering (the initial cut is a bit faster than that, but there's always a bug tail).

That said, if you've had a bad experience with GDI+, not to worry -- we've learned a lot from GDI+. There's two big problem areas with GDI+ -- formatted text and performance -- and we've addressed both in WPF. By formatted text, I mean having a paragraph of text that has multiple fonts/sizes/styles -- GDI+ didn't provide this functionality natively, nor does it provide enough low-level hooks for you to build such functionality on top. WPF provides both the low-level hooks and the pre-canned functionality.

Similarly, GDI+ had a couple big performance issues -- there was no hardware acceleration, it tended to be slow over terminal server, and tends to generate very large spool files on printers. All of this came from the same underlying problem -- the video cards/TS/printer didn't understand certain rendering primitives like gradients, so GDI+ gave them one pixel at a time. WPF addresses all this -- hardware acceleration on video cards, updating terminal server's RDP to understand gradients, and the XPS format for printer spools.

So there you have it, porting a great big hwnd to a great big WPF element. It's definitely not for everyone, and it's not something to enter into lightly without thinking through the costs and benefits, but sometimes its a great way to get WPF functionality without the cost of a full rewrite.