DESKTOP

Creating Windows with Mixed Content

10/2/2010 3:24:53 PM
In some cases the clean window-by-window separation isn't suitable. For example, you might want to place WPF content in an existing form alongside Windows Form content. Although this model is conceptually messier, WPF handles it quite gracefully.

In fact, including Windows Forms content in a WPF application (or vice versa) is more straightforward than adding ActiveX content to a Windows Forms application. In the latter scenario, Visual Studio must generate a wrapper class that sits between the ActiveX control and your code, which manages the transition from managed to unmanaged code. This wrapper is component-specific, which means each ActiveX control you use requires a separate customized wrapper. And because of the quirks of COM, the interface exposed by the wrapper might not match the interface of the underlying component exactly.

When integrating Windows Forms and WPF content, you don't need a wrapper class. Instead, you use one of a small set of containers, depending on the scenario. These containers work with any class, so there's no code generation step. This simpler model is possible because even though Windows Forms and WPF are dramatically different technologies, they are both firmly grounded in the world of managed code.

The most significant advantage of this design is that you can interact with Windows Forms controls and WPF elements in your code directly. The interoperability layer comes into effect only when this content is rendered in the window. This part takes place automatically without requiring any developer intervention. You also don't need to worry about keyboard handling in modeless windows because the interoperability classes you'll use (ElementHost and WindowsFormsHost) handle that automatically.

1. WPF and Windows Forms "Airspace"

To integrate WPF and Windows Forms content in the same window, you need to be able to segregate a portion of your window for "foreign" content. For example, it's completely reasonable to throw a 3-D graphic into a Windows Forms application because you can place that 3-D graphic in a distinct region of a window (or even make it take up the entire window). However, it's not easy or worthwhile to reskin all the buttons in your Windows Forms application by making them WPF elements, because you'll need to create a separate WPF region for each button.

Along with the considerations of complexity, there are also some things that just aren't possible with WPF interoperability. For example, you can't combine WPF and Windows Forms content by overlapping it. That means you can't have a WPF animation send an element flying over a region that's rendered with Windows Forms. Similarly, you can't overlap partially transparent Windows Forms content over a WPF region to blend them together. Both of these violate what's known as the airspace rule, which dictates that WPF and Windows Forms must always have their own distinct window regions, which they manage exclusively. Figure 1 shows what's allowed and what isn't.

Figure 1. The airspace rule

Technically, the airspace rule results from the fact that in a window that includes WPF content and Windows Forms content, both regions have a separate window handle, or hwnd. Each hwnd is managed, rendered, and refreshed separately.

Window handles are managed by the Windows operating system. In classic Windows applications, every control is a separate window, which means each control has ownership of a distinct piece of screen real estate. Obviously, this type of "window" isn't the same as the top-level windows that float around your screen—it's simply a self-contained region (rectangular or otherwise). In WPF, the model is dramatically different—there's a single, top-level hwnd, and the WPF engine does the compositing for the entire window, which allows more pleasing rendering (for example, effects such as dynamic anti-aliasing) and far greater flexibility (for example, visuals that render content outside their bounds).

NOTE

There are a few WPF elements that use separate window handles. These include menus, tooltips, and the drop-down portion of a combo box, all of which need the ability to extend beyond the bounds of the window.

The implementation of the airspace rule is fairly straightforward. If you place Windows Forms content overtop of WPF content, you'll find that the Windows Forms content is always overtop, no matter where it's declared in the markup or what layout container you use. That's because the WPF content is a single window, and the container with Windows Forms content is implemented as a separate window that's displayed overtop of a portion of the WPF window.

If you place WPF content in a Windows Forms form, the result is a bit different. Every control in Windows Forms is a distinct window and therefore has its own hwnd. So, WPF content can be layered anywhere with relation to other Windows Forms controls in the same window, depending on its z-index. (The z-index is determined by the order in which you add controls to the parent's Controls collection so that controls added later appear on top of those added before.) However, the WPF content still has its own completely distinct region. That means you can't use transparency or any other technique to partially overwrite (or combine your element with) Windows Forms content. Instead, the WPF content exists in its own self-contained region.

2. Hosting Windows Forms Controls in WPF

To show a Windows Forms control in a WPF window, you use the WindowsFormsHost class in the System.Windows.Forms.Integration namespace. The WindowsFormsHost is a WPF element (it derives from FrameworkElement) that has the ability to hold exactly one Windows Forms control, which is provided in the Child property.

It's easy enough to create and use WindowsFormsHost programmatically. However, in most cases it's easiest to create it declaratively in your XAML markup. The only disadvantage is that Visual Studio doesn't include much designer support for the WindowsFormsHost control. Although you can drag and drop it onto a window, you need to fill in its content (and map the required namespace) by hand.

The first step is to map the System.Windows.Forms namespace so you can refer to the Windows Forms control you want to use:

<Window x:Class="InteroperabilityWPF.HostWinFormControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
Title="HostWinFormControl" Height="300" Width="300" >

Now you can create the WindowsFormsHost and the control inside just as you would any other WPF element. Here's an example that uses the MaskedTextBox from Windows Forms:
<Grid>
<WindowsFormsHost>
<wf:MaskedTextBox x:Name="maskedTextBox"></wf:MaskedTextBox>
</WindowsFormsHost>
</Grid>

The WindowsFormsHost can hold any Windows Forms control (that is, any class that derives from System.Windows.Forms.Control). It can't hold Windows Forms components that aren't controls, such as the HelpProvider or the NotifyIcon.


Figure 2 shows a MaskedTextBox in a WPF window.

Figure 2. A masked text box for a phone number

You can set most of the properties of your MaskedTextBox directly in your markup. That's because Windows Forms uses the same TypeConverter infrastructure  to change strings into property values of a specific type. This isn't always convenient—for example, the string representation of a type may be awkward to enter by hand—but it usually allows you to configure your Windows Forms controls without resorting to code. For example, here's a MaskedTextBox equipped with a mask that shapes user input into a seven-digit phone number with an optional area code:

<wf:MaskedTextBox x:Name="maskedTextBox" Mask="(999)-000-0000"></wf:MaskedTextBox>

You can also use ordinary XAML markup extensions to fill in null values, use static properties, create type objects, or use objects that you've defined in the Resources collection of the window. Here's an example that uses the type extension to set the MaskedTextBox.ValidatingType property. This specifies that the MaskedTextBox should change the supplied input (a phone number string) into an Int32 when the Text property is read or the focus changes:
<wf:MaskedTextBox x:Name="maskedTextBox" Mask="(999)-000-0000"
ValidatingType="{x:Type sys:Int32}"></wf:MaskedTextBox>

One markup extension that won't work is a data binding expression because it requires a dependency property. (Windows Forms controls are constructed out of normal .NET properties.) If you want to bind a property of a Windows Forms control to the property of a WPF element, there's an easy workaround—just set the dependency property on the WPF element and adjust the BindingDirection as required.

Finally, it's important to note that you can hook events up to your Windows Forms control using the familiar XAML syntax. Here's an example that attaches an event handler for the MaskInputRejected event, which occurs when a keystroke is discarded because it doesn't suit the mask:

<wf:MaskedTextBox x:Name="maskedTextBox" Mask="(999)-000-0000"
MaskInputRejected="maskedTextBox_MaskInputRejected"></wf:MaskedTextBox>

Obviously, these aren't routed events, so you can't define them at higher levels in the element hierarchy.

When the event fires, your event handler responds by showing an error message in another element. In this case, it's a WPF label that's located elsewhere on the window:

private void maskedTextBox_MaskInputRejected(object sender,
System.Windows.Forms.MaskInputRejectedEventArgs e)
{
lblErrorText.Content = "Error: " + e.RejectionHint.ToString();
}

Don't import the Windows Forms namespaces (such as System.Windows.Forms) in a code file that already uses WPF namespaces (such as System.Windows.Controls). The Windows Forms classes and the WPF classes share many names. Basic ingredients (such as Brush, Pen, Font, Color, Size, and Point) and common controls (such as Button, TextBox, and so on) are found in both libraries. To prevent naming clashes, it's best to import just one set of namespaces in your window (WPF namespaces for a WPF window, Windows Forms namespaces for a form) and use fully qualified names or a namespace alias to access the others.


This example illustrates the nicest feature about WPF and Windows Forms interoperability: it doesn't affect your code. Whether you're manipulating a Windows Forms control or a WPF element, you use the familiar class interface for that object. The interoperability layer is simply the magic that lets both ingredients coexist in the window. It doesn't require any extra code.

NOTE

To have Windows Forms controls use more up-to-date control styles introduced with Windows XP, you must call EnableVisualStyles() when your application starts.

Windows Forms content is rendered by Windows Forms, not WPF. Therefore, display-related properties of the WindowsFormsHost container (properties such as Transform, Clip, and Opacity) have no effect on the content inside. This means that even if you set a rotational transform, set a narrow clipping region, and make your content 50% transparent, you'll see no change. Similarly, Windows Forms uses a different coordinate system that sizes controls using physical pixels. As a result, if you increase the system DPI setting of your computer, you'll find that the WPF content resizes cleanly to be more detailed, but the Windows Forms content does not.

3. WPF and Windows Forms User Controls

One of the most significant limitations of the WindowsFormsHost element is that it can hold only a single Windows Forms control. To compensate, you could use a Windows Forms container control. Unfortunately, Windows Forms container controls don't support XAML content models, so you'll need to fill in the contents of the container control programmatically.

A much better approach is to create a Windows Forms user control. This user control can be defined in a separate assembly that you reference, or you can add it directly to your WPF project (using the familiar Add → New Item command). This gives you the best of both worlds—you have full design support to build your user control and an easy way to integrate it into your WPF window.

In fact, using a user control gives you an extra layer of abstraction similar to using separate windows. That's because the containing WPF window won't be able to access the individual controls in your user control. Instead, it will interact with the higher-level properties you've added to your user control, which can then modify the controls inside. This makes your code better encapsulated and simpler because it limits the points of interaction between the WPF window and your Windows Forms content. It also makes it easier to migrate to a WPF-only solution in the future, simply by creating a WPF user control that has the same properties and swapping that in place of the WindowsFormsHost. (And once again, you can further improve the design and flexibility of your application by moving the user control into a separate class library assembly.)

NOTE

Technically, your WPF window can access the controls in a user control by accessing the Controls collection of the user control. However, to use this back door, you need to write error-prone lookup code that searches for specific controls using a string name. That's always a bad idea.

As long as you're creating a user control, it's a good idea to make it behave as much like WPF content as possible so it's easier to integrate into your WPF window layout. For example, you may want to consider using the FlowLayoutPanel and TableLayoutPanel container controls so that the content inside your user controls flows to fit its dimensions. Simply add the appropriate control and set its Dock property to DockStyle.Fill. Then place the controls you want to use inside. For more information about using the Windows Forms layout controls (which are subtly different than the WPF layout panels), refer to my book Pro .NET 2.0 Windows Forms and Custom Controls in C# (Apress, 2005).

ACTIVEX INTEROPERABILITY

WPF has no direct support for ActiveX interoperability. However, Windows Forms has extensive support in the form of runtime callable wrappersprimary interop assembly, which is a handcrafted, fine-tuned RCW that's guaranteed to dodge interop issues. (RCWs), dynamically generated interop classes that allow a managed Windows Forms application to host an Active component. Although there are .NET-to-COM quirks that can derail some controls, this approach works reasonably well for most scenarios, and it works seamlessly if the person who creates the component also provides a

So, how does this help you if you need to design a WPF application that uses an ActiveX control? In this case, you need to layer two levels of interoperability. First you place the ActiveX control in a Windows Forms user control or form. You then place that user control in your WPF window or show the form from your WPF application.


4. Hosting WPF Controls in Windows Forms

The reverse approach—hosting WPF content in a form built with Windows Forms—is just as easy. In this situation, you don't need the WindowsFormsHost class. Instead, you use the System.Windows.Forms.Integration.ElementHost class, which is part of the WindowsFormsIntegration.dll assembly.

The ElementHost has the ability to wrap any WPF element. However, the ElementHost is a genuine Windows Forms control, which means you can place it in a form alongside other Windows Forms content. In some respects, the ElementHost is more straightforward than the WindowsFormsHost, because every control in Windows Forms is displayed as a separate hwnd. Thus, it's not terribly difficult for one of these windows to be rendered with WPF instead of User32/GDI+.

Visual Studio provides some design-time support for the ElementHost control, but only if you place your WPF content in a WPF user control. Here's what to do:

  1. Right-click the project name in the Solution Explorer, and choose Add → New Item. Pick the User Control (WPF) template, supply a name for your custom component class, and click Add.

    NOTE

    This example assumes you're placing the WPF user control directly in your Windows Forms project. If you have a complex user control, you must choose to use a more structured approach and place it in a separate class library assembly.

  2. Add the WPF controls you need to your new WPF user control. Visual Studio gives you the usual level of design-time support for this step, so you can drag WPF controls from the Toolbox, configure them with the Properties window, and so on.

  3. When you're finished, rebuild your project (choose Build → Build Solution). You can't use your WPF user control in a form until you've compiled it.

  4. Open to the Windows Forms form where you want to add your WPF user control (or create a new form by right-clicking the project in the Solution Explorer and choosing Add → Windows Form).

  5. To place the WPF user control in a form, you need the help of the ElementHost control. The ElementHost control appears on the WPF Interoperability tab of the Toolbox. Drag it onto your form, and size it accordingly.

    For better separation, it's a good idea to add the ElementHost to a specific container rather than directly to the form. This makes it easier to separate your WPF content from the rest of the window. Typically, you'll use the Panel, FlowLayoutPanel, or TableLayoutPanel.


  6. To choose the content for the ElementHost, you use the smart tag. If the smart tag isn't visible, you can show it by selecting the ElementHost and clicking the arrow in the top-right corner. In the smart tag you'll find a drop-down list named Select Hosted Content. Using this list, you can pick the WPF user control you want to use, as shown in Figure 3.

    Figure 3. Selecting WPF content for an ElementHost
  7. Although the WPF user control will appear in your form, you can't edit its content there. To jump to the corresponding XAML file in a hurry, click the Edit Hosted Content link in the ElementHost smart tag.

Technically, the ElementHost can hold any type of WPF element. However, the ElementHost smart tag expects you to choose a user control that's in your project (or a referenced assembly). If you want to use a different type of control, you'll need to write code that adds it to the ElementHost programmatically.

5. Access Keys, Mnemonics, and Focus

The WPF and Windows Forms interoperability works because the two types of content can be rigorously separated. Each region handles its own rendering and refreshing and interacts with the mouse independently. However, this segregation isn't always appropriate. For example, it runs into potential problems with keyboard handling, which sometimes needs to be global across an entire form. Here are some examples:

  • When you tab from the last control in one region, you expect focus to move to the first control in the next region.

  • When you use a shortcut key to trigger a control (such as a button), you expect that button to respond no matter what region of the window it's located in.

  • When you use a label mnemonic, you expect the focus to move to the linked control.

  • Similarly, if you suppress a keystroke using a preview event, you don't expect the corresponding key event to occur in either region, no matter what control currently has focus.

The good news is that all these expected behaviors work without any customization needed. For example, consider the WPF window shown in Figure 4. It includes two WPF buttons (top and bottom) and a Windows Forms button (in the middle).

Figure 4. Three buttons with shortcut keys

Here's the markup:

<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Button Click="cmdClicked">Use Alt+_A</Button>
<WindowsFormsHost Grid.Row="1">
<wf:Button Text="Use Alt+&amp;B" Click="cmdClicked"></wf:Button>
</WindowsFormsHost>
<Button Grid.Row="2" Click="cmdClicked">Use Alt+_C</Button>
</Grid>

NOTE

The syntax for identifying accelerator keys is slightly different in WPF (which uses an underscore) than in Windows Forms. Windows Forms uses the & character, which must be escaped as &amp; in XML because it's a special character.

When this window first appears, the text in all buttons is normal. When the user presses and holds the Alt key, all three shortcuts are underlined. The user can then trigger any one of the three buttons by pressing the A, B, or C key (while holding down Alt).

The same magic works with mnemonics, which allows labels to forward the focus to a nearby control (typically a text box). You can also tab through the three buttons in this window as though they were all WPF-defined controls, moving from top to bottom. Finally, the same example continues to work if you host a combination of Windows Forms and WPF content in a Windows Forms form.

Keyboard support isn't always this pretty, and there are a few focus-related quirks that you may run into. Here's a list of issues to watch out for:

  • Although WPF supports a keystroke forwarding system to make sure every element and control gets a chance to handle keyboard input, the keyboard handling models of WPF and Windows Forms still differ. For that reason, you won't receive keyboard events from the WindowsFormsHost when the focus is in the Windows Forms content inside. Similarly, if the user moves from one control to another inside a WindowsFormsHost, you won't receive the GotFocus and LostFocus events from the WindowsFormsHost.

    NOTE

    Incidentally, the same is true for WPF mouse events. For example, the MouseMove event won't fire for the WindowsFormsHost while you move the mouse inside its bounds.

  • Windows Forms validation won't fire when you move the focus from a control inside the WindowsFormsHost to an element outside the WindowsFormsHost. Instead, it will fire only when you move from one control to another inside the WindowsFormsHost. (When you remember that the WPF content and the Windows Forms content are essentially separated windows, this makes perfect sense because it's the same behavior you experience if you switch between different applications.)

  • If the window is minimized while the focus is somewhere inside a WindowsFormsHost, the focus may not be restored when the window is restored.

6. Property Mapping

One of the most awkward details in interoperability between WPF and Windows Forms is the way they use similar but different properties. For example, WPF controls have a Background property that allows you to supply a brush that paints the background. Windows Forms controls use a simpler BackColor property that fills the background with a color based on an ARGB value. Obviously, there's a disconnect between these two properties, even though they're often used to set the same aspect of a control's appearance.

Most of the time, this isn't a problem. As a developer, you'll simply be forced to switch between both APIs, depending on the object you're working with. However, WPF adds a little bit of extra support through a feature called property translators.

Property translators won't allow you to write WPF-style markup and have it work with Windows Forms controls. In fact, property translators are quite modest. They simply convert a few basic properties of the WindowsFormsHost (or ElementHost) from one system to another so that they can be applied on the child control.

For example, if you set the WindowsFormsHost.IsEnabled property, the Enabled property of the control inside is modified accordingly. This isn't a necessary feature (you could do much the same thing by modifying the Enabled property of the child directly, instead of the IsEnabled property of the container), but it can often make your code a bit clearer.

To make this work, the WindowsFormsHost and ElementHost classes both have a PropertyMap collection, which is responsible for associating a property name with a delegate that identifies a method that performs the conversion. By using a method, the property map system is able to handle sticky conversions such as BackColor to Background, and vice versa. By default, each is filled with a default set of associations. (You're free to create your own or replace the existing ones, but this degree of low-level fiddling seldom makes sense).

Table 1 lists the standard property map conversions that are provided by the WindowsFormHost and ElementHost classes.

Table 1. Property Maps
WPF PropertyWindows Forms PropertyComments
ForegroundForeColorConverts any ColorBrush into the corresponding Color object. In the case of a GradientBrush, the color of the GradientStop with the lowest offset value is used instead. For any other type of brush, the ForeColor is not changed, and the default is used.
BackgroundBackColor or BackgroundImageConverts any SolidColorBrush to the corresponding Color object. Transparency is not supported. If a more exotic brush is used, the WindowsFormsHost creates a bitmap and assigns it to the BackgroundImage property instead.
CursorCursor 
FlowDirectionRightToLeft 
FontFamily, FontSize, FontStretch, FontStyle, FontWeightFont 
IsEnabledEnabled 
PaddingPadding 
VisibilityVisibleConverts a value from the Visibility enumeration into a Boolean value. If Visibility is Hidden, the Visible property is set to true so that the content size can be used for layout calculations but the WindowsFormsHost does not draw the content. If Visibility is Collapsed, the Visible property is not changed (so it remains with its currently set or default value), and the WindowsFormsHost does not draw the content.

NOTE

Property maps work dynamically. For example, if the WindowsFormsHost.FontFamily property is changed, a new Font object is constructed and applied to the Font property of the child control.

WIN32 INTEROPERABILITY

With Windows Forms entering its twilight years and no major feature enhancements planned, it's hard to remember that Windows Forms was a new kid on the block just a few years ago. WPF certainly doesn't limit its interoperability to Windows Forms application—if you want to work with the Win32 API or place WPF content in a C++ MFC application, you can do that too.

You can host Win32 in WPF using the System.Windows.Interop.HwndHost class, which works analogously to the WindowsFormsHost class. The same limitations that apply to WindowsFormsHost apply to HwndSource (for example, the airspace rule, focus quirks, and so on). In fact, WindowsFormsHost derives from HwndHost.

The HwndHost is your gateway to the traditional world of C++ and MFC applications. However, it also allows you to integrate managed DirectX content. Currently, WPF does not include any DirectX interoperability features, and you can't use the DirectX libraries to render content in a WPF window. However, you can use DirectX to build a separate window and then host that inside a WPF window using the HwndHost. Although DirectX is far beyond the scope of this book (and an order of magnitude more complex than WPF programming), you can download the managed DirectX libraries at http://msdn.microsoft.com/directx.

The complement of HwndHost is the HwndSource class. While HwndHost allows you to place any hwnd in a WPF window, HwndSource wraps any WPF visual or element in an hwnd so it can be inserted in a Win32-based application, such as an MFC application. The only limitation is that your application needs a way to access the WPF libraries, which are managed .NET code. This isn't a trivial task. If you're using a C++ application, the simplest approach is to use the Managed Extensions for C++. You can then create your WPF content, create an HwndSource to wrap it, set the HwndHost.RootVisual property to the top-level element, and then place the HwndSource into your window.

You'll find much more content to help you with complex integration projects and legacy code online and in the Visual Studio help.


Other  
 
Top 10
Sony STR-DN1030 – Home Cinema Amplifier
Spotflux - Enjoy The Privilege Of A US Based IP Address
Sony KDL-26EX553 – LCD Television
Protecting Me
Do Top Twitter Tips
Canon PowerShot SX240 HS - A Powerful Perfection
Fujifilm X-Pro1 : Mechanical dials are everywhere
Sony NEX-F3 Review (Part 3)
Sony NEX-F3 Review (Part 2)
Sony NEX-F3 Review (Part 1)
Most View
How to beat 2012’s web threats (Part 1)
Magix Photo Story On DVD MX Deluxe
2012 Make It Worth ( Part 1)
Ultrabook vs MacBook (Part 2)
Programming .NET Security : Asymmetric Encryption Explained (part 1) - Creating Asymmetric Keys
Windows 7 : Managing and Applying LGPOs (part 3) - Using Local Policies
Exchange Server 2010 server roles (part 3) - Edge Transport Server role
Olympus Launches OM-D E-M5
Microsoft SQL Server 2005 : Report Definition and Design (part 3)
Will Apple Be The Next Big Name in Gaming? (Part 3)
IIS 7.0 : Managing Application Pools (part 3) - Advanced Application Pool Configuration, Monitoring Application Pool Recycling Events
Cloud Application Architectures : Database Management
Web Security : Seeking Design Flaws - Bypassing Required Navigation, Attempting Privileged Operations
The best browser hacks (part 1) - Mozilla Firefox
SharePoint 2010 : Operations Management with the SharePoint Central Administration Tool (part 4) - Reviewing Backup and Restore Settings in SPCA
SQL Server 2008 : Using the CLR - CLR and Managed Code Explained
Rollout Strategy in Group Policy of Windows Vista
Reporting Services with SQL Azure : Deploying the Report & Creating a Subreport
Vigor 2850n
The Best Computers You're (Probably) Never Heard Of (Part 2) - Tatung Einstein, Camputers Lynx