Search code examples
c#wpfdatagriddatatemplateselector

WPF DataGrid CellTemplateSelector Item


I have a grid bound to a collection of VMs. When using DataTemplateSelector for my DataGridTemplateColumn I'm getting the whole VM as a data item, how do I narrow it down to a specific property value (otherwise I have to create 'DataTemplateSelector' for each VM or use interfaces, with both are too cumbersome) ?

Saw Bind a property to DataTemplateSelector, but it looks like a nasty workaround.


Solution

  • You can use Expressions Trees in a DataTemplateSelector's derived class, that I called PropertyTemplateSelector. Here its code:

    public abstract class PropertyTemplateSelector : DataTemplateSelector
    {
        private Delegate getPropertyValue;
        private string propertyName;
        private Type itemType;
    
        public string PropertyName
        {
            get
            {
                return propertyName;
            }
            set
            {
                propertyName = value;
            }
        }
    
        public Type ItemType
        {
            get
            {
                return itemType;
            }
            set
            {
                itemType = value;
            }
        }
    
        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            if (ItemType.IsInstanceOfType(item))
            {
                if (getPropertyValue == null)
                {
                    System.Linq.Expressions.ParameterExpression instanceParameter =
                        System.Linq.Expressions.Expression.Parameter(item.GetType(), "p");
    
                    System.Linq.Expressions.MemberExpression currentExpression =
                        System.Linq.Expressions.Expression.PropertyOrField(instanceParameter, PropertyName);
    
                    System.Linq.Expressions.LambdaExpression lambdaExpression =
                        System.Linq.Expressions.Expression.Lambda(currentExpression, instanceParameter);
    
                    getPropertyValue = lambdaExpression.Compile();
                }
    
                return SelectTemplateImpl(getPropertyValue.DynamicInvoke(item), container);
            }
    
            return base.SelectTemplate(item, container);
        }
    
        protected abstract DataTemplate SelectTemplateImpl(object propertyValue, DependencyObject container);
    }
    

    You can extend this class with your own logic, just by implementing the SelectTemplateImpl method. As you can see the PropertyTemplateSelector narrows the item object down to a specific property value (which is passed to the SelectTemplateImpl method). For example I created a NameTemplateSelector in this way:

    public class NameTemplateSelector : PropertyTemplateSelector
    {
        protected override DataTemplate SelectTemplateImpl(object propertyValue, DependencyObject container)
        {
            string name = (string)propertyValue;
    
            if (name != null && name.StartsWith("A", StringComparison.OrdinalIgnoreCase))
            {
                return (DataTemplate)App.Current.MainWindow.FindResource("VipName");
            }
    
            return (DataTemplate)App.Current.MainWindow.FindResource("NormalName");
        }
    }
    

    The you can easly use these template selectors in your XAML

    <Window.Resources>
        <DataTemplate x:Key="NormalName">
            <TextBlock Text="{Binding Mode=OneWay, Path=Name}" Margin="3" />
        </DataTemplate>
        <DataTemplate x:Key="VipName">
            <TextBlock Text="{Binding Mode=OneWay, Path=Name}" Margin="3" Foreground="Red" FontWeight="Bold" />
        </DataTemplate>
    
        <DataTemplate x:Key="NormalSurname">
            <TextBlock Text="{Binding Mode=OneWay, Path=Surname}" Margin="3" />
        </DataTemplate>
        <DataTemplate x:Key="VipSurname">
            <TextBlock Text="{Binding Mode=OneWay, Path=Surname}" Margin="3" Foreground="Green" FontStyle="Italic" />
        </DataTemplate>
    
        <local:NameTemplateSelector x:Key="NameTemplateSelector" PropertyName="Name" ItemType="{x:Type local:Person}" />
        <local:SurnameTemplateSelector x:Key="SurnameTemplateSelector" PropertyName="Surname" ItemType="{x:Type local:Person}" />
    </Window.Resources>
    
    <Grid>
        <DataGrid ItemsSource="{Binding Path=People, Mode=OneWay}" AutoGenerateColumns="False"
                    CanUserAddRows="False">
            <DataGrid.Columns>
                <DataGridTemplateColumn Header="Name"
                                        CellTemplateSelector="{StaticResource NameTemplateSelector}" />
                <DataGridTemplateColumn Header="Surname"
                                        CellTemplateSelector="{StaticResource SurnameTemplateSelector}" />
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
    

    I hope it can help you.