using System; using System.Collections.Generic; using System.ComponentModel; using System.Windows; using System.Windows.Controls; using System.Windows.Markup; // See http://lambert.geek.nz/2010/02/25/templates-galore/ for more info. namespace Mirality.Wpf { /// /// Associates the named property value with DataTemplate. /// public struct GenericDataTemplateSelectorItem { /// /// Gets or sets the name of the property of the data item which /// is used as a template selector. /// /// The name of the property. public string PropertyName { get; set; } /// /// Gets or sets the path of the property of the data item which /// is used as a template selector. (Similar to PropertyName, but /// uses a more flexible but heavier lookup mechanism.) /// /// The path of the property. public string PropertyPath { get; set; } /// /// Gets or sets the value of the property that triggers the DataTemplate /// association. /// /// The value. public object Value { get; set; } /// /// Gets or sets the DataTemplate. /// /// The DataTemplate. public DataTemplate Template { get; set; } /// /// 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. /// /// The type of the templated item or null. public Type TemplatedType { get; set; } /// /// Gets or sets the user-readable description. /// It could be used in UI to allow the user to select one or other template. /// /// The description. public string Description { get; set; } } /// /// Generic DataTemplateSelector based on the value of the property of the Item templated. /// [ContentProperty("SelectorItems")] public class GenericDataTemplateSelector : DataTemplateSelector { private readonly List selectorItems = new List(); /// /// Gets the collection of selector items. /// /// The selector items collection. public List SelectorItems { get { return selectorItems; } } /// /// When overridden in a derived class, returns a /// based on custom logic. /// /// The data object for which to select the template. /// The data-bound object. /// /// Returns a or null. The default value is null. /// public override DataTemplate SelectTemplate(object item, DependencyObject container) { if (SelectorItems != null && item != null) { foreach (var selectorItem in SelectorItems) { // If TemplatedType is specified we should check the item has that type // or is derived from it. if (selectorItem.TemplatedType != null && !selectorItem.TemplatedType.IsAssignableFrom(item.GetType())) { continue; } if (selectorItem.PropertyPath != null) // PropertyPath wins over PropertyName, since it's more flexible { var currentValue = EvaluateBinding(item, new PropertyPath(selectorItem.PropertyPath)); if (currentValue == null) { if (selectorItem.Value == null) { return selectorItem.Template; } continue; } var currentValueType = currentValue.GetType(); if (MatchProperty(currentValueType, currentValue, selectorItem.Value)) { return selectorItem.Template; } } else if (selectorItem.PropertyName != null) { // If the property exists on item and its value matches with the value provided // then select that template. var propertyDescriptor = TypeDescriptor.GetProperties(item)[selectorItem.PropertyName]; if (propertyDescriptor != null) { if (MatchProperty(propertyDescriptor.PropertyType, propertyDescriptor.GetValue(item), selectorItem.Value)) { return selectorItem.Template; } } } else { return selectorItem.Template; // no property conditions, so we win } } } return null; } private static bool MatchProperty(Type expectedType, object currentValue, object expectedValue) { if (expectedValue != null) { var converter = TypeDescriptor.GetConverter(expectedType); if (converter != null && converter.CanConvertFrom(expectedValue.GetType())) { expectedValue = converter.ConvertFrom(expectedValue); } else { converter = TypeDescriptor.GetConverter(expectedValue.GetType()); if (converter != null && converter.CanConvertTo(expectedType)) { expectedValue = converter.ConvertTo(expectedValue, expectedType); } } return Equals(expectedValue, currentValue); } return (currentValue == null); } private static object EvaluateBinding(object source, PropertyPath path) { var binding = new Binding(); binding.Source = source; binding.Path = path; // This is a bit fugly, but it works :) var obj = new FrameworkElement(); BindingOperations.SetBinding(obj, FrameworkElement.DataContextProperty, binding); var value = obj.DataContext; BindingOperations.ClearAllBindings(obj); return value; } } }