我可以使用列表吗?和一个列表填充WPF数据网格

本文关键字:列表 Expression 填充 数据 网格 数据网 一个 WPF 可以使 我可以 | 更新日期: 2023-09-27 18:13:39

我可以使用List<T>List<Expression>来填充wpf数据网格吗?

  {
      var processes = Process.GetProcesses().ToList();
      PopulateDataGrid( processes, x => x.ProcessName, x => GetSafeFilename( x ) );  
  }
  private string GetSafeFilename( Process p )
  {
     try
     {
        return p.MainModule.FileName;
     }
     catch ( Exception )
     {
        return "";
     }
  }

这个想法是,我希望能够传递表达式的列表和参数列表来填充数据网格。我只想显示数据网格上的表达式列表。

我还希望能够获得所选行的底层对象。

我知道我可以使用匿名类型,如:

var list = processes.Select( x => new {TagObject = x, ProcessName = x.ProcessName, Filename = GetSafeFilename( x )} ).ToList();

但是我必须确保不将"TagObject"添加到数据网格中。

任何想法?我非常喜欢这个语法:

PopulateDataGrid( processes, x => x.ProcessName, x => GetSafeFilename( x ) ); 

但我不知道如何让它发生。

我可以使用列表吗?和一个列表<Expression>填充WPF数据网格

您希望提供一组表达式,并使用每个表达式在网格中创建自己的列。有一个主要问题需要解决:DataGrid列是使用对象属性的数据绑定来解决的:

<DataGridTextColumn Header="ProcessName" Binding="{Binding ProcessName}" />

,因此我们可以使用适当的绑定将属性访问表达式映射到DataGrid列。但是你的第二列不代表一个属性访问,而是一个方法调用;并且不能将datagridcolumn的绑定设置为方法调用:

<!-- won't work -->
<DataGridTextColumn Header="GetSafeFilenamee" Binding="{Binding GetSafeFilename}" />

在这种情况下,因为该方法的目的是处理在试图访问MainModule上的详细信息时可能出现的异常;我们可以通过属性访问和使用WPF的目标回退机制来避免异常。但是,一般的机制可以深入到任意方法的IL中以找出相关的属性访问,这几乎肯定超出了您想要做的范围。


代替PopulateDataGrid接受多个表达式,每个表达式都有自己的属性访问,我建议使用包含多个属性访问的单个表达式。我能想到两个这样的表达:

  • 返回某个数组的表达式

    PopulateDataGrid(processes, x => new [] { x.ProcessName, x.MainModule.FileName });
    
  • 或返回匿名类型的表达式。这还有一个额外的好处,允许您将标题传递给列:

    PopulateDataGrid(processes, x => new { x.ProcessName, Path = x.MainModule.FileName });
    

另外,我建议将其作为DataGrid上的扩展方法公开。签名可以像这样:

public static void PopulateDataGrid<TElement, TFieldsExpression>(this DataGrid dg, IEnumerable<TElement> itemsSource, Expression<Func<TElement, TFieldsExpression>> fieldsExpr) {
}

我们需要TFieldsExpression泛型形参,这样编译器才能将第二个形参识别为表达式。


第一步是将多表达式解析为单独的头和属性访问。您可以使用如下内容:

private static List<(string name, Expression expr)> ParseFields<TElement, TFieldsExpression>(Expression<Func<TElement, TFieldsExpression>> fieldsExpression) {
    var body = fieldsExpression.Body;
    switch (body) {
        // an array initialization with elements
        // (as opposed to an array initialization with bounds -- new int[5])
        case NewArrayExpression newArrayExpr when body.NodeType == ExpressionType.NewArrayInit:
            return newArrayExpr.Expressions.Select(x => ("", x)).ToList();
        // anonymous type
        // the IsAnonymous extension method is included at the end of the post
        case NewExpression newExpr when newExpr.Type.IsAnonymous():
            return newExpr.Constructor.GetParameters().Select(x => x.Name).Zip(newExpr.Arguments).ToList();
        default:
            throw new ArgumentException("Unhandled expression type.");
    }
}

那么你可以写如下方法:

public static void PopulateDataGrid<TElement, TFieldsExpression>(this DataGrid dg, IEnumerable<TElement> itemsSource, Expression<Func<TElement, TFieldsExpression>> fieldsExpr) {
    dg.ItemsSource = itemsSource;
    dg.Columns.Clear();
    var fields = ParseFields(fieldsExpr);
    foreach (var (name, expr) in fields) {
        if (expr is MemberAccessExpression mexpr) {
            dg.Columns.Add(new DataGridTextColumn {
                Header = name,
                Binding = new Binding(mexpr.Member.Name)
            })
        } else {
            throw new ArgumentException("Unhandled expression type.");
        }
    }
}

你可以这样调用这个方法:

dg.PopulateDataGrid(list, x => new [] {x.ProcessName, x.HasExited, x.MachineName};

注意:其中大部分来自我写的关于表达式树的MSDN文章附带的示例代码。示例代码处理其他表达式类型、长路径链(例如x.MainModule.FileName)和对String.Format的方法调用。


辅助扩展:

// using System.Reflection;
public static bool IsAnonymous(this Type type) =>
    type.HasAttribute<CompilerGeneratedAttribute>() && type.Name.Contains("Anonymous") && type.Name.ContainsAny("<>", "VB$");
public static bool HasAttribute<TAttribute>(this MemberInfo mi, bool inherit = false) where TAttribute : Attribute =>
    mi.GetCustomAttributes(typeof(TAttribute), inherit).Any();
public static bool ContainsAny(this string s, params string[] testStrings) =>
   testStrings.Any(x => s.Contains(x));