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:
- As a set of properties
- As a mapping from items to properties
- 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:
internal partial class PaneStyleSelector : StyleSelector
{
public Dictionary<Type, Style> Styles { get; set; } = new Dictionary<Type, Style>();
}
We now have to implement the mapping logic:
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...
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:
<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 Document
s 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:
What's Next
Now you get the idea for the StyleSelector
, it is easy to transpose this to the TemplateSelector
.
internal partial class PaneTemplateSelector : DataTemplateSelector
{
public Dictionary<Type, DataTemplate>
Templates { get; set; } = new Dictionary<Type, DataTemplate>();
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