Вопрос

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?

Это было полезно?

Решение

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

Другие советы

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

You can use the AutoGeneratingColumns event.

private void dataGridAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
  if (e.PropertyName.StartsWith("MyColumn")
    e.Column.Header = "Anything I Want";
}

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.

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

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

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();

 }
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top