Freigeben über


Hosting a WPF Control in a Windows Forms Application

(Blogging Tunes: Screaming Headless Torsos - "1995")

We've talked about hosting Windows Forms controls in WPF applications, but what about the other way?  You may very well want to just keep your existing Windows Forms application and "sprinkle" in some WPF sweetness in strategic places.  That means you will need some means to be able to place WPF controls side-by-side with Windows Forms controls.  Is it do-able?  Well, if it weren't, I probably wouldn't have a blog entry on the subject now would I? 

So what we'll do is create the converse of our other application that demonstrated hosting a Windows Forms ListBox in a WPF application.  That means that we will create a Windows Forms application and have it host a WPF ListBox.  Pretty exciting huh?  Try to contain yourself...

First step is to create a Windows Forms application using Visual Studio (VS):

Next we'll need to add reference to the WPF namespaces and the System.Windows.Forms.Integration namespace:

Note that you will need the following references: PresentationCore, PresentationFramework, UIAutomationProvider, UIAutomationTypes, WindowsBase

Remember, this DLL can be found in the "\Program Files\Reference Assemblies\Microsoft\Avalon\v2.0.50215" directory.  Now let's just go to the Load event for our Form and add the code to populate the Form with the WPF ListBox control.

         private void Form1_Load(object sender, EventArgs e)
        {
            ElementHost host = new ElementHost();
            System.Windows.Controls.ListBox wpfListBox = new System.Windows.Controls.ListBox();
            for (int i = 0; i < 10; i++)
            {
                wpfListBox.Items.Add("Item " + i.ToString());
            }
            host.Controls.Add(wpfListBox);
            host.Dock = DockStyle.Fill;
            this.Controls.Add(host);
        }

All we have to do here is create an instance of the ElementHost control, create an instance of the WPF ListBox, populate the ListBox with some items and add it to the ElementHost control.  Then we just have to add the ElementHost control to the Form itself.  Note that if we don't dock fill the host control, we won't see the ListBox at all.  Now let's run the application:

Cool.  But what if you don't want to just use a single, standard WPF control?  What if you want to instead use a composite WPF control that is defined in some XAML file somewhere.  How can you make that happen, Houdini?  Well, it's easier than you might think, oh inquisitive one.  Let's use the same project, but let's rip out the list box and replace it with a WPF composite control.

Blow away the following lines of code:

             System.Windows.Controls.ListBox wpfListBox = new System.Windows.Controls.ListBox();
            for (int i = 0; i < 10; i++)
            {
                wpfListBox.Items.Add("Item " + i.ToString());
            }
            host.Controls.Add(wpfListBox);

Next let's add a new item to our project, specifically an Avalon UserControl:

This will create a UserControl1.xaml and a UserControl1.xaml.cs file and put them in our project.  As you may guess, this just provides you with a XAML file that will describe the WPF composite control and the code behind file to go with it.  So let's modify the UserControl1.xaml file to represent the composite control:

 <UserControl x:Class="WindowsApplication89.UserControl1"
    xmlns="https://schemas.microsoft.com/winfx/avalon/2005"
    xmlns:x="https://schemas.microsoft.com/winfx/xaml/2005"
    >
    <UserControl.FixedTemplate>
        <Grid Background="VerticalGraident LemonChiffon Red">
          <StackPanel>
            <Button>Hello</Button>
            <Button>From</Button>
            <Button>WFP</Button>
          </StackPanel>
  </Grid>
    </UserControl.FixedTemplate>
</UserControl>

You can see that all we are really doing here is creating a composite of three WPF buttons.  Obviously, you would want to do something much more useful, but I'm too lazy to think of anything really clever here.  Let's build the app and see what happens!

DOH!  Whaddya mean an ERROR?  Yep, you should have the following error:

What happened?  Well, let's think about the error for just a minute...  If you double-click the error message to take you to the line of code that it is complaining about, you will see that it takes you to the code behind file for your WPF UserControl and it is complaining about the InitializeComponent method call in that class.  Huh?  What's going on here?  I have no idea where the implementation of this method is supposed to be!  To understand what's happening here, you have to have some insight into how WPF works.  When you create a WPF application, the project template is smart enough to know how to deal with XAML objects.  Specifically, when you build a WPF project, the compiler knows how to parse the XAML, create a binary representation of the XAML (known as BAML) and generate code for the XAML.  The problem is that we have a Windows Forms application and not a WPF application and the template we used to create this application does not know anything about XAML.

Whatchoo talkin' 'bout Willis?

Well Gary, we have to teach the Windows Forms application how to understand the XAML files and to know what to do with them so the right thing happens.  We do this by hand modifiying the project file (.csproj, .vbproj, etc.).  YIKES!  Okay, don't panic.  It's not that big of a deal.  Just go find the project file and open it up in your favorite text editor (which is of course Notepad, isn't it?).  Make sure you don't just double-click on the project file or VS will just open the project.  Okay, now that we have the project file open in the editor, scroll down to the bottom of the file.  You will see the following line:

   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />

This line instructs the build system how to build general CSharp applications.  We have to add a similar line to tell the build system how to deal with WPF things as well.  So what we'll do is copy that line, paste it below the other one and make a small change.

   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
  <Import Project="$(MSBuildBinPath)\Microsoft.WinFX.targets" />

Notice that all we changed after we pasted the line was that we changed CSharp to WinFX.  Now the build system will know how to foshizzle the XAML!  Okay, let's save the project file, then reopen the project using VS and rebuild the solution.

Ah, success at last!  If you're really nosy, you can cruise over to the bin directory below your project and see all of the files that get created for you as a result of adding this line.  This is where the implementation of the InitializeComponent method lives (among other stuff).

This little hiccup is just a temporary situation, by the time we ship the integration layer, we should have these kinds of details ironed out so you won't have to do such a goofy hack.  Okay, let's run the beast:

There you have it!  Okay, I'm gettin' kinda' hungry so I'm gonna go get some grub.  Happy interop everybody!

Comments

  • Anonymous
    January 27, 2006
    This helped me resolve EXACTLY what I just ran into! Thanks a bunch and take an extra doughnut, you've earned it.

    John
  • Anonymous
    April 15, 2006
    I want to know how to propagate events between the WinFX user control to the window it is hosting?