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;
}
}
}