How do you rename DataGrid columns when AutoGenerateColumns = True?
Asked Answered
S

7

20

I have a simple data structure class:

public class Client {
    public String name {set; get;}
    public String claim_number {set; get;}
}

Which I am feeding into a DataGrid:

this.data_grid_clients.ItemSource = this.clients;

I would like to change the column headings. Ie: claim_number to "Claim Number". I know this can be done when you manually create the columns by doing something like:

this.data_grid_clients.Columns[0].Header = "Claim Number"

However, the Columns property is empty when auto-generating the columns. Is there a way to rename the columns, or do I have to manually generate the columns?

Seitz answered 27/11, 2012 at 7:5 Comment(2)
If the only customization you need to make is header renaming and you are not afraid of extra event handlers - Ekk's answer is the way to go. But if there is even a little posibility to customize anything else (cell template, columns to show) it is better to explicitely state columns as lately, if similar requirement will raise, you will be forced either to rewrite the code or to add similar constructions to edit/re-create auto-generated columnsAdhere
Thanks, that makes sense. Given that I might do a little more customization, I might just manually add each column.Seitz
C
42

You can use DisplayNameAttribute and update some part of your code to achieve what you want.

The first thing you have to do is, adding a [DisplayName("")] to properties in the Client class.

public class Client {
    [DisplayName("Column Name 1")]
    public String name {set; get;}

    [DisplayName("Clain Number")]
    public String claim_number {set; get;}
}

The update you xaml code, add an event handler to AutoGenerationColumn event.

<dg:DataGrid AutoGenerateColumns="True" AutoGeneratingColumn="OnAutoGeneratingColumn">
</dg:DataGrid>

Finally, add a method to the code-behind.

private void OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
    var displayName = GetPropertyDisplayName(e.PropertyDescriptor);

    if (!string.IsNullOrEmpty(displayName))
    {
        e.Column.Header = displayName;
    }

}

public static string GetPropertyDisplayName(object descriptor)
{
    var pd = descriptor as PropertyDescriptor;

    if (pd != null)
    {
        // Check for DisplayName attribute and set the column header accordingly
        var displayName = pd.Attributes[typeof(DisplayNameAttribute)] as DisplayNameAttribute;

        if (displayName != null && displayName != DisplayNameAttribute.Default)
        {
            return displayName.DisplayName;
        }

    }
    else
    {
        var pi = descriptor as PropertyInfo;

        if (pi != null)
        {
            // Check for DisplayName attribute and set the column header accordingly
            Object[] attributes = pi.GetCustomAttributes(typeof(DisplayNameAttribute), true);
            for (int i = 0; i < attributes.Length; ++i)
            {
                var displayName = attributes[i] as DisplayNameAttribute;
                if (displayName != null && displayName != DisplayNameAttribute.Default)
                {
                    return displayName.DisplayName;
                }
            }
        }
    }

    return null;
}
Cordeiro answered 27/11, 2012 at 7:31 Comment(3)
Thanks, that did it! I appreciate the clean code - that makes reading it much easier.Seitz
This is it! Allows for reuse of the same window/datagrid across an application.Hogen
@Krythic There is nothing wrong with his use of "var". In fact, every single time he uses var, the type is clear (for example "as PropertyInfo"). Microsoft themselves state that "var" is fine if the type is clear from the code. As for the code being "ugly", that is subjective at best.Wastebasket
P
24

The nice answer

You can modify the Header of the auto generated DataGridColumn header in the AutoGeneratingColumn event, where you can access the DisplayNameAttribute

Client.cs

public class Client
{
    [DisplayName("Name")]
    public String name { set; get; }

    [DisplayName("Claim Number")]
    public String claim_number { set; get; }
}

.xaml

<DataGrid ItemSource="{Binding Clients}"
          AutoGenerateColumns="True"
          AutoGeneratingColumn="DataGrid_AutoGeneratingColumn" />

.xaml.cs

v1

// This snippet can be used if you can be sure that every
// member will be decorated with a [DisplayNameAttribute]
private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
    => e.Column.Header = ((PropertyDescriptor)e.PropertyDescriptor)?.DisplayName ?? e.Column.Heaader;

v2

// This snippet is much safer in terms of preventing unwanted
// Exceptions because of missing [DisplayNameAttribute].
private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
    if (e.PropertyDescriptor is PropertyDescriptor descriptor)
    {
        e.Column.Header = descriptor.DisplayName ?? descriptor.Name;
    }
}
Psychiatrist answered 5/1, 2018 at 22:44 Comment(2)
This should be top answer!!Ethno
@Wolle In my case, the error occurs saying that DataGridAutoGeneratingColumnEventArgs does not contain PropertyDescriptor. I'm using DataGridAutoGeneratingColumnEventArgs from using Microsoft.Toolkit.Uwp.UI.Controls;.Deplorable
A
4

MVVM answer

In order to keep this consistent with the MVVM pattern and avoid using the horrible code-behind, you can use custom behaviors from Sytem.Windows.Interactivity (part of the Expression Blend SDK found on nuget). You also need Windows.Base.dll in the project where you create the behaviors.

XAML

<DataGrid AutoGenerateColumns="True">
    <i:Interaction.Behaviors>
        <behaviours:ColumnHeaderBehaviour/>
    </i:Interaction.Behaviors>
</DataGrid>

Behaviour Class

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)
        {
            eventArgs.Column.Header = descriptor.DisplayName ?? descriptor.Name;
        }
        else
        {
            eventArgs.Cancel = true;
        }
    }
}

Behaviors are really useful and don't have to be defined in the same project as your views meaning that you can create a library of behaviors and use them in many applications.

Astral answered 2/9, 2019 at 8:13 Comment(2)
Nice use of behaviors, +1. Such an underrated answer. Btw, instead of using Expression Blend SDK, it's better to use the open source Microsoft.Xaml.Behaviors.Wpf package. It's more up to date.Turnkey
Didn't know that existed, I use use that from now on. Will update the answer later.Astral
C
3

You can use the AutoGeneratingColumns event.

private void dataGridAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
  if (e.PropertyName.StartsWith("MyColumn")
    e.Column.Header = "Anything I Want";
}
Cotinga answered 27/11, 2012 at 7:29 Comment(1)
This actually does not answer the OP question.Turley
P
1

The short answer

You can modify the Header of the auto generated DataGridColumn header in the AutoGeneratingColumn event.

.xaml

<DataGrid ItemSource="{Binding Clients}"
          AutoGenerateColumns="True"
          AutoGeneratingColumn="DataGrid_AutoGeneratingColumn" />

.xaml.cs

private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
    switch (e.Name)
    {
        case nameof(Client.name):
            e.Column.Header = "Name";
            break;

        case nameof(Client.claim_number):
            e.Column.Header = "Claim Number";
            break;
    }
}
Psychiatrist answered 5/1, 2018 at 23:2 Comment(0)
J
0

I refactored the answer of Ekk to a shorter and Resharper compatible solution:

public static string GetPropertyDisplayName(object descriptor)
{
   var propertyDescriptor = descriptor as PropertyDescriptor;
   if (propertyDescriptor != null)
   {
      var displayName = propertyDescriptor.Attributes[typeof(DisplayNameAttribute)] as DisplayNameAttribute;
      if (displayName != null && !Equals(displayName, DisplayNameAttribute.Default))
      {
         return displayName.DisplayName;
      }
   }
   else
   {
      var propertyInfo = descriptor as PropertyInfo;
      if (propertyInfo != null)
      {
         var attributes = propertyInfo.GetCustomAttributes(typeof(DisplayNameAttribute), true);
         foreach (object attribute in attributes)
         {
            var displayName = attribute as DisplayNameAttribute;
            if (displayName != null && !Equals(displayName, DisplayNameAttribute.Default))
            {
               return displayName.DisplayName;
            }
         }
      }
   }
   return null;
}
Jenness answered 1/2, 2017 at 10:42 Comment(0)
P
-1

Another Method of Generating Column Headers

To add upon what others have stated in the context of attaching to the OnAutoGeneratingColumn method; I find the following approach useful.

It will assure that it uses the DisplayName attribute from a view model property just like the others, but if that name is not set it will use regular expressions to take a Pascal cased name and turn it into a nice column header.

 //Note that I cleaned this up after I pasted it into the Stackoverflow Window, I don't
 //think I caused any compilation errors but you have been warned.
 private void InvoiceDetails_OnAutoGeneratingColumn(object sender,
                                                    DataGridAutoGeneratingColumnEventArgs e)
 {
     if (!(e.PropertyDescriptor is PropertyDescriptor descriptor)) return;

     //You cannot just use descriptor.DisplayName because it provides you a value regardless to it 
     // being manually set.  This approach only uses a DisplayName that was manually set on a view
     // model property.  
     if (descriptor.Attributes[typeof(DisplayNameAttribute)] 
                    is DisplayNameAttribute displayNameAttr
                    && !string.IsNullOrEmpty(displayNameAttr.DisplayName))
     {
         e.Column.Header = displayNameAttr.DisplayName;
         return;
     }

     //If you only wanted to display columns that had DisplayName set you could collapse the ones
     // that didn't with this line.
     //e.Column.Visibility = Visibility.Collapsed;
     //return;

     //This alternative approach uses regular expressions and does not require 
     //DisplayName be manually set.  It will Breakup Pascal named variables 
     //"NamedLikeThis" into nice column headers that are "Named Like This".
     e.Column.Header = Regex.Replace(descriptor.Name,
             @"((?<=[A-Z])([A-Z])(?=[a-z]))|((?<=[a-z]+)([A-Z]))",
             @" $0",
             RegexOptions.Compiled)
            .Trim();

 }
Phonate answered 25/5, 2020 at 4:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.