Having learned how to create user interfaces in WPF, it would be fantastic to never have to worry about "old" technologies anymore. However, the reality is that most applications will not be completely rewritten just to take advantage of WPF. Many organizations with existing applications built using older UI technologies such as Windows Forms will opt to use WPF only for certain elements of their UI where it gives them specific advantages. Even if an organization does decide to adopt WPF totally, they aren't likely to stage any migration of existing applications.
In addition, given the amount of time and effort spent developing Windows Forms controls over the years, for some time you may find that there are many Windows Forms controls that you want to use that do not have a WPF equivalent.
These issues mean it is necessary to have WPF UI elements integrate with older UI technologies—and this situation will likely continue for many years. Fortunately, WPF provides very good integration with Windows Forms. The recipes in this chapter describe how to:
Use WPF windows in Windows Forms applications (recipe 13-1)
Use WPF controls in Windows Forms (recipe 13-2)
Use Windows Forms forms in WPF applications (recipe 13-3)
Use Windows Forms controls in WPF windows (recipe 13-4)
WPF also provides reasonable integration with older Win32 interfaces elements. We have chosen not to discuss Win32 integration here and instead focus only on Windows Forms. For a description on how to integrate WPF and Win32, see the article "WPF and Win32 Interoperation Overview" on MSDN at http://msdn.microsoft.com/en-us/library/ms742522.aspx
.
Create an instance of the System.Windows.Window
you want to display in your Windows Forms code. Call Window.ShowDialog
to display the window modal, or call Window.Show
to display the window modeless.
The only difficult thing about displaying a WPF window in a Windows Forms application is actually integrating the WPF source code into your project correctly if you are using Visual Studio. There is no option to add a WPF Window
when you select Add New Item in Solution Explorer.
By far the easiest way around this is to import an existing WPF Window
using the Add Existing option in Solution Explorer. This will set everything up appropriately (adding the necessary assembly references), and you can then edit the WPF Window
as you would when creating a WPF application.
Once you have a WPF window declared, you can reference and instantiate the class the same as you would do any other class. Calling Window.ShowDialog
will display the window modally, meaning that the user can interact with only that Window
and must close it before they can interact again with the rest of the application. Calling Window.Show
will display the window modeless, allowing the user to interact with the new Window
as well as the rest of the application.
The following example (shown running in Figure 13-1) displays a Windows form with two buttons. The left button opens and closes a modeless WPF Window
, and the right button opens a modal Window
. When the example creates the modeless window, it subscribes an event handler to the Window.Closing
event so that the application can update the button state should the user choose to close the Window
directly instead of using the button. The following code is the codebehind for the main Windows form:
using System; using System.Windows.Forms; using System.ComponentModel; namespace Recipe_13_01 {
public partial class Form1 : Form { private Window1 modelessWindow; private CancelEventHandler modelessWindowCloseHandler; public Form1() { InitializeComponent(); modelessWindowCloseHandler = new CancelEventHandler(Window_Closing); } // Handles the button click event to open and close the modeless // WPF Window. private void OpenModeless_Click(object sender, EventArgs e) { if (modelessWindow == null) { modelessWindow = new Window1(); // Add an event handler to get notification when the window // is closing. modelessWindow.Closing += modelessWindowCloseHandler; // Change the button text. btnOpenModeless.Text = "Close Modeless Window"; // Show the Windows Form. modelessWindow.Show(); } else { modelessWindow.Close(); } } // Handles the button click event to open the modal WPF Window. private void OpenModal_Click(object sender, EventArgs e) { // Create and display the modal window. Window1 window = new Window1(); window.ShowDialog(); } // Handles the WPF Window's Closing event for the modeless window. private void Window_Closing(object sender, CancelEventArgs e) { // Remove the event handler reference.
modelessWindow.Closing -= modelessWindowCloseHandler; modelessWindow = null; // Change the button text. btnOpenModeless.Text = "Open Modeless Window"; } } }
The following XAML provides the declaration of the WPF Window
that is opened from the Windows Forms application:
<Window x:Class="Recipe_13_01.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="WPF Recipes 13_01" Height="200" Width="300"> <StackPanel Margin="20"> <TextBlock FontSize="20" Text="A WPF Window" TextAlignment="Center"/> <Button Click="btnClose_Click" Content="Close" Margin="50" MaxWidth="50" Name="btnClose" /> </StackPanel> </Window>
The following is a small amount of code-behind used by the WPF Window
to allow users to close the Window
by clicking the Close button:
using System.Windows; using System.Windows.Forms; namespace Recipe_13_01 { /// <summary> /// Interaction logic for Window1.xaml /// </summary> public partial class Window1 : Window { public Window1() { InitializeComponent(); } private void btnClose_Click(object sender, RoutedEventArgs e) { this.Close(); } } }
Use a System.Windows.Forms.Integration.ElementHost
control on your Windows form, and host the WPF control inside it.
The ElementHost
control is a Windows Forms control that allows you to host WPF controls in Windows Forms. The ElementHost
control makes integrating WPF controls into your Windows Forms application relatively simple and even provides some limited visual design-time support.
The ElementHost
can contain a single WPF element that inherits from System.Windows.UIElement
. This element can be one of the layout containers discussed in Chapter 2, which allows you to create rich structured WPF content within the ElementHost
control. Often, the WPF element you place in the ElementHost
control will be a WPF user control (see Chapter 4) but can also be any common WPF control.
To use the ElementHost
control in Visual Studio's graphical design environment, open the Toolbox, and browse to the WPF Interoperability category. Drag the ElementHost
control, and drop it on the Windows form as you would with any other control. Using the ElementHost Tasks window, you can then select any WPF user control currently in your project to place in the ElementHost
control (see Figure 13-2).
If you do not want to use a user control, then you will need to populate the ElementHost
control programmatically by assigning the desired WPF element to the Child
property of the ElementHost
control.
The following example demonstrates how to integrate WPF controls into a Windows Forms application. The example (shown in Figure 13-3) uses a simple WPF user control consisting of a System.Windows.Shapes.Ellipse
that can change between red and blue color gradients. This EllipseControl
is assigned to one ElementHost
using the Visual Studio form builder. Another ElementHost
is populated programmatically with a System.Windows.Controls.TextBox
. A standard Windows Forms button triggers the EllipseControl
to change color and then writes a log entry to the TextBox
. Here is the XAML for the WPF user control:
<UserControl x:Class="Recipe_13_02.EllipseControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Height="300" Width="300"> <Grid x:Name="Grid1"> <Grid.Resources> <RadialGradientBrush x:Key="RedBrush" RadiusX=".8" RadiusY="1" Center="0.5,0.5" GradientOrigin="0.05,0.5"> <GradientStop Color="#ffffff" Offset="0.1" /> <GradientStop Color="#ff0000" Offset="0.5" /> <GradientStop Color="#880000" Offset="0.8" /> </RadialGradientBrush> <RadialGradientBrush x:Key="BlueBrush" RadiusX=".8" RadiusY="1" Center="0.5,0.5" GradientOrigin="0.05,0.5"> <GradientStop Color="#ffffff" Offset="0.1" /> <GradientStop Color="#0000ff" Offset="0.5" /> <GradientStop Color="#000088" Offset="0.8" /> </RadialGradientBrush> </Grid.Resources>
<Ellipse Margin="5" Name="Ellipse1" ToolTip="A WPF Ellipse." Fill="{StaticResource RedBrush}"> </Ellipse> </Grid> </UserControl>
Here is the code-behind for the EllipseControl
, which is used to control and query its current color gradient:
using System.Windows.Controls; using System.Windows.Media; namespace Recipe_13_02 { /// <summary> /// Interaction logic for EllipseControl.xaml /// </summary> public partial class EllipseControl : UserControl { public EllipseControl() { InitializeComponent(); } // Gets the name of the current color. public string Color { get { if (Ellipse1.Fill == (Brush)Grid1.Resources["RedBrush"]) { return "Red"; } else { return "Blue"; } } } // Switch the fill to the red gradient. public void ChangeColor() { // Check the current fill of the ellipse. if (Ellipse1.Fill == (Brush)Grid1.Resources["RedBrush"]) { // Ellipse is red, change to blue. Ellipse1.Fill = (Brush)Grid1.Resources["BlueBrush"]; }
else { // Ellipse is blue, change to red. Ellipse1.Fill = (Brush)Grid1.Resources["RedBrush"]; } } } }
The following is the code-behind for the main Windows Forms form—Form1
. The Form1
constructor demonstrates the programmatic creation and configuration of an ElementHost
control to display a standard WPF TextBox
control. The button1_Click
method is invoked when the user clicks the button, and it changes the color of the ellipse and appends a message to the content of the TextBox
. The rest of the application code generated by Visual Studio is not shown here but is provided in the sample code.
using System; using System.Windows; using System.Windows.Forms; using WPFControls=System.Windows.Controls; using System.Windows.Forms.Integration; namespace Recipe_13_02 { public partial class Form1 : Form { WPFControls.TextBox textBox; public Form1() { InitializeComponent(); // Create a new WPF TextBox control. textBox = new WPFControls.TextBox(); textBox.Text = "A WPF TextBox "; textBox.TextAlignment = TextAlignment.Center; textBox.VerticalAlignment = VerticalAlignment.Center; textBox.VerticalScrollBarVisibility = WPFControls.ScrollBarVisibility.Auto; textBox.IsReadOnly = true; // Create a new ElementHost to host the WPF TextBox. ElementHost elementHost2 = new ElementHost(); elementHost2.Name = "elementHost2"; elementHost2.Dock = DockStyle.Fill; elementHost2.Child = textBox; elementHost2.Size = new System.Drawing.Size(156, 253); elementHost2.RightToLeft = RightToLeft.No;
// Place the new ElementHost in the bottom left table cell. tableLayoutPanel1.Controls.Add(elementHost2, 1, 0); } private void button1_Click(object sender, EventArgs e) { // Change the ellipse color. ellipseControl1.ChangeColor(); // Get the current ellipse color and append to TextBox. textBox.Text += String.Format("Ellipse color changed to {0} ", ellipseControl1.Color); textBox.ScrollToEnd(); } } }
Create an instance of the System.Windows.Forms.Form
you want to display in your WPF code. Call Form.ShowDialog
to display the form modal or Form.Show
to display the form modeless.
It is straightforward to add a Windows form to your WPF application because Visual Studio allows you to select the Windows Forms template from the Add New Item option. You can then use Visual Studio's form builder to create the form visually.
Once you have a Form
declared, you can reference and instantiate the class the same as you would do any other class. Calling Form.ShowDialog
will display the Form
modally, meaning the user can interact only with that Form
and must close it before they can interact again with the rest of the application. Calling Form.Show
will display the Form
modeless, allowing the user to interact with the new Window
as well as the rest of the application.
The following example (shown running in Figure 13-4) displays a WPF Window
with two buttons. The left button opens and closes a modeless Form
, and the right button opens a modal Form
. When the example creates the modeless Form
, it subscribes an event handler to the Form.FormClosing
event so that the application can update the button state should the user choose to close the Form
directly instead of using the button. The following is the XAML defining the application's main window:
<Window x:Class="Recipe_13_03.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="WPF Recipes 13_03" Height="100" Width="300"> <UniformGrid Columns="2"> <Button HorizontalAlignment="Center" MaxHeight="25" Name="btnOpenModeless" Content="Open Modeless Form" Click="OpenModeless_Click" /> <Button HorizontalAlignment="Center" MaxHeight="25" Name="btnOpenModal" Content="Open Modal Form" Click="OpenModal_Click" /> </UniformGrid> </Window>
The following is the code-behind for the main WPF window, which shows how the Form
is manipulated by the WPF application:
using System.Windows; using System.Windows.Forms; namespace Recipe_13_03 { /// <summary> /// Interaction logic for Window1.xaml /// </summary> public partial class Window1 : Window { private Form1 modelessForm; private FormClosingEventHandler modelessFormCloseHandler;
public Window1() { InitializeComponent(); modelessFormCloseHandler = new FormClosingEventHandler(ModelessFormClosing); } // Handles the Windows Form Closing event for the modeless form. private void ModelessFormClosing(object sender, FormClosingEventArgs e) { // Remove the event handler reference. modelessForm.FormClosing -= modelessFormCloseHandler; modelessForm = null; // Change the button text. btnOpenModeless.Content = "Open Modeless Form"; } // Handles the button click event to open the modal Windows Form. private void OpenModal_Click(object sender, RoutedEventArgs e) { // Create and display the modal form. Form1 form = new Form1(); form.ShowDialog(); } // Handles the button click event to open and close the modeless // Windows Form. private void OpenModeless_Click(object sender, RoutedEventArgs e) { if (modelessForm == null) { modelessForm = new Form1(); // Add an event handler to get notification when the form // is closing. modelessForm.FormClosing += modelessFormCloseHandler; // Change the button text. btnOpenModeless.Content = "Close Modeless Form"; // Show the Windows Form. modelessForm.Show(); }
else { modelessForm.Close(); } } } }
Use a System.Windows.Forms.Integration.WindowsFormsHost
control on your WPF window, and host the Windows Forms control inside it.
WPF makes no specific provision for hosting ActiveX controls. To host an ActiveX control, you need to use it as you would in Windows Forms and then host the Windows Forms wrapper in WPF as described in this recipe. For further details, see the article on MSDN at http://msdn.microsoft.com/en-us/library/ms742735.aspx
.
The WindowsFormsHost
control is a WPF control that allows you to host Windows Forms controls. The WindowsFormsHost
control makes integrating Windows Forms controls into your WPF application simple.
The WindowsFormsHost
can contain a single Windows Forms control that inherits from System.Windows.Forms.Control
.
The following code demonstrates how easy it is to use the WindowsFormsHost
control to include Windows Forms controls alongside WPF controls in your WPF applications.
The example (shown in Figure 13-5) uses a Windows Forms Button
and a DataGridView
to demonstrate the ease of integration. Clicking the Button
raises an event that causes the WPF System.Windows.Shapes.Ellipse
to change between a red and a blue gradient fill. The data in the DataGridView
is provided by binding its DataSource
property to a CountryCollection
containing a set of Country
objects defined directly in XAML as a static resource.
<Window x:Class="Recipe_13_04.Window1" 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" xmlns:rec="clr-namespace:Recipe_13_04" Title="WPF Recipes 13_04" Height="300" Width="500"> <UniformGrid Columns="2" Rows="2" x:Name="Grid1"> <UniformGrid.Resources> <RadialGradientBrush x:Key="RedBrush" RadiusX=".8" RadiusY="1" Center="0.5,0.5" GradientOrigin="0.05,0.5"> <GradientStop Color="#ffffff" Offset="0.1" /> <GradientStop Color="#ff0000" Offset="0.5" /> <GradientStop Color="#880000" Offset="0.8" /> </RadialGradientBrush> <RadialGradientBrush x:Key="BlueBrush" RadiusX=".8" RadiusY="1" Center="0.5,0.5" GradientOrigin="0.05,0.5"> <GradientStop Color="#ffffff" Offset="0.1" /> <GradientStop Color="#0000ff" Offset="0.5" /> <GradientStop Color="#000088" Offset="0.8" /> </RadialGradientBrush> <rec:CountryCollection x:Key="Countries"> <rec:Country ID="1" Name="Australia" Capital="Sydney" /> <rec:Country ID="2" Name="United Kingdom" Capital="London" /> <rec:Country ID="3" Name="India" Capital="New Delhi" /> <rec:Country ID="4" Name="Russia" Capital="Moscow" /> <rec:Country ID="5" Name="Japan" Capital="Tokyo" /> </rec:CountryCollection> </UniformGrid.Resources> <!-- A Winforms Button control--> <WindowsFormsHost > <wf:Button x:Name="btnWinFormButton" Text="Make Blue" MaximumSize="100,25" BackColor="LightGray" Click="btnWinFormButton_Click"/>
</WindowsFormsHost> <!-- A WPF Ellipse--> <Ellipse Margin="5" Name="Ellipse1" ToolTip="A WPF Ellipse." Fill="{StaticResource RedBrush}"> </Ellipse> <!-- A WPF RichTextBox control--> <RichTextBox DockPanel.Dock="Bottom" ToolTip="A WPF Ellipse." VerticalScrollBarVisibility="Visible"> <FlowDocument> <Paragraph FontSize="15"> A WPF Rich text Box. </Paragraph> <Paragraph FontSize="12"> Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure. </Paragraph> </FlowDocument> </RichTextBox> <!-- A Winforms DataGridView control--> <WindowsFormsHost HorizontalAlignment="Center"> <wf:DataGridView x:Name="dataGrid" DataSource="{StaticResource Countries}"/> </WindowsFormsHost> </UniformGrid> </Window>
The following is the code-behind that handles the click event raised when the user clicks the Windows Forms Button
:
using System; using System.Windows; using System.Windows.Media; namespace Recipe_13_04 { /// <summary> /// Interaction logic for Window1.xaml /// </summary> public partial class Window1 : Window { public Window1() { InitializeComponent(); }
// Handles the click of the Winforms button. Changes the fill // of the ellipse and changes the button text. private void btnWinFormButton_Click(object sender, EventArgs e) { // Check the current fill of the ellipse. if (Ellipse1.Fill == (Brush)Grid1.Resources["RedBrush"]) { // Ellipse is red, change to blue. Ellipse1.Fill = (Brush)Grid1.Resources["BlueBrush"]; // Change the Text on the Winforms button. btnWinFormButton.Text = "Make Red"; } else { // Ellipse is blue, change to red. Ellipse1.Fill = (Brush)Grid1.Resources["RedBrush"]; // Change the Text on the Winforms button. btnWinFormButton.Text = "Make Blue"; } } } }
Here is the code that defines the Country
and CountryCollection
classes used as data in the DataGridView
:
using System; using System.Collections.Generic; namespace Recipe_13_04 { public class CountryCollection : List<Country> { } public class Country { public int ID { get; set; } public string Name { get; set; } public string Capital { get; set; } } }