Application Resources for WPF Views Hosted in Windows Forms

Datetime:2016-08-23 03:55:40          Topic: WPF           Share

Using Application Resources with Windows Forms Hosted WPF Views

A viewer of my Pluralsight Course WPF Data Binding In Depth asked an interesting question about WPF views hosted in a Windows Forms app that was a challenge I solved in the past but never blogged about, so figured I'd put it out there here for posterity for anyone who is still migrating to WPF or living in a hybrid WPF/Windows Forms world, and for a more public answer than just posting it to the Pluralsight discussion forum for the course.

To paraphrase, the question was: I am using WPF Interop to host some WPF views within a predominantly Windows Forms host application. I want to have application scoped resources shared among the views that I can access as Resources from XAML, or Application.Current.FindResource(), but can't do that because there is no Application root class to put the resources into. How can I do this?

So here is the trick to answer that requirement... Just because you don't have a root WPF Application instance in normal Windows Forms interop hosting of WPF views, doesn't mean you can't .

Because the WPF Application class has an internal singleton model for itself, you can actually create your own instance and establish it as the current WPF application, and then any resources you put into its resource dictionary become app scoped just like if it was a normal WPF app.

Let me walk through a quick example. You can download the full source demo here.

Create the Hosted WPF Controls

I start with a new Windows Forms project. Then I add a WPF User Control Library to the solution and reference it from the main Windows Forms app. Then I define two WPF UserControls in that library named View1 and View2.

In View1 I add a TextBlock with "View 1" as its Text, and also a Button to show programmatic access to resources. I also put a border around the content to make it clear where the WPF control boundary is once it is hosted in the Windows.

<Border BorderBrush="Blue"  
        BorderThickness="3">
    <Grid>
        <TextBlock Text="View 1"
                    Foreground="{DynamicResource TextBrush}" />
        <Button x:Name="button"
                Content="Button"
                HorizontalAlignment="Left"
                Margin="12,98,0,0"
                VerticalAlignment="Top"
                Width="76"
                Click="button_Click" />
    </Grid>
</Border>

Notice that the TextBlock is using a DynamicResource named TextBrush. The button click handler uses Application.Current.FindResource to show that works as well.

private void button_Click(object sender, RoutedEventArgs e)  
{
    var otherBrush = Application.Current.FindResource("OtherBrush");
    if (otherBrush != null) MessageBox.Show("Found App Resource!");
}

In View2 I have another Border containing a TextBlock using DynamicResource to get to the same Brush out of app resources as View1:

<Border BorderBrush="Blue"  
        BorderThickness="3">
    <Grid>
        <TextBlock Text="View 2"
                    Foreground="{DynamicResource TextBrush}" />
    </Grid>
</Border>

Host the WPF Views in Windows Forms

All I need to do at this point is build my solution, then go to a Windows Form in the designer, and drag and drop my two views onto the form:

As you can see the Windows Forms Designer is a little flaky about rendering the hosted controls in the designer, so you need to run to verify everything is working right. When you do your drag-drop, a Windows Forms control called ElementHost is created for each WPF control you drag out and provides the bridge between the Windows Forms object model and the WPF one.

Creating the Application Resources

So now I need to do the magic part here - make it so there is a WPF Application Object with application scoped resources. Well, really there is no magic to it, it is actually pretty straightforward.

First I can add a ResourceDictionary to my WPF project, and I'll name it AppResources.xaml. In there I add the TextBrush and OtherBrush resources that the WPF views are depending on.

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:WPFViews">
    <SolidColorBrush x:Key="TextBrush"
                     Color="Blue" />
    <SolidColorBrush x:Key="OtherBrush"
                     Color="Azure" />

</ResourceDictionary>

Then I declare an Application derived class in one of the WPF libraries that are being pulled into the host. I expose a Start method on it that can be called from the hosting application to initialize it:

public class WPFApplication : Application  
{
    private static WPFApplication _wpfApp;
    public static void Start()
    {
        _wpfApp = new WPFApplication();
        _wpfApp.ShutdownMode = ShutdownMode.OnExplicitShutdown;
        ResourceDictionary appResources = new ResourceDictionary();
        appResources.Source = new Uri("/WPFViews;component/AppResources.xaml", UriKind.Relative);
        _wpfApp.Resources.MergedDictionaries.Add(appResources);
    }
}

Note that in this, I am creating my own static instance of a WPFApplication object, which itself inherits from the WPF Application class. I change its Shutdown mode because by default, WPF will shutdown the application instance when the first Window that is created is closed - but the hosting Windows Forms app needs to be the one controlling app lifetime.

Then you can see I simply create an empty ResourceDictionary, use the Source property to pull in my AppResources.xaml file, and then use the MergedDictionaries property on the App instance Resources to merge them in.

Then I just need to call Start from the startup code of the host:

[STAThread]
static void Main()  
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    WPFApplication.Start();
    Application.Run(new Form1());
}

And viola, we have Application resources for WPF views hosted in a Windows Forms app.

In case you are wondering if you can use StaticResource to pull in those resources instead of DynamicResource. The answer is a heavily caveated yes - if you are willing to kill the Windows Forms designer for any hosting view that contains those WPF views. It turns out it will resolve and work at runtime, but at design time it will break, and that throws the Windows Forms designer into a tizzy. So stick to DynamicResource and you will be good.

Hope people find this helpful.

Please be sure to check out all my WPF courses on Pluralsight :

And coming very soon: WPF Productivity Playbook!





About List