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:
public class DataTemplateMetaData
{
public string Case { get; set; }
public Type HandlerType { get; set; }
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
:
public interface IDataTemplatesHandler
{
IEnumerable<DataTemplateMetaData> GetDataTemplateDetails();
}
The next stage is writing a loader which invokes these classes:
Assembly assembly = Assembly.LoadFile(file);
if (assembly.GetReferencedAssemblies().FirstOrDefault(
c => c.Name == typeof(DataTemplateMetaDataLoader).Assembly.GetName().Name) != null)
{
foreach (Type dth in assembly.GetTypes())
{
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:
public class DetailDataTemplateSelector : DataTemplateSelector
{
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