Hosting Windows Forms and ActiveX Controls on a Canvas

I'm blogging to:

KEXP
Live Stream

 

Happy Halloween!

I received an email from Keeron today and he was experiencing some difficulties in hosting an ActiveX control in a WindowsFormsHost which was parented to a Canvas in a WPF application. His basic problem seemed to be that the control simply was not showing up in the WPF application at all. So what's up with that?

Let's start off by removing the ActiveX control from the equation since the issue isn't directly related to the ActiveX control, the issue exists for just standard Windows Forms controls as well. So let's assume we have a simply scenario where we want to host a Windows Forms button in a WPF application. Let's first consider hosting it in a Grid element then we will switch it to use a Canvas element.

<

Window x:Class="AvalonApplication27.Window1"
xmlns=https://schemas.microsoft.com/winfx/avalon/2005
xmlns:x=https://schemas.microsoft.com/winfx/xaml/2005
Title="AvalonApplication27"
Loaded="WindowLoaded"
     >
<Grid x:Name="grid1">

</Grid>
</Window>

The code behind would look something like:

private void WindowLoaded(object sender, RoutedEventArgs e)
{
WindowsFormsHost host = new WindowsFormsHost();
System.Windows.Forms.Button b = new System.Windows.Forms.Button();
b.Text = "WF Button";
host.Children.Add(b);
this.grid1.Children.Add(host);
}

Run the app and everything works just fine. But now lets change the Grid element to a Canvas element...

<

Window x:Class="AvalonApplication27.Window1"
xmlns=https://schemas.microsoft.com/winfx/avalon/2005
xmlns:x=https://schemas.microsoft.com/winfx/xaml/2005
Title="AvalonApplication27"
Loaded="WindowLoaded"
>
<Canvas x:Name="canvas1">

</Canvas>
</Window>

And change the code behind to reference the Canvas instead of the Grid:

private void WindowLoaded(object sender, RoutedEventArgs e)
{
WindowsFormsHost host = new WindowsFormsHost();
System.Windows.Forms.Button b = new System.Windows.Forms.Button();
b.Text = "WF Button";
host.Children.Add(b);
this.canvas1.Children.Add(host);
}

Run the app again and we don't see our button! What gives? Well, let's think about it just a second. Firstly, I neglected to indicate where in the canvas the button should go. Normally, you would do this is XAML as follows:

<

Canvas x:Name="canvas1">
<Button Canvas.Top="5" Canvas.Left="5">I'm a button</Button>
</Canvas>

But I'm working in code so I need to figure out how to do this using C#... Well the Canvas.Top and Canvas.Left properties are actually "attached properties" in WPF parlance and to set these using code you would use the SetValue() method on the object in question and then provide the property and value:

private void WindowLoaded(object sender, RoutedEventArgs e)
{
WindowsFormsHost host = new WindowsFormsHost();
System.Windows.Forms.Button b = new System.Windows.Forms.Button();
b.Text = "WF Button";
host.Children.Add(b);
host.SetValue(Canvas.TopProperty, 5.0);
host.SetValue(Canvas.LeftProperty, 5.0);
this.canvas1.Children.Add(host);
}

Cool, that should do it right? Let's give it a whirl... Doh! Still didn't work! Why not? Well, the issue is related to how the WPF layout engine works. The problem is that since we did not explicitly set the size of the WindowsFormsHost control, the layout engine thinks that it has a size of 0 and therefore does not layout the control properly. To fix this, we just need to give the WindowsFormsHost control an explicit size:

private void WindowLoaded(object sender, RoutedEventArgs e)
{
WindowsFormsHost host = new WindowsFormsHost();
host.Height = 100;
host.Width = 300;
System.Windows.Forms.Button b = new System.Windows.Forms.Button();
b.Text = "WF Button";
host.Children.Add(b);
host.SetValue(Canvas.TopProperty, 5.0);
host.SetValue(Canvas.LeftProperty, 5.0);
this.canvas1.Children.Add(host);
}

Now when we re-run the application, we will see our button.