(Blogging tunes: Nine Inch Nails - "Pretty Hate Machine")

So I got an email from a guy named Mike (what a great name) who has been reading my blog and he thought he would try out some of this really cool interop technology by taking the WPF Clock that Nick Kramer demonstrated at the PDC and hosting it in a Windows Forms application following the information I had in one of my posts.  If you didn't catch Nick's presentation, one of his demonstrations involved creating a clock control based on WPF which is far superior to the clock that exists in Windows.  So my buddy Mike says, "hey, I'll host that cool clock in a Windows Forms application".  So he gets Nick's source code and the he created a solution that included the clock project as well as a Windows Forms application project to host it.  He followed the standard protocol for hosting a WPF control by using the ElementHost control

ElementHost host = new ElementHost();
WpfClock.
Clock clock = new WpfClock.Clock();
host.Controls.Add(clock);
host.Dock =
DockStyle.Fill;
this.Controls.Add(host);

Okay, good to go right?  He runs the app and then he sees the following:

Dude, WTF!  Well this is one of those times when the runtime error message that you get is actually helpful :).  You notice that it states that "Only Window and Frame can parent a Page".  This should tip you off to what's going on.  Let's go take a look at how Nick defined his clock control.  If you look at the XAML for the clock control (Clock.xaml) you will see that the root element in his XAML is a Page element.  Likewise, in the code behind for the clock class (Clock.xaml.cs) the base class for the clock control is a Page element.

<Page
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
x:Class="WpfClock.Clock"
>
     <
Grid>
          <
Grid.Background>
               <
LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
               <
LinearGradientBrush.GradientStops>
                    <
GradientStopCollection>
                         <
GradientStop Color="#FFfbfbfd" Offset="0" />
                         <
GradientStop Color="#FFf4f3ee" Offset="1" /> 
                    </
GradientStopCollection>
               </
LinearGradientBrush.GradientStops>
          </
LinearGradientBrush
       </
Grid.Background>
...

public partial class Clock : Page
{
     private System.Windows.Media.Animation.Clock _animationClock;
     private DispatcherTimer _dayTimer;
     public Clock()
     {
          InitializeComponent();
          this.Loaded += new RoutedEventHandler(Clock_Loaded);
     }

...

The problem is that we cannot host a top level WPF element in an ElementHost control.  So if your WPF control is derived from Window or Page for example, it cannot be placed into an ElementHost control in a Windows Forms application.  The good news is that this is really easy to fix, once you understand what's going on.

All we have to do is change the root tag of the clock control and then also make sure we match that to the base class in the code behind.  So for example, we can change Nick's XAML above by replacing the <Page> tag with say a <Grid> tag.  That means we also need to change the base class for the clock control from Page to Grid

<Grid
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"

xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"

x:Class="WpfClock.Clock"

>
     <
Grid
>
          <
Grid.Background
>
               <
LinearGradientBrush StartPoint="0,0" EndPoint="0,1"
>
               <
LinearGradientBrush.GradientStops
>
                    <
GradientStopCollection
>
                         <
GradientStop Color="#FFfbfbfd" Offset="0"
/>
                         <
GradientStop Color="#FFf4f3ee" Offset="1"
/> 
                    </
GradientStopCollection
>
               </
LinearGradientBrush.GradientStops
>
          </
LinearGradientBrush

       </
Grid.Background>

...

public partial class Clock : Grid
{
     private System.Windows.Media.Animation.Clock _animationClock;
     private DispatcherTimer _dayTimer;
     public Clock()
     {
          InitializeComponent();
          this.Loaded += new RoutedEventHandler(Clock_Loaded);
     }

...

Now we can re-run the application and everything should be good to go!  Let's give it a spin, shall we?

SWEET!  Just what we wanted, huh?  I just hit the bottom of my coffee cup, so gotta run get juiced up again...MHender signing off...