Chapter 13. Migrating and Windows Forms Interoperability

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:

Note

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.

Use WPF Windows in a Windows Forms Application

Problem

You need to display a WPF window in a Windows Forms application.

Solution

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.

How It Works

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 Code

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();
         }
     }
}
Displaying a WPF Window from a Windows Forms application

Figure 13-1. Displaying a WPF Window from a Windows Forms application

Use WPF Controls in Windows Forms

Problem

You need to display WPF UI elements alongside Windows Forms controls in a Windows form.

Solution

Use a System.Windows.Forms.Integration.ElementHost control on your Windows form, and host the WPF control inside it.

How It Works

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).

Using ElementHost in Visual Studio

Figure 13-2. Using ElementHost in Visual Studio

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 Code

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();
       }
   }
}
Using WPF controls in a Windows Forms form

Figure 13-3. Using WPF controls in a Windows Forms form

Use Windows Forms in a WPF Application

Problem

You need to display a Windows form in a WPF application.

Solution

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.

How It Works

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 Code

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();
            }
        }
    }
}
Displaying a Windows Forms form from a WPF application

Figure 13-4. Displaying a Windows Forms form from a WPF application

Use Windows Forms Controls in a WPF Window

Problem

You need to display Windows Forms controls alongside WPF controls in a WPF window.

Solution

Use a System.Windows.Forms.Integration.WindowsFormsHost control on your WPF window, and host the Windows Forms control inside it.

Note

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.

How It Works

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 Code

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; }
   }
}
Using Windows Forms controls in a WPFWindow

Figure 13-5. Using Windows Forms controls in a WPFWindow

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset