当AutoGenerateColumns = True时,如何重命名DataGrid列?

19

我有一个简单的数据结构类:

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

我正在将其输入到 DataGrid 中:

this.data_grid_clients.ItemSource = this.clients;

我想要更改列标题。例如:claim_number 变成 "Claim Number"。我知道当你手动创建列时可以这样做:

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

然而,在自动生成列时,Columns属性为空。是否有一种方法可以重命名列,还是必须手动生成列?


如果您需要进行的唯一自定义是标题重命名,并且不怕额外的事件处理程序 - Ekk的答案是正确的选择。 但是,如果有任何其他自定义的可能性(例如单元格模板、要显示的列),最好明确指定列,因为如果出现类似的要求,您将被迫重新编写代码或添加类似的结构以编辑/重新创建自动生成的列。 - Nogard
谢谢,这很有道理。考虑到我可能需要进行一些自定义,我可能会手动添加每一列。 - jmulmer
7个回答

40

您可以使用DisplayNameAttribute并更新代码的一部分来实现您想要的功能。

第一件事是在Client类的属性中添加[DisplayName("")]

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

    [DisplayName("Clain Number")]
    public String claim_number {set; get;}
}
更新您的XAML代码,向AutoGenerationColumn事件添加事件处理程序。
<dg:DataGrid AutoGenerateColumns="True" AutoGeneratingColumn="OnAutoGeneratingColumn">
</dg:DataGrid>

最后,在代码后端添加一个方法。

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

谢谢,解决了!我很感激你写的清晰易懂的代码,这让阅读变得更加容易。 - jmulmer
这就是它!允许在应用程序中重复使用相同的窗口/数据网格。 - AndrewK
尽管你的代码运行得很好,但我必须严格地给你点个踩,因为你的代码很丑陋,而且在可以使用显式声明的情况下,你错误地使用了“var”关键字。 - Krythic

22

好的回答

您可以在 AutoGeneratingColumn 事件中修改自动生成的 DataGridColumn 标头的 Header,在该事件中,您可以访问 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;
    }
}

3
这应该是最佳答案! - Daltons
@Wolle 在我的情况下,错误提示说 DataGridAutoGeneratingColumnEventArgs 不包含 PropertyDescriptor。我正在使用来自 using Microsoft.Toolkit.Uwp.UI.Controls;DataGridAutoGeneratingColumnEventArgs - Siri

4

MVVM解答

为了使其符合MVVM模式并避免使用可怕的代码后台,您可以使用来自Sytem.Windows.Interactivity的自定义行为(Expression Blend SDK中的一部分,在nuget上找到)。您还需要在创建行为的项目中添加Windows.Base.dll。

XAML

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

行为类

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) 是非常有用的,而且不必在与视图相同的项目中定义它们,这意味着您可以创建一个行为库,然后在多个应用程序中使用它们。


2
很好的行为使用,加一。这是一个被低估的答案。顺便说一下,与其使用Expression Blend SDK,最好使用开源的Microsoft.Xaml.Behaviors.Wpf包。它更加更新。 - Xam
1
以前不知道这个存在,从现在开始我会使用它。稍后会更新答案。 - Rob Husband

3
你可以使用AutoGeneratingColumns事件。
private void dataGridAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
  if (e.PropertyName.StartsWith("MyColumn")
    e.Column.Header = "Anything I Want";
}

这实际上并没有回答原帖的问题。 - ˈvɔlə

1

简短回答

您可以在AutoGeneratingColumn事件中修改自动生成的DataGridColumn标题的Header

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

0

我将Ekk的答案重构为更短且与Resharper兼容的解决方案:

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

-1

生成列标题的另一种方法

在附加到OnAutoGeneratingColumn方法的上下文中,除了其他人已经提到的内容外;我发现以下方法很有用。

它将确保使用视图模型属性中的DisplayName属性,就像其他属性一样。但是,如果没有设置该名称,则会使用正则表达式获取Pascal命名方式并将其转换为漂亮的列标题。

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

 }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接