Skip to main content

C# - Using WinForms In WPF [Beginner]


So we all know that WPF is awesome, but it is still a very young framework. And so, sometimes, it doesn't have everything that we might want or desire. For instance, there are a number of controls in WinForms that don't exist in WPF - and they come in handy once in a while. What we are going to look at today is how to use WinForms controls inside of WPF - a very easy and almost painless process, in my opinion.

We are going to take a look at both embedding WinForms using C# code and using XAML - both have their own advantages and disadvantages. The WinForms control we are going to be using is the DataGridView - a powerful WinForms control that does not have a comparable WPF control (although, the service pack update to WPF coming this summer will include a DataGrid - possibly eliminating the need for embedding the WinForms version). There are some drawbacks for embedding, and we will address those as well.

So the first thing that we need to do (after creating a new WPF project) is add a few references. You can do this by by right-clicking on the references folder in the solution explorer, and choosing "Add Reference":

Visual Studio Add Reference Context Menu

Then you will get a dialog like this:

Visual Studio Add Reference Dialog

There are two .NET components you will want to add - first, System.Windows.Forms, and then, all the way at the bottom (after you get to this dialog a second time), WindowsFormsIntegration.

Once those two references are added, we have access to all of the WinForms controls, and access to the components that will allow us to embed them in WPF. So now lets get started on some code. First, we are going to take a look at embedding the DataGridView using C#, so this means our XAML is going to be extremely simple:
<Window x:Class="WinFormsInWPF.Window1" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Using WinForms In WPF" Height="300" Width="300">
  <Grid x:Name="_Container">
    <Grid.RowDefinitions>
      <RowDefinition Height="*"></RowDefinition>
      <RowDefinition Height="20"></RowDefinition>
    </Grid.RowDefinitions>
    <Button Grid.Row="1" Click="ClearClick" HorizontalAlignment="Right">
      Clear All Rows
    </Button>
  </Grid>
</Window>
 
As you might notice, there is no reference to the DataGridView anywhere in that XAML. All we have is a grid with two rows, and a button in the second row. This is because we are going to be shoving in the DataGridView into the first row using C# code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Forms.Integration;

namespace WinFormsInWPF
{
  public partial class Window1 : Window
  {
    private System.Windows.Forms.DataGridView _MyDataGrid;
    private WindowsFormsHost _MyHost;

    public Window1()
    {
      InitializeComponent();

      _MyHost = new WindowsFormsHost();
      _MyDataGrid = new System.Windows.Forms.DataGridView();

      System.Windows.Forms.DataGridViewColumn col;
      col = new System.Windows.Forms.DataGridViewColumn();
      col.CellTemplate = new System.Windows.Forms.DataGridViewTextBoxCell();
      col.Name = "Col 1";
      _MyDataGrid.Columns.Add(col);
      col = new System.Windows.Forms.DataGridViewColumn();
      col.CellTemplate = new System.Windows.Forms.DataGridViewTextBoxCell();
      col.Name = "Col 2";
      _MyDataGrid.Columns.Add(col);
      _MyDataGrid.Rows.Add(new object[] { "Item 1", "Foo" });
      _MyDataGrid.Rows.Add(new object[] { "Item 2", "Bar" });

      _MyHost.Child = _MyDataGrid;
      _Container.Children.Add(_MyHost);     
    }

    private void ClearClick(object sender, RoutedEventArgs e)
    {
      _MyDataGrid.Rows.Clear();
    }
  }
}
 
So, here in the constructor (after the InitializeComponent call - we want all our XAML created stuff to be initialized first), we make a new WindowsFormsHost (which is from the namespace System.Windows.Forms.Integration). This object is the glue layer between WPF and WinForms. The WindowsFormsHost is a FrameworkElement, so it can be added to anything in WPF that can take an element. But it has a property Child which takes in a system.Windows.Forms.Control - and this is the control that will be embedded.

So we create the WindowsFormsHost and the DataGridView, and then populate the DataGridView with some info. You might be wondering why there is no using statement for System.Windows.Forms - and instead all the references are explicit. This is because there are some conflicts between the System.Windows.Forms and the standard WPF namespaces - so if you do add a using statement for System.Windows.Forms, it is very likely ambiguous references will start to crop up in your code. So it is just safer and easier to explicitly reference the System.Windows.Forms classes.

After the DataGridView is all set up, we add it as the child for the WindowsFormsHost, and then we add the WindowsFormsHost as a child of our Grid. Interacting with the DataGridView in code works exactly as you might expect - for instance, the ClearClick method which clears the current rows in the DataGridView when the WPF button is clicked. The interaction on the user side of things also now pretty much just works, although there can be some quirks with focus and input that you may have to deal with (and will probably be specific to your particular situation). Here is a picture of the sample app in action:

WPF in WinForms

Now onto how do this in XAML, instead of using C#. Here is our new XAML code:
<Window x:Class="WinFormsInWPF.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:WinForms="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
    Title="Using WinForms In WPF" Height="300" Width="300">
  <Grid x:Name="_Container">
    <Grid.RowDefinitions>
      <RowDefinition Height="*"></RowDefinition>
      <RowDefinition Height="20"></RowDefinition>
    </Grid.RowDefinitions>
    <WindowsFormsHost Grid.Row="0">
      <WinForms:DataGridView x:Name="_MyDataGrid">
      </WinForms:DataGridView>
    </WindowsFormsHost>
    <Button Grid.Row="1" Click="ClearClick" HorizontalAlignment="Right">
      Clear All Rows
    </Button>
  </Grid>
</Window>
 
As you can see, it got a bit more complicated. First, we added a new xmlns attribute to the Window tag. This pulls in the System.Windows.Forms under the prefix WinForms. The other change is the addition of the WindowsFormsHost component. We add it just like any other WPF FrameworkElement, but inside we get to declare a WinForms control - in this case a DataGridView. We give our DataGridView a name so we can refer to it later, and that's it for the XAML side. Clean, isn't it?

And the C# side gets cleaned up as well:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WinFormsInWPF
{
  public partial class Window1 : Window
  {
    public Window1()
    {
      InitializeComponent();

      System.Windows.Forms.DataGridViewColumn col;
      col = new System.Windows.Forms.DataGridViewColumn();
      col.CellTemplate = new System.Windows.Forms.DataGridViewTextBoxCell();
      col.Name = "Col 1";
      _MyDataGrid.Columns.Add(col);
      col = new System.Windows.Forms.DataGridViewColumn();
      col.CellTemplate = new System.Windows.Forms.DataGridViewTextBoxCell();
      col.Name = "Col 2";
      _MyDataGrid.Columns.Add(col);
      _MyDataGrid.Rows.Add(new object[] { "Item 1", "Foo" });
      _MyDataGrid.Rows.Add(new object[] { "Item 2", "Bar" });
    }

    private void ClearClick(object sender, RoutedEventArgs e)
    {
      _MyDataGrid.Rows.Clear();
    }
  }
}
 
As you can see, all we have to worry about is the filling of the DataGridView with some data (and it still needs to come after the InitializeComponent call - otherwise nothing will have been created yet). The only downside to using XAML to embed the WinForms control is that you don't have any control over the constructor - the default no argument constructor is always used. Other than that, its nice and keeps the data/display separation paradigm intact.

Now that we have it all working, what are the drawbacks for embedding a WinForms control in WPF? Well, there are a couple. First off, there is one I already mentioned - sometimes you will have to do extra work to get focus and input to transition smoothly across the boundaries. For instance, tabbing may not initially do what you want it to (although in most cases it works well). Another drawback is that you don't have a lot of WPF's fancy render capability - for instance, you can't apply transforms/animations to WinForms controls, and you can't overlap WPF and WinForms controls - it pretty much is guaranteed not to do what you want.

And that is about it for hosting WinForms controls inside of WPF. If you would like, you can download a Visual Studio project containing all the above code here. As always, if you have any questions or comments, feel free to leave them below.

Source Files:

Comments

Popular posts from this blog

C# Snippet - Shuffling a Dictionary [Beginner]

Randomizing something can be a daunting task, especially with all the algorithms out there. However, sometimes you just need to shuffle things up, in a simple, yet effective manner. Today we are going to take a quick look at an easy and simple way to randomize a dictionary, which is most likely something that you may be using in a complex application. The tricky thing about ordering dictionaries is that...well they are not ordered to begin with. Typically they are a chaotic collection of key/value pairs. There is no first element or last element, just elements. This is why it is a little tricky to randomize them. Before we get started, we need to build a quick dictionary. For this tutorial, we will be doing an extremely simple string/int dictionary, but rest assured the steps we take can be used for any kind of dictionary you can come up with, no matter what object types you use. Dictionary < String , int > origin = new Dictionary < string , int >(); ...

C# WPF Printing Part 2 - Pagination [Intermediate]

About two weeks ago, we had a tutorial here at SOTC on the basics of printing in WPF . It covered the standard stuff, like popping the print dialog, and what you needed to do to print visuals (both created in XAML and on the fly). But really, that's barely scratching the surface - any decent printing system in pretty much any application needs to be able to do a lot more than that. So today, we are going to take one more baby step forward into the world of printing - we are going to take a look at pagination. The main class that we will need to do pagination is the DocumentPaginator . I mentioned this class very briefly in the previous tutorial, but only in the context of the printing methods on PrintDialog , PrintVisual (which we focused on last time) and PrintDocument (which we will be focusing on today). This PrintDocument function takes a DocumentPaginator to print - and this is why we need to create one. Unfortunately, making a DocumentPaginator is not as easy as...

C# WPF Tutorial - Implementing IScrollInfo [Advanced]

The ScrollViewer in WPF is pretty handy (and quite flexible) - especially when compared to what you had to work with in WinForms ( ScrollableControl ). 98% of the time, I can make the ScrollViewer do what I need it to for the given situation. Those other 2 percent, though, can get kind of hairy. Fortunately, WPF provides the IScrollInfo interface - which is what we will be talking about today. So what is IScrollInfo ? Well, it is a way to take over the logic behind scrolling, while still maintaining the look and feel of the standard ScrollViewer . Now, first off, why in the world would we want to do that? To answer that question, I'm going to take a an example from a tutorial that is over a year old now - Creating a Custom Panel Control . In that tutorial, we created our own custom WPF panel (that animated!). One of the issues with that panel though (and the WPF WrapPanel in general) is that you have to disable the horizontal scrollbar if you put the panel in a ScrollV...