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

Create Data and Control Templates using Delegates

0.00/5 (No votes)
21 Aug 2014 1  
Avoid generating strings as templates. Avoid the complicated FrameworkElementFactory. Create templates that use a delegate to create new instances, so you can use normal code to do the job.

Introduction

I don't know how many of you agree with me, but I think that no framework should force us to use a single string as input when more specific objects could be used and, in my opinion, data-templates fall in this category.

Actually we have two options to create data-templates. We can use the FrameworkElementFactory, which MSDN says to be deprecated, or we can load a data-template using a string or an XamlReader, which is considered the "right" way to do it.

Expected solution

Well, to me the right way to create a date-template in code should be a method like this:

DataTemplate.Create(Func<DependencyObject> factory);

So, if our template simply needs to create a TextBox, we will be able to create a Data-Template like this:

DataTemplate template = DataTemplate.Create(() => new TextBox());

Of course, as it will be code, we will be able to set properties, set-up bindings and even use context data (things like static or thread-static properties and methods). In fact, I think that such delegate should receive the owner control, so we can verify the actual control state before doing any action, yet the DataTemplate.LoadContent() method doesn't receive any parameters and that's why I decided the delegate should not receive any parameters either.

So, if we had data-templates that invoked a delegate to do the job we will be able to use any code during the LoadContent() call, being able to use ifs to choose which controls to generate, being able to use "context" variables and, in fact, this would even make data-template selectors useless, as a delegate will be able to choose any other existing data-template and call its LoadContent() method.

In my opinion, the only thing that we need to do this is to have a virtual LoadContent method. As it is not virtual, I will be happy to have something like a virtual OnLoadContent, which the LoadContent invokes, maybe validate the result, and finally return. Unfortunately, if we sub-class the DataTemplate class we have no useful methods to override. So, isn't there a way to create a template using a simple delegate?

The solution

I initially though it was impossible to have a solution as all the interesting methods to override in the DataTemplate class are internal, but actually there's a work-around. It is not 100% what I wanted, but it allows us to create a data-template that will invoke a delegate to do its job.

To do this, I use the FrameworkElementFactory (yeah, the "deprecated" class) to create a helper control. I can't make the FrameworkElementFactory execute code directly, but I can set a dependency property on the control it generates with any value I want, which in my case is the delegate I want to execute. And the dependency properties can be configured to execute code when they are set and so, I use the delegate that it receives to create the control and set as its inner control.

In the end, I have a data-template that executes my delegate to create the control I want when it is invoked. I don't like the fact that I actually don't end with the control I want directly, I receive a ContentControl that has the object created by my delegate as its Content.

Well, I said it is a work-around, not the perfect solution, and it doesn't affect the visual or the behavior of the generated control, so I will only worry if that extra control is really causing performance problems.

Why is the FrameworkElementFactory deprecated?

The MSDN documentation says that the FrameworkElementFactory is deprecated but doesn't give an explanation aside "not all of the template functionality is available when you create a template using this class".

When I did a research to better understand this, I found this page http://www.ikriv.com/dev/wpf/DataTemplateCreation/ in which the author shows a situation that causes a binding problem.

Well, even if my solution uses the FrameworkElementFactory, it doesn't share such problem. Maybe it is the moment in which the delegate is executed, maybe it is simply because it is normal code that does the object creation. In any case, it doesn't have such problem and I even changed that sample to show that this new solution works.

Using parent information to correctly create the control

The LoadContent() method doesn't receive the container control and I think this is pretty bad, as we can't extract information from the parent. Because of such limitation I made the delegate without parameters.

Yet, the delegate that creates the new control is free to post messages (call Dispatcher.BeginInvoke()) to the control it just created. When the Dispatcher executes such code, the control will usually be attached to its parent, so it will be possible to get that extra information at this moment, which is still before the control is shown in the screen, so there will be no flickering by doing this.

I am not saying this is the best thing to do, but I believe it is a valid work-around for the problem.

ControlTemplate

Well, the technique I used for data-templates works for control-templates too, so I decided to create a method for them too.

The Code

The entire code of this helper class is this:

/// <summary>
/// Class that helps the creation of control and data templates by using delegates.
/// </summary>
public static class TemplateGenerator
{
  private sealed class _TemplateGeneratorControl:
    ContentControl
  {
    internal static readonly DependencyProperty FactoryProperty = DependencyProperty.Register("Factory", typeof(Func<object>), typeof(_TemplateGeneratorControl), new PropertyMetadata(null, _FactoryChanged));

    private static void _FactoryChanged(DependencyObject instance, DependencyPropertyChangedEventArgs args)
    {
      var control = (_TemplateGeneratorControl)instance;
      var factory = (Func<object>)args.NewValue;
      control.Content = factory();
    }
  }

  /// <summary>
  /// Creates a data-template that uses the given delegate to create new instances.
  /// </summary>
  public static DataTemplate CreateDataTemplate(Func<object> factory)
  {
    if (factory == null)
      throw new ArgumentNullException("factory");

    var frameworkElementFactory = new FrameworkElementFactory(typeof(_TemplateGeneratorControl));
    frameworkElementFactory.SetValue(_TemplateGeneratorControl.FactoryProperty, factory);
      
    var dataTemplate = new DataTemplate(typeof(DependencyObject));
    dataTemplate.VisualTree = frameworkElementFactory;
    return dataTemplate;
  }

  /// <summary>
  /// Creates a control-template that uses the given delegate to create new instances.
  /// </summary>
  public static ControlTemplate CreateControlTemplate(Type controlType, Func<object> factory)
  {
    if (controlType == null)
      throw new ArgumentNullException("controlType");

    if (factory == null)
      throw new ArgumentNullException("factory");

    var frameworkElementFactory = new FrameworkElementFactory(typeof(_TemplateGeneratorControl));
    frameworkElementFactory.SetValue(_TemplateGeneratorControl.FactoryProperty, factory);
      
    var controlTemplate = new ControlTemplate(controlType);
    controlTemplate.VisualTree = frameworkElementFactory;
    return controlTemplate;
  }
}

And you can use it like this:

DataTemplate template = TemplateGenerator.CreateDataTemplate(() => new TextBox());

Or, if you want something a little more complex, you can create it with a binding, like this:

DataTemplate template = 
  TemplateGenerator.CreateDataTemplate
  (
    () =>
    {
      var result = new TextBox()
      result.SetBinding(TextBox.TextProperty, "BindingPathHere");
      return result;
    }
  );

You are free to use real methods instead of lambdas and to use any code you see fit to create the control, the same way you can do when you create it by code but outside of a data-template. In this case I considered the lambda as the best option, but for complex creations I will dedicate a method for that.

Sample

The sample application is a modified copy of the sample found in the page http://www.ikriv.com/dev/wpf/DataTemplateCreation/ that adds a fourth control in the main window, which uses the TemplateGenerator class to do its job.

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