我可以使用列表吗?和一个列表填充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 ) );
但我不知道如何让它发生。
您希望提供一组表达式,并使用每个表达式在网格中创建自己的列。有一个主要问题需要解决: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));