Gotchas For Working With Windows Forms/WPF Interop

As a dev who worked on Windows Forms/WPF interop (FKA Crossbow), I compiled a list of some of the issues you might hit when working with Windows Forms and WPF together. It's not a complete list, but it's a start. Some of the issues are rough edges where the two technologies just don't come together smoothly. Others are either design decisions or design differences between Windows Forms and WPF.

 

Running with WPF Doesn’t Make Windows Forms WPF

Windows Forms has a number of limitations that WPF does not. Adding a Windows Forms control to a WindowsFormsHost in a WPF Window doesn’t mean the Windows Forms control will be able to rotate. The control doesn’t support it, it’s not going to happen (yes, if you try this, you’ll end up with a rotated window with an obstinately unrotated Windows Forms control).

Hwnds

Windows Forms has a separate hwnd for each control, while WPF uses one hwnd for all its content. A side effect of this is that a Windows Forms control hosted in WPF is effectively on top of the WPF content. WPF content inside an ElementHost is at whatever place in the z-order the ElementHost is (that is, it’s possible to put one ElementHost on top of another, but you wouldn’t see contents from two ElementHosts that overlap combining in any way).

One Child Per Host

Both WindowsFormsHost and ElementHost have a Child property (as opposed to a collection of children). You’d need to put the things you wanted as children in some sort of container to effectively add more than one child. That is, you could add a Windows Forms Button and CheckBox to a Panel, then set a WindowsFormsHost’s Child to the Panel (but not add the Button and CheckBox to the WindowsFormsHost directly).

Scaling

WPF and Windows Forms have different scaling models. We tried to make it so that scaling just works, but there are places it won’t . For example, scaling to 0 doesn’t make sense in the Windows Forms world – if you scale to 0, then back to a non-zero value, the Windows Forms control will go to 0-size and stay there.

Adapter

Both WindowsFormsHost and ElementHost have a hidden container (“adapter”) that they use to host their content. This is an implementation detail that doesn’t usually come up, but it will come up in some of the oddities we talk about. For WindowsFormsHost, the adapter derives from ContainerControl. For ElementHost, the adapter derives from DockPanel.

Nesting

Nesting WindowsFormsHost inside ElementHost and vice-versa: Interoperability between Windows Forms and WPF in either direction requires some changes in messages and/or focus. Routing to Windows Forms and back to WPF (or vice-versa) is not supported.

Focus

Focus works differently for WPF and Windows Forms, and there were some rough edges around here that we were unable to fix. If you have focus inside a WindowsFormsHost and either minimize/restore the form or show a modal dialog, the focus inside the WindowsFormsHost may be lost – the WindowsFormsHost still has focus, but the control inside it may not.

Validation has issues because of focus issues as well. Validation works within a WindowsFormsHost, but doesn’t work as you tab out of the WindowsFormsHost, or between two different WindowsFormsHosts.

Property Mapping

Property mappings enable you to react to changes in fonts, colors, and other properties. Generally, Property mappings work by listening for either *Changed events or OnPropertyChanged calls, and setting appropriate properties on either the child control or its adapter.

WindowsFormsHost.Background: the WPF world supports true transparency – if you want a StackPanel to have the same Background as its parent, you leave it null: the StackPanel won’t paint anything, and its parent will show through. In Windows Forms, this isn’t true: if you don’t paint anything, you don’t get anything, so you’ll see a black “hole” in your app where you want the parent to show through. Because of how the Background works, there’s no change notification when the parent’s Background changes (your Background didn’t change, it’s still null). This means that Background doesn’t map reliably when there are changes: we aren’t aware of the changes, so can’t react to them. If you’re changing the Background, and want the WindowsFormsHost to keep up, you can call windowsFormsHost1.PropertyMap.Apply(“Background”). When the mapping is applied, it sets the BackColor of the adapter if the Background was a solid opaque color. Otherwise, it sets BackgroundImage on the adapter and the hosted control to a brush based on the System.Windows.Media.Brush. Note that this means the background will be a snapshot of the background, not a live view. If the BackgroundImage is set, it won’t override the hosted control’s BackgroundImage unless the hosted control’s BackgroundImage was the same as the adapter’s BackgroundImage or null.

ElementHost.BackColor/BackgroundImage/BackgroundImageLayout: when any of these background-affecting properties changes, the ElementHost snaps a picture of what its background should be and uses this as a brush. If BackColorTransparent is set to true, this snapshot is based on the ElementHost’s parent background. Otherwise, it’s based on the ElementHost’s background. The snapshot includes BackColor, BackgroundImage, BackgroundImageLayout, and any paint events. Like the WindowsFormsHost Background mapping, this is a snapshot: if your Paint handler continually changes how the form looks, the ElementHost will get out of sync. Also note that it’s not possible to remove just one of these property mappings and have it work the way you likely want it: that is, if you remove the BackgroundImageLayout mapping, BackgroundImageLayout will still be used to create the snapshot, but the snapshot will not be updated if BackgroundImageLayoutChanged fires. This is because the snapshot actually asks the control to paint itself rather than trying to duplicate the control painting logic.

WindowsFormsHost.Cursor: this is similar to WindowsFormsHost.Background, in that we don’t get change notification when one of our parents changes their cursor. If Cursor is mapped, the adapter overrides the Cursor getter to check up the parent’s chain for a FrameworkElement that has Cursor set (or has ForceCursor set if ForceCursor is mapped). If either of these mappings is tampered with at all, we act as if it’s not mapped, so even
windowsFormsHost1.PropertyMap[“Cursor”] += delegate {Console.WriteLine(“Mapping hit!”);};

will cause us not to honor the cursor mapping (since cursor is so odd, we don’t believe anything you could reasonably do in your mapping would be compatible with what we’re doing, so we take a hands-off approach).

Layout

The design is for your hosted content to fill the WindowsFormsHost or ElementHost. In order to do that, a number of properties are set when you set an ElementHost’s Child property…

· Height

· Width

· Margin

· VerticalAlignment

· HorizontalAlignment

· MaximumSize

…or a WindowsFormsHost’s Child property:

· Margin

· Dock

· AutoSize

· Location

Navigation Applications

WindowsFormsHost will recreate its controls when used in a navigation application. This means that any content that has been typed in by the user will be lost.

Message Loop Interop

WindowsFormsHost.EnableWindowsFormsInterop: this is called by the WindowsFormsHost constructor. It adds a message filter to the WPF message loop that calls System.Windows.Forms.Control.PreProcessMessage if a System.Windows.Forms.Control was the target of the message and translates/dispatches the message if needed.

If you show a Window on a Windows Forms message loop (System.Windows.Forms.Application.Run()), you can’t type anything unless you call ElementHost.EnableModelessKeyboardInterop. ElementHost.EnableModelessKeyboardInterop takes a Window, and adds an IMessageFilter that re-routes key-related messages to WPF’s message pump.

Opacity/Layered Windows/AllowTransparency

Setting Opacity on a WindowsFormsHost (setting AllowsTransparency on a Window) will not work, since HwndHost doesn't support this.

Dispose

When mixing Windows Forms and WPF to make sure the ElementHost or WindowsFormsHost is disposed, or you could leak resources. Windows Forms will dispose an ElementHost when the non-modal Form it’s on closes; WPF will dispose a WindowsFormsHost if your application shuts down. (Really the interop-specific bit here is that you could show a WindowsFormsHost on a Window in a Windows Forms message loop and never get that your Application is shutting down.)

EnableVisualStyles

System.Windows.Forms.Application.EnableVisualStyles is called in the template for a Windows Forms application. That is, while this isn’t called by default, if you use Visual Studio to create a project, you’ll get COMCTL6-themed controls if available (buttons, checkboxes, comboboxes, etc.) EnableVisualStyles needs to be called before handles are created on the thread. For instance, you could add a System.Windows.Forms.Application.EnableVisualStyles call in your Window’s constructor before InitializeComponent runs. There’s not a built-in way to do this in XAML .

Licensed Controls

Some licensed controls show dialogs in response to handle creation, perhaps either informing the user that they need a license, or that they have 3 trial uses of the control remaining. WindowsFormsHost derives from HwndHost, and the child control’s handle is created inside BuildWindowCore, if it’s not already. HwndHost doesn’t allow messages to be processed during BuildWindowCore – and showing a dialog pumps messages. In order to avoid this, you could call CreateControl on the control programmatically before setting it as the WindowsFormsHost’s Child.