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":
Then you will get a dialog like this:
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:
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
Post a Comment