Generic DataTemplateSelector

Recently I have found nice blog post by Nick Zhebrun on how to create DataTemplateSelector in XAML. It requires two supporting types:

  1. GenericDataTemplateSelector deriving from DataTemplateSelector and containing the collection of GenericDataTemplateSelectorItem objects.
  2. GenericDataTemplateSelectorItem maps together a data item property name, its value and DataTemplate which is returned if the data item has the named property with the value specified.

GenericDataTemplateSelector object and its SelectorItems collection can be defined directly in XAML and don’t require any code (say, almost any – more on that a bit later) except two aforementioned types.

I inserted it into my current project and found it fit my needs. But I’ve made some changes to turn Nick Zhebrun selector to make it even more generic (at least from my point of view).

My variation of GenericDataTemplateSelectorItem looks like follows:

/// <summary>
/// Associates the named property value with DataTemplate.
/// </summary>
public struct GenericDataTemplateSelectorItem
{
    /// <summary>
    /// Gets or sets the name of the property of the data item which 
    /// is used as a template selector.
    /// </summary>
    /// <value>The name of the property.</value>
    public string PropertyName { get; set; }
    /// <summary>
    /// Gets or sets the value of the property that triggers the DataTemplate 
    /// association.
    /// </summary>
    /// <value>The value.</value>
    public object Value { get; set; }
    /// <summary>
    /// Gets or sets the DataTemplate.
    /// </summary>
    /// <value>The DataTemplate.</value>
    public DataTemplate Template { get; set; }
    /// <summary>
    /// Gets or sets the type of the templated data item to which template could be applied.
    /// The templated item must have the TemplatedType or be derived from it.
    /// </summary>
    /// <value>The type of the templated item or null.</value>
    public Type TemplatedType { get; set; }
    /// <summary>
    /// Gets or sets the user-readable description.
    /// It could be used in UI to allow the user to select one or other template.
    /// </summary>
    /// <value>The description.</value>
    public string Description { get; set; }
}

Changes to GenericDataTemplateSelectorItem:

  • GenericDataTemplateSelectorItem type changed to struct.
  • Value property type changed to object to be more generic.
  • TemplatedType property added. It gives the ability to check the templated data item has that type or is derived from it.
  • Description property added. It’s useful in UI to allow the user to select one or other template.

My variation of GenericDataTemplateSelector looks like follows:

/// <summary>
/// Generic DataTemplateSelector based on the value of the property of the Item templated.
/// <para>Modified GenericDataTemplateSelector by Nick Zhebrun 
/// (http://zhebrun.blogspot.com/2008/09/are-you-tired-to-create.html).</para>
/// </summary>
[ContentProperty("SelectorItems")]
public class GenericDataTemplateSelector : DataTemplateSelector
{
    Collection<GenericDataTemplateSelectorItem> selectorItems 
	= new Collection<GenericDataTemplateSelectorItem>();
    /// <summary>
    /// Gets the collection of selector items.
    /// </summary>
    /// <value>The selector items collection.</value>
    public Collection<GenericDataTemplateSelectorItem> SelectorItems 
    { 
        get { return selectorItems;  }
    }

    /// <summary>
    /// When overridden in a derived class, returns a 
    /// <see cref="T:System.Windows.DataTemplate"/> based on custom logic.
    /// </summary>
    /// <param name="item">The data object for which to select the template.</param>
    /// <param name="container">The data-bound object.</param>
    /// <returns>
    /// Returns a <see cref="T:System.Windows.DataTemplate"/> or null. The default value is null.
    /// </returns>
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        if (item != null)
        {
            foreach (GenericDataTemplateSelectorItem selectorItem in SelectorItems)
            {
                // If the TemplatedType specified we should check the item has that type
                // or is derived from it.
                if (selectorItem.TemplatedType != null 
                        && !isDerived(item.GetType(), selectorItem.TemplatedType))
                    continue;

                // If the property exists on item and its value matches with the value provided
                // then select that template.
                PropertyDescriptor propertyDescriptor 
			= TypeDescriptor.GetProperties(item)[selectorItem.PropertyName];
                if (propertyDescriptor != null 
			&& selectorItem.Value.Equals(propertyDescriptor.GetValue(item)))
                    return selectorItem.Template;
            }
        }
        return null;
    }

    /// <summary>
    /// Check the itemType is the baseType type or is derived from it.
    /// </summary>
    /// <param name="itemType">Type of the templated item.</param>
    /// <param name="baseType">Acceptable base type of the templated item.</param>
    /// <returns></returns>
    private static bool isDerived(Type itemType, Type baseType)
    {
        if (itemType == baseType)
            return true;
        if (itemType.BaseType == null)
            return false;
        return isDerived(itemType.BaseType, baseType);
    }
}

 

Changes to GenericDataTemplateSelector:

  • The class  is adorned with ContentPropertyAttribute.
  • Property DataTemplateSelectorItems renamed to SelectorItems and made read only.
  • Type of SelectorItems changed to Collection<>.
  • SelectorItems collection is created on construction.
  • Item type is checked in SelectTemplate override to decide if the template could be applied to it.
  • External Reflector type from Nick Zhebrun post replaced with TypeDescriptor.

The definition of GenericDataTemplateSelector in XAML might look like this:

<parts:GenericDataTemplateSelector x:Key="chartItemsTemplateSelector">
    <parts:GenericDataTemplateSelectorItem
        PropertyName="VisualCue" 
        Value="{x:Type parts:PolylineSampledCurve}"
        Template="{StaticResource polylineTemplate}"
        TemplatedType="{x:Type parts:SampledCurveDataView}"
        Description="Polyline Sampled Curve"/>
    <parts:GenericDataTemplateSelectorItem
        PropertyName="VisualCue" 
        Value="{x:Type parts:BezierSampledCurve}"
        Template="{StaticResource bezierTemplate}"
        TemplatedType="{x:Type parts:SampledCurveDataView}"
        Description="Bezier Sampled Curve"/>
    <parts:GenericDataTemplateSelectorItem
        PropertyName="VisualCue" 
        Value="{x:Type parts:SplineSampledCurve}"
        Template="{StaticResource splineTemplate}"
        TemplatedType="{x:Type parts:SampledCurveDataView}"
        Description="Spline Sampled Curve"/>
</parts:GenericDataTemplateSelector>

StaticResource’s reference DataTemplate’s resources defined somewhere before this code snippet.

Templated ItemsSource control is bound to a collection of data items. Each item has VisualCue property of type object. In the example above I’m using the type of visual to pair it with data item both in VisualCue property and GenericDataTemplateSelectorItem.Value property. TemplatedType and Description properties allow me to click on the visual, show its "Properties" dialog and, in that dialog, allow the user to select the template desired.

One thing to note: when data item VisualCue property is changed the entire DataTemplateSelector should be reapplied to ItemsSource control the changes take effect. To do so visuals should have VisualCueChanged routed event. Somewhere in the appropriate location this event should be handled like below:

DataTemplateSelector old = chart.ItemTemplateSelector;
chart.ItemTemplateSelector = null;
chart.ItemTemplateSelector = old;

It’s a bit ugly, but it works.

Cheers,
Oleg V. Polikarpotchkin

Advertisements

About ovpwp

I am engaged in programming, maintenance and supply of computing technique since 1970. Started with the computers M, BESM, Minsk series and received appropriate education in the least, in which it was then possible in THE USSR. Then he went the usual way - ES series, IBM 360/370, Sun Spark, Ibm Power & PS2, PC. Programming started in the code (machine commands), then were sorts of assemblers, Algol, FORTRAN, PL/1, C, C , VB, C#. It is only the ones that I used the production scale; but there were, of course, others like List, Modula, Pascal, Java, etc. Currently I prefer .NET platform for desktop development. I don't really like web-programming (probably because of the inability for quality design), but I have enough experience in site building in LAMP environment using PHP.
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s