Click here to Skip to main content
15,885,366 members
Articles / Desktop Programming / XAML
Tip/Trick

Simplifying StyleSelector and TemplateSelector for Xceed AvalonDock

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
25 Aug 2017CPOL3 min read 6.5K   2  
How to make it easier to manage StyleSelector and TemplateSelector with AvalonDock, especially when the selection is based on the ViewModel type only.

Introduction

Like many, I am using the powerful AvalonDock from Xceed to build powerful EDI. And like many, I struggle with the lack of documentation. The most advanced documentation I found is here on codeproject.com and is based on EDI. And I first worked with the PaneStyleSelector and PaneTemplateSelector as designed in EDI. But I found this a bit heavy, and managed to build up a lighter version that I want now to share with you.

Background

When it comes to providing a StyleSelector and a TemplateSelector, you can see in the article series mentioned above that it involves:

  • In the selector itself, a property for each style / template to implement
  • A new case in the SelectStyle/SelectTemplate method to map the item to the property
  • In the XAML part, assigned to the property, the actual style or template.

This provides flexibility to implement any kind of logic to map an item to a style or template. But the cost for that is that the mapping list is hardcoded three times:

  1. As a set of properties
  2. As a mapping from items to properties
  3. As a mapping from properties to styles/templates

And in EDI, like in any implementation I've gone through so far, this mapping has always been based on the item type only.

So I decided to reduce this to one single list, with the assumption that I would only use item types for my mapping.

The New StyleSelector

The styles and templates are coded on the XAML side. So if the list is implemented only once, this is the place. Therefore, we would need a generic place to store this information in the Selector. Let's go for the StyleSelector and build the appropriate tool:

C#
internal partial class PaneStyleSelector : StyleSelector
{
  /// <summary>
  /// The mapping between a type and a style.
  /// </summary>
  public Dictionary<Type, Style> Styles { get; set; } = new Dictionary<Type, Style>();

}

We now have to implement the mapping logic:

C#
/// <summary>
/// Returns the style to use based on the given item's type.
/// </summary>
/// <param name="item">The item to select a style for.</param>
/// <param name="container">Ignored.</param>
/// <returns>The style to apply for this item.</returns>
public override Style SelectStyle(object item, DependencyObject container)
{
  var itemType = item.GetType();
  foreach (Type t in Styles.Keys)
  {
    if (itemType.Equals(t) || itemType.IsSubclassOf(t))
      return Styles[t];
  }

  return base.SelectStyle(item, container);
}

In this implementation, the mapping condition...

C#
if (itemType.Equals(t) || itemType.IsSubclassOf(t))

...enables the usage of inheritance. That is, if you have different types of documents that inherit from the same Document superclass - or even interface - , and you want them all to have the same style mapped, you can simply map the Document type to that style.

So let's see how this goes on the XAML side:

XML
<local:PaneStyleSelector x:Key="PaneStyleSelector">
  <local:PaneStyleSelector.Styles>
    <Style x:Key="{x:Type documents:IDocument}" TargetType="{x:Type xcad:LayoutItem}">
      <... />
    </Style>
    <Style x:Key="{x:Type local:ToolViewModel}"  TargetType="{x:Type xcad:LayoutAnchorableItem}">
      <... />
    </Style>
  </local:PaneStyleSelector.Styles>
</local:PaneStyleSelector>

One Point of Attention

The order in a dictionary is uncertain, and may not be the order in which you entered the items. Imagine you want all your Documents to use StyleD, except for the ConfidentialDocument type (that also inherits from Document) that should use StyleC. You cannot assume that putting an entry for ConfidentialDocument in the dictionary, and then an entry for Document will get you to the expected result: it can happen that the order is reversed in the dictionary and that StyleD is used for ConfidentialDocument.

There are several ways to solve this issue:

  • Adding intelligence to the mapping procedure:
    C#
    if (item is ConfidentialDocument)
      return Styles[typeof(ConfidentialDocument)];
    else
      foreach (Type t in Styles.Keys)
        ...
    
  • Tweaking the Document classes hierarchy: you could implement a NonConfidentialDocument class that would inherit from Document and contain all children classes but ConfidentialDocument, and map the style on this type instead of Document. You may be likely to treat non-confidential document differently in other parts of your code.
  • Get back to the original property-based version. I wouldn't.

What's Next

Now you get the idea for the StyleSelector, it is easy to transpose this to the TemplateSelector.

C#
internal partial class PaneTemplateSelector : DataTemplateSelector
{
  /// <summary>
  /// The link between a type and a template.
  /// </summary>
  public Dictionary<Type, DataTemplate>
                    Templates { get; set; } = new Dictionary<Type, DataTemplate>();

  /// <summary>
  /// Returns the appropriate template based on the given item's type.
  /// </summary>
  /// <param name="item">The item to select a template for.</param>
  /// <param name="container">Ignored.</param>
  /// <returns>The style to apply for this item.</returns>
  public override DataTemplate SelectTemplate(object item, DependencyObject container)
  {
    var itemType = item.GetType();
    foreach (Type t in Templates.Keys)
    {
      if (itemType.Equals(t) || itemType.IsSubclassOf(t))
        return Templates[t];
    }

    return base.SelectTemplate(item, container);
  }
}

And given that the selectors implementations are purely generic, you can simply store this in your favorite library that you will reference in your XAML code. And everything in your application will be in the XAML.

Have fun!

History

  • 08.25.2017: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Switzerland Switzerland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --