将WPF DataGrid绑定到List

你好,我有一个DataGrid,我有不同的报告,我想显示。 我要改变课程,所以他们在这里更短,但是想法是一样的。

让我们说我有一个名为IReports的接口

public interface IReports { } 

还有三个class叫做学生,class级,汽车

 public class Students:IReports { public string Name { get; set; } } public class Classes : IReports { public string ClassName { get; set; } public string StudentName { get; set; } } public class Cars : IReports { public int Mileage { get; set; } public string CarType { get; set; } public string StudentName { get; set; } } 

列表

 private List _reportsTable; public List ReportsTable { get { return _reportsTable; } set { SetProperty(ref (_reportsTable), value); } } 

DataGrid

  

好的,所以重要的是它们都有不同的属性名称,有些还有更少的属性。 如何绑定DataGrid以查看不同的属性? 这是MVVM,如果这有任何区别。

更新:这将始终只使用其中一个类。但是当有人更改combobox时,它将触发一个将填充IList的事件。

在此处输入图像描述

这将始终只使用其中一个类 。 但当有人更改combobox时,它将触发一个填充IList 的事件。

我理解上面的方式是你永远不会在列表中混合不同的元素(即它只包含ClassesStudentsCars )。 所有其他答案都假设列表包含混合内容,但如果这是真的,则DataGrid根本不是此类内容的正确演示者。

如果上述假设是正确的,那么唯一的问题是如何用单个可绑定属性表示不同的列表。 从数据绑定概述中可以看出,在处理集合时,数据绑定并不真正关心它们是否是通用的。 可识别的源类型是非通用的IEnumerableIListIBindingList 。 但是,集合视图实现使用一些规则来确定集合的元素类型 ,方法是通过查找实际数据源类实现的IEnumerable接口的generics类型参数,检查第一个可用项或获取信息来自ITypedList实现等。所有规则及其优先级都可以在参考源中看到。

考虑到所有这些,一种可能的解决方案是更改ReportsTable属性类型以允许分配ListListList 。 任何公共类/接口都可以工作(记住,数据绑定将检查GetType()返回的实际类型),如objectIEnumerableIListIEnumerable等,所以我将选择最接近的协变类型List这是IReadOnlyList

 private IReadOnlyList _reportsTable; public IReadOnlyList ReportsTable { get { return _reportsTable; } set { SetProperty(ref (_reportsTable), value); } } 

现在当你这样做

 viewModel.ReportsTable = new List { new Students { Name = "A" }, new Students { Name = "B" }, new Students { Name = "C" }, new Students { Name = "D" }, }; 

你得到

在此处输入图像描述

与此同时

 viewModel.ReportsTable = new List { new Classes { ClassName = "A", StudentName = "A" }, new Classes { ClassName = "A", StudentName ="B" }, new Classes { ClassName = "B", StudentName = "C" }, new Classes { ClassName = "B", StudentName = "D" }, }; 

表明

在此处输入图像描述

最后这个

 viewModel.ReportsTable = new List { new Cars { Mileage = 100, CarType = "BMW", StudentName = "A" }, new Cars { Mileage = 200, CarType = "BMW", StudentName = "B" }, new Cars { Mileage = 300, CarType = "BMW", StudentName = "C" }, new Cars { Mileage = 400, CarType = "BMW", StudentName = "D" }, }; 

结果是

在此处输入图像描述

更新:以上内容需要修改模型以返回具体的List实例。 如果你想保持模型不变(即返回List ),那么你需要一个不同的解决方案,这次使用ITypedList 。 为此,我们将使用System.Collections.ObjectModel.Collection 基类创建一个简单的列表包装器:

 public class ReportsList : Collection, ITypedList { public ReportsList(IList source) : base(source) { } public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors) { return TypeDescriptor.GetProperties(Count > 0 ? this[0].GetType() : typeof(IReports)); } public string GetListName(PropertyDescriptor[] listAccessors) { return null; } } 

然后将bindable属性更改为

 private IList _reportsTable; public IList ReportsTable { get { return _reportsTable; } set { SetProperty(ref _reportsTable, value as ReportsList ?? new ReportsList(value)); } } 

你完成了

据我所知,您希望数据网格显示实现接口的各种类的各个列。 如果挂钩DataGrid的LoadingRow事件,您可以看到在运行时处理的对象类型。 您可以使用reflection从行的datacontext中获取属性,然后检查datagrid以查看该属性是否有列。 如果没有,请添加它。

如果列表中有不同的类型,并且类型没有另一种类型的属性(例如,Cars没有Name属性,并且Student和Cars都在列表中),则会出现问题。 如果编辑对象上不存在的属性的列,则会抛出exception。 要解决这个问题,您需要一个将其应用于datagridcells的转换器和样式。 为了好玩,我还添加了一个数据触发器,如果​​禁用它,则将单元格的背景更改为Silver。 一个问题是如果你需要更改单元格的样式,那么你必须在代码中执行它(或者根据你的样式更改代码中的样式)。

XAML:

  

CS

 private void DataGrid_LoadingRow(object sender, DataGridRowEventArgs e) { var dg = sender as DataGrid; var pis = e.Row.DataContext.GetType().GetProperties(); foreach (var pi in pis) { // Check if this property already has a column in the datagrid string name = pi.Name; var q = dg.Columns.Where(_ => _.SortMemberPath == name); if (!q.Any()) { // No column matches, so add one DataGridTextColumn c = new DataGridTextColumn(); c.Header = name; c.SortMemberPath = name; System.Windows.Data.Binding b = new Binding(name); c.Binding = b; // All columns don't apply to all items in the list // So, we need to disable the cells that aren't applicable // We'll use a converter on the IsEnabled property of the cell b = new Binding(); b.Converter = new ReadOnlyConverter(); b.ConverterParameter = name; // Can't apply it directly, so we have to make a style that applies it Style s = new Style(typeof(DataGridCell)); s.Setters.Add(new Setter(DataGridCell.IsEnabledProperty, b)); // Add a trigger to the style to color the background when disabled var dt = new DataTrigger() { Binding = b, Value = false }; dt.Setters.Add(new Setter(DataGridCell.BackgroundProperty, Brushes.Silver)); s.Triggers.Add(dt); c.CellStyle = s; // Add the column to the datagrid dg.Columns.Add(c); } } } 

CS转换器:

 public class ReadOnlyConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value != null) { var prop = value.GetType().GetProperty(parameter as string); if (prop != null) return true; } return false; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } 

而且,为了完成,这是我用于设置屏幕截图数据的内容:

 public List ReportsTable { get; set; } public MainWindow() { InitializeComponent(); ReportsTable = new List() { new Students() { Name = "Student 1" }, new Students() { Name = "Student 2" }, new Classes() { ClassName="CS 101", StudentName = "Student 3" }, new Cars() { CarType = "Truck", Mileage=12345, StudentName = "Student 4" } }; this.DataContext = this; } 

截图:

在此处输入图像描述

为什么不在基本接口中添加一个getter,而不是显示给定字符串值的转换器选项。 然后,每个类只返回它自己的,几乎就像每个对象都可以覆盖它的“ToString()”方法因为你要创建一个列表,例如用于显示或拾取,无论如何该值都是只读的,使它只是一个吸气剂…

 public interface IReports { string ShowValue {get;} } public class Students:IReports { public string Name { get; set; } public string ShowValue { get { return Name; } } } public class Classes : IReports { public string ClassName { get; set; } public string StudentName { get; set; } public string ShowValue { get { return ClassName + " - " + StudentName ; } } } public class Cars : IReports { public int Mileage { get; set; } public string CarType { get; set; } public string StudentName { get; set; } public string ShowValue { get { return CarType + "(" + Mileage + ") - " + StudentName; } } } 

然后在你的视图模型管理器…

 public class YourMVVMClass { public YourMVVMClass() { SelectedRptRow = null; ReportsTable = new List() { new Students() { Name = "Student 1" }, new Students() { Name = "Student 2" }, new Classes() { ClassName="CS 101", StudentName = "Student 3" }, new Cars() { CarType = "Truck", Mileage=12345, StudentName = "Student 4" } }; } // This get/set for binding your data grid to public List ReportsTable { get; set; } // This for the Selected Row the data grid binds to public IReports SelectedRptRow { get; set; } // This for a user double-clicking to select an entry from private void Control_OnMouseDoubleClick(object sender, MouseButtonEventArgs e) { // Now, you can look directly at the SelectedRptRow // as in the data-grid binding declaration. if (SelectedRptRow is Classes) MessageBox.Show("User selected a class item"); else if( SelectedRptRow is Cars) MessageBox.Show("User selected a car item"); else if( SelectedRptRow is Students) MessageBox.Show("User selected a student item"); else MessageBox.Show("No entry selected"); } } 

最后在您的表单/视图中

       

结果绑定并从datagrid双击

使用转换器的其他答案只是另一条路径,但这对我来说更容易,因为您可以根据需要更改每个单独的类并进行扩展/调整。 暴露的“ShowValue”getter对于“IReports”的所有实例都是通用的,因此绑定是直接的,无需通过转换器。 如果你删除一个类,或将来扩展,你的底层都是自包含的。

现在不要误解我的意思,我确实使用转换器,通常使用布尔类型字段来分别显示,隐藏,折叠控件。 这很好,因为我有不同的布尔转换器,如

 BoolToVisibleHidden = if True, make visible vs Hidden BoolToHiddenVisible = if True, make Hidden vs Visible BoolToVisibleCollapse = if True, make visible vs Collapsed BoolToCollapseVisible = if True, make Collapsed vs visible. 

因此,在我的MVVM上有一个布尔属性,我可以显示和隐藏不同的控件……也许比如管理员和标准用户选项。

我还使用转换器处理日期以进行替代格式化。

你可以滥用IValueConverter 。 为每列创建一个。
ValueConverter您可以测试类型并返回正确的属性。 我的意思的一个例子:

 public class NameValueConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is Students) { return (value as Students).Name; } if (value is Classes) { return (value as Classes).ClassName; } if (value is Cars) { return (value as Cars).CarType; } return ""; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } 

要使用它,请将其作为资源添加到DataGrid:

    

并在绑定中指定它,如下所示:

 {Binding Path=., Converter={StaticResource NameValueConverter}} 

此解决方案仅适用于只读DataGrids(编辑抛出NotImplementedException)。