WPF and handles

Print Friendly, PDF & Email

There’s a famous article from Microsoft that says “Give me an handle, and I”ll show you an object“, dated July 15, 1993.

There are a lot of Windows objects that can be retrieved using a handle, mainly GDI and USER objects but also Kernel objects are identified by handles – for example, the HeapCreate function, used to “create a private heap object that can be used by the calling process” returns a handle to the newly created heap that must be used by subsequent calls to HeapAlloc.

Of course, each native (or wrapper around native) window has it own handle: take spyxx, open the “Window Search” tool and play around with the finder Tool, you’ll get all the handles of the various windows.

Spyxx browsing window handles

That’s absolutely fine until you want to do something low level with a WPF application that hosts WinForms parts.

WPF: where are my handles?

WPF is a different beast; that’s not the place to go deep in the basics on how WPF builds the layout of the application but we know that it uses DirectX to render the UI. So, we won’t find anything but a single handle for a WPF Window in contrast of what we’ve seen for a WinForms Form in which each element (button, label, combo, …) had a handle.

The interop is not exactly straightforward if you need something more than the basic WindowsFormsHost or the ElementHost classes; we have an application which UI is quite flexible and can be freely composed starting from various type of  “building blocks” that we call “components”. These can be pure WPF components or “old” WinForms components or even mixed ones; each component can be kept floating in a window or can be docked through a custom Window Manager: it is important to be able to exactly identify which component has “the focus” in each moment.

To do this and to cope with the multiple technologies used, we have in place an application Mouse Hooker that helps us fix the (many) mouse iterop problem between WinForms and WPF; everything would be fine if we could be WPF-only: in that case we could clearly identify which component has been clicked and thus has to receive the focus, but things are complicated by WinForms stuff hosted in the components. Consider this use case:

This is a WPF Window hosting three component, each one with its caption; the Docking Manager is a fully WPF object, so if we click the caption we know what component should be focused (the orange one, the green one or the blue one). The same can be easily done if the content of a component is a WPF element but if a component is hosting a WinForms (or a Win32) “thing”, then the Docking Manager won’t be called and in that case we have to find an easy way to make the thing work in all the cases. We decided to go the Mouse hooker way, intercepting all the mouse down events and implementing a quite simple HitTest function.

Everything went smooth until we got a “popup widget” hosted outside the Docking Manager in something that can be a top menu or a ribbon or whatever else docked on top of the window, that popups over the docking area: you click it and… say goodbye to the focus – if the focus was in the green component it jumps in the orange component because this is how the Mouse Hooker works: it kicks in before everything and the HitTest function does the rest.

Popups

Fortunately, in WPF, the Popup is not part of the Window: it can span outside of it and this is generally a good sign that something has its own handle; in fact, that’s the case of the Popup class. You can get its handle and use a bit of Win32 to get its real estate area:

Popup popup = ...
HwndSource source = PresentationSource.FromVisual(popup.Child) as HwndSource;

RECT rect = new RECT();
GetWindowRect(source.Handle, ref rect);

and then use that area to exclude the MouseDown events happening inside of it when the popup is open.

The key here is that you have to use the Child property of the Popup to get the handle, not the Popup itself; once you’ve done this you can treat it just like a well known Win32 window because “you have a handle“!

Leave a Reply

Your email address will not be published.