【问题标题】:Can I use a List<T> and a List<Expression> to populate a wpf datagrid?我可以使用 List<T> 和 List<Expression> 来填充 wpf 数据网格吗?
【发布时间】:2017-02-27 23:42:49
【问题描述】:

我可以使用 List&lt;T&gt;List&lt;Expression&gt; 来填充 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 ) ); 

但我不确定如何实现。

【问题讨论】:

标签: c# wpf


【解决方案1】:

您想提供一组表达式并使用每个表达式在网格中创建自己的列。您必须解决一个主要问题:使用数据绑定到对象的属性来解析 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};

注意:其中大部分来自sample code,伴随着MSDN article I've written about expression trees。示例代码处理额外的表达式类型、长路径链(例如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));

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-10-06
    • 2018-09-16
    • 1970-01-01
    • 1970-01-01
    • 2013-10-05
    • 1970-01-01
    • 2014-03-13
    相关资源
    最近更新 更多