Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically load WPF View & View Model from DLL

I have a wizard created in WPF consisting of pages as UserControl objects. What I'm trying to do is to load plugins from .DLL files which contain the following:

  • A code file for the plugin logic.
  • A XAML user control which will present configuration options for the plugin, displayed in the main wizard.
  • A view model for the user control.

I've been able to successfully load in and instantiate the UserControl object as well as the View Model, and I have gotten to the stage where the Control appears in it's own wizard page as intended. (This is probably the view model that's instantiated correctly, since I set the title for the wizard page in the view model and that all works fine)

The problem I'm getting is that the UserControl I've loaded from the DLL isn't displaying correctly. Instead of displaying the UserControl contents, it just shows the plaintext MyDLL.MyCustomUserControl from the line x:Class="MyDLL.MyCustomUserControl" where the actual user control should be.

The contents of my user control that I'm loading from the DLL is:

<UserControl x:Class="MyDLL.MyCustomUserControl"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<StackPanel>
    <Label Content="This is the Plugin Options Page" />
    <TextBox Text="{Binding Path=PluginStringText}" />
</StackPanel>
</UserControl>

The property PluginStringText exists in the View Model for this User Control.

I have a feeling I need to somehow assign or bind the View Model to the User Control after loading it in from the DLL. As part of my wizard, there is a section where I define Data Templates (this is another UserControl which contains the Wizard's pages), but I don't know how to actually add extra templates during runtime. I have a feeling this is causing the issue.

Searching through many topics reveals I could probably do this through the code behind file for the view, but then I have no way of obtaining a reference to the view's code behind in the first place to call a method. Is there a way of adding a new DataTemplate entry from the View's View Model class?

<UserControl.Resources>

    <DataTemplate DataType="{x:Type viewModel:ExistingPage1ViewModel}">
        <view:ExistingPage1View />
    </DataTemplate>

    <DataTemplate DataType="{x:Type viewModel:ExistingPage2ViewModel}">
        <view:ExistingPage2View />
    </DataTemplate>
<...>

Thanks in advance if someone could point me in the right direction

like image 497
Antix Avatar asked Jan 05 '14 04:01

Antix


1 Answers

I found a way to resolve this. The problem was as I had expected, there was no mapping between the ViewModel data type and the View in which to render the ViewModel with.

I discovered a useful guide here http://www.ikriv.com/dev/wpf/DataTemplateCreation/ which explains how to create a new data template within code, very similar to if you'd hard coded the template in the XAML.

This was all well and good, but I still had to find a way to call the method to create the data template.

Well - It turned out I didn't need to call the method at all in the code behind! I just put the method to create the data template directly in my view model which is responsible for populating the wizard's pages.

This also has the added benefit that I do not have to hard code my existing pages datatemplate's in the xaml.


I will add the code referenced at http://www.ikriv.com/dev/wpf/DataTemplateCreation/ for future reference:

The method to create the DataTemplate object:

DataTemplate CreateTemplate(Type viewModelType, Type viewType)
{
    const string xamlTemplate = "<DataTemplate DataType=\"{{x:Type vm:{0}}}\"><v:{1} /></DataTemplate>";
    var xaml = String.Format(xamlTemplate, viewModelType.Name, viewType.Name, viewModelType.Namespace, viewType.Namespace);

    var context = new ParserContext();

    context.XamlTypeMapper = new XamlTypeMapper(new string[0]);
    context.XamlTypeMapper.AddMappingProcessingInstruction("vm", viewModelType.Namespace, viewModelType.Assembly.FullName);
    context.XamlTypeMapper.AddMappingProcessingInstruction("v", viewType.Namespace, viewType.Assembly.FullName);

    context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
    context.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
    context.XmlnsDictionary.Add("vm", "vm");
    context.XmlnsDictionary.Add("v", "v");

    var template = (DataTemplate)XamlReader.Parse(xaml, context);
    return template;
}

This DataTemplate then needs to be registered as an application resource:

Registering the DataTemplate:

Application.Current.Resources.Add(template.DataTemplateKey;, template);

Once again, this code was provided thanks to Ivan Krivyakov at http://www.ikriv.com/dev/wpf/DataTemplateCreation/

like image 155
Antix Avatar answered Oct 04 '22 12:10

Antix