Click here to Skip to main content
15,881,882 members
Articles / Desktop Programming / WPF
Tip/Trick

How to Auto-generate Multiple Custom Columns in a WPF DataGrid for Same Type Properties

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
1 Apr 2020CPOL2 min read 12.5K   124   6   8
A generic workaround for auto-generating multiple custom columns in a WPF DataGrid in case where the data class has multiple properties of the same type
Auto-generating multiple custom columns in a WPF DataGrid in case where the data class has multiple properties of the same type doesn’t work like one would have expected because the custom control’s data context is not the property one intends to bind to, but the containing instance. So, the custom control or its view model doesn't know to which of the same-type properties to bind.

Introduction

There is an issue when auto-generating multiple custom columns in a WPF DataGrid in case where the data class has multiple properties of the same type (like Item here below).

C#
public class Item
{
   public Enum Prop0 { get; set; } = Enum.CustomEnum1;
   public Enum Prop1 { get; set; } = Enum.CustomEnum2;
}

The reason why it doesn’t work like one would have expected is that the custom control’s data context is not the property one intends to bind to (Prop0 or Prop1) but their containing instance (an instance of Item in our case). So, if you auto-generate custom columns, you cannot tell the custom control or its view model which of the two properties to bind to.

Workaround

A workaround I'd suggest, consists in making a facade for the data class, instances of which you intend(ed) to bind to in the data grid. The facade class would basically replicate the initial data class with the exception that the properties of the same type, which require custom date cells, would have individual types in the containing facade:

C#
public class ItemFacade
{
    private readonly Item item;
    public ItemFacade(Item item) => this.item = item;

    public EnumContainer0 Prop0 
    {
        get => new EnumContainer0 { Enum = this.item.Prop0 };
        set => this.item.Prop0 = value.Enum;
    }
    public EnumContainer1 Prop1
    {
        get => new EnumContainer1 { Enum = this.item.Prop1 };
        set => this.item.Prop1 = value.Enum;
    }
}

These individual specific types can either be sub-classes of the original property's type in question, if it is possible, or be sub-classes of a facade type thereof:

C#
public class EnumContainer
{
    public Enum Enum { get; set; }
}
public class EnumContainer0 : EnumContainer { }
public class EnumContainer1 : EnumContainer { }

You can even consider implicit cast in that case :).

The related XAML code will then look as below:

XML
Window x:Class="TestWpfDataGrid.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TestWpfDataGrid"
        xmlns:i="clr-namespace:System.Windows.Interactivity;
                 assembly=System.Windows.Interactivity" 
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <DataGrid ItemsSource="{Binding Items}">
            <i:Interaction.Behaviors>
                <local:ColumnHeaderBehaviour/>
            </i:Interaction.Behaviors>
                <DataGrid.Resources>
                    <DataTemplate DataType="{x:Type local:EnumContainer0}">
                    <StackPanel>
                        <TextBlock Text="Custom "/>
                        <TextBlock Text="{Binding Prop0.Enum}"/>
                    </StackPanel>
                </DataTemplate>
                    <DataTemplate DataType="{x:Type local:EnumContainer1}">
                    <StackPanel>
                        <TextBlock Text="Custom "/> 
                        <TextBlock Text="{Binding Prop1.Enum}"/>
                    </StackPanel> 
                    </DataTemplate>
                </DataGrid.Resources>
        </DataGrid>
    </Grid>
</Window>

Note that you must bind to individual properties in the data templates, which is now possible as the individual properties have their own types and thus can have individual data templates.

The attached behavior in local:ColumnHeaderBehaviour can be as follows:

C#
public class ColumnHeaderBehaviour : Behavior<DataGrid>
    {
        protected override void OnAttached()
        {
            AssociatedObject.AutoGeneratingColumn += OnGeneratingColumn;
        }

        protected override void OnDetaching()
        {
            AssociatedObject.AutoGeneratingColumn -= OnGeneratingColumn;
        }

        private static void OnGeneratingColumn(object sender,
                                               DataGridAutoGeneratingColumnEventArgs eventArgs)
        {
            if (eventArgs.PropertyDescriptor is PropertyDescriptor descriptor)
            {
                var control = (DataGrid)sender;
                var resourceDictionary = control.Resources;
                var dataTemplate = resourceDictionary.Values
                    .OfType<DataTemplate>()
                    .Where(el => (Type)el.DataType == descriptor.PropertyType)
                    .FirstOrDefault();

                if (dataTemplate != null)
                {
                    var column = new DataGridTemplateColumn()
                    {
                        CellTemplate = dataTemplate,
                    };
                    eventArgs.Column = column;
                }
                eventArgs.Column.Header = descriptor.DisplayName ?? descriptor.Name;
            }
            else
            {
                eventArgs.Cancel = true;
            }
        }
    }

Note that the above behavior code works if the data templates are specified within the related data grid.

This facade class is fully testable. Typing it down and the tests thereof would certainly cost me less time than I've spent when searching for a solution.

Conclusion

A simple facade for the data object, just to have its properties typed differently, won't be sufficient. It's all about custom data cells, isn't it? So, there will be a control in the custom data cell, that will display and probably change the related property. So, it should also be aware if the property changes. In this way, we come to an idea that:

  • the data object facade should be a view model, and
  • the properties' facades should be view models too.

Moreover, it would be more secure to always use a view model as a data grid context.

History

  • 1st April, 2020: Initial version

License

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


Written By
Software Developer (Senior) Vassili Kravtchenko-Berejnoi Technical Computing
Austria Austria
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionError found in MainWindow.xaml markup Pin
seenItDoneIt2-Apr-20 10:20
seenItDoneIt2-Apr-20 10:20 
AnswerRe: Error found in MainWindow.xaml markup Pin
Vassili Kravtchenko-Berejnoi2-Apr-20 22:35
professionalVassili Kravtchenko-Berejnoi2-Apr-20 22:35 
GeneralRe: Error found in MainWindow.xaml markup Pin
Vassili Kravtchenko-Berejnoi2-Apr-20 22:44
professionalVassili Kravtchenko-Berejnoi2-Apr-20 22:44 
GeneralRe: Error found in MainWindow.xaml markup Pin
seenItDoneIt4-Apr-20 0:04
seenItDoneIt4-Apr-20 0:04 
GeneralRe: Error found in MainWindow.xaml markup Pin
Vassili Kravtchenko-Berejnoi4-Apr-20 0:17
professionalVassili Kravtchenko-Berejnoi4-Apr-20 0:17 
GeneralRe: Error found in MainWindow.xaml markup Pin
seenItDoneIt4-Apr-20 0:41
seenItDoneIt4-Apr-20 0:41 
QuestionMore details Pin
abolfazl pourmohammad1-Apr-20 5:24
abolfazl pourmohammad1-Apr-20 5:24 
QuestionMore details Pin
abolfazl pourmohammad1-Apr-20 5:23
abolfazl pourmohammad1-Apr-20 5:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.