Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

DataTemplate Injection

0.00/5 (No votes)
1 Jun 2011 1  
Add XAML resources to an existing application

Introduction

Plug-ins are a mechanism for making an application more robust. By enabling a system to include libraries into it after its release, users can easily upgrade a system or simply add features which respond to their needs. When using Plug-in based development in WPF, a way for adding XAML resources into the application should be added as well.

Background

I was writing an application which analyzed data. The program was parsing some raw binary files and presenting the data found within it. The data was constructed by many different types. Each type had different properties and was presented in a unique way at separate parts of the application. The system was Plug-in based, which meant customers could write Plug-ins that improved the data parsing. Many times, requests to support new data types were received. I thought of enhancing the application so that our client could add the data types by themselves. This required enabling them to add DataTemplates to the application.

The System

Different locations on the application required different DataTemplates for each type. A class which holds meta data over the DataTemplate had to be created:

/// <summary>
/// This class holds all the required data over the data template.
/// </summary>
public class DataTemplateMetaData
{
    /// <summary>
    /// Describes where the data template should be used
    /// </summary>
    public string Case { get; set; }
    /// <summary>
    /// Describes which type the data handler handles
    /// </summary>
    public Type HandlerType { get; set; }

    /// <summary>
    /// The actual data template to use
    /// </summary>
    public DataTemplate DataTemplate { get; set; }

    public DataTemplateMetaData()
    {
    }
} 

These instances were to be loaded into the application by a class inside the Plug-in which implemented the following interface:

/// <summary>
/// interface which returns all the templates meta data
/// </summary>
public interface IDataTemplatesHandler
{
    IEnumerable<DataTemplateMetaData> GetDataTemplateDetails();
} 

The next stage is writing a loader which invokes these classes:

Assembly assembly = Assembly.LoadFile(file);
// Check if the assembly refernces this assembly. 
if (assembly.GetReferencedAssemblies().FirstOrDefault(
    c => c.Name == typeof(DataTemplateMetaDataLoader).Assembly.GetName().Name) != null)
    {
	foreach (Type dth in assembly.GetTypes())
	{
		// Check if the type implements the relevant interface
		if (
			dth.GetInterfaces().FirstOrDefault(
				w => w.Name == typeof(IDataTemplatesHandler).Name) 
						!= null)
		{
			IDataTemplatesHandler handler = 
			(IDataTemplatesHandler)Activator.CreateInstance(dth, true);
			_templatesList.AddRange(handler.GetDataTemplateDetails());
		}
	}
    } 	

Last but not least, a DataTemplateSelector which is able to handle these dynamically loaded types, had to be created:

/// <summary>
/// This class comes to handle the templates data templates selection
/// </summary>
public class DetailDataTemplateSelector : DataTemplateSelector
{
    /// <summary>
    /// This string states the type of Templates we're seeking
    /// </summary>
    public string Case { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        DataTemplate dataTemplate = null;
        if (item != null)
        {
            var details = DataTemplateMetaDataLoader.GetDetailsForCertainCase(Case).
                FirstOrDefault(
                    c => c.HandlerType == item.GetType());
            if (details != null)
            {
                dataTemplate = details.DataTemplate;
            }                
        }

        return dataTemplate;
    }

    private bool GetDataTemplateForSpecificType(Type type, out DataTemplate dataTemplate)
    {
        var details = DataTemplateMetaDataLoader.GetDetailsForCertainCase(Case).
            FirstOrDefault(
                c => c.HandlerType == type);
        if (details != null)
        {
            dataTemplate = details.DataTemplate;
        }
        else
        {
            dataTemplate = null;
        }
        return dataTemplate != null;
    }
}
<Window.Resources>
    <dti:DetailDataTemplateSelector x:Key="mainselector" Case="MainView" />
    <dti:DetailDataTemplateSelector x:Key="treeselector" Case="TreeView" />
</Window.Resources> 

Using the Code

The example solution is an application with a TreeView and a ContentPresenter which presents data over the selected TreeView node. The data within the tree is loaded using reflection from the Plug-in assemblies.

History

  • 2nd June, 2011: Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here