你好,我有一个DataGrid,我想要显示不同的报告。我要改变课程,所以他们在这里更短,但是想法是一样的。
让我们说我有一个名为IReports的界面
public interface IReports
{
}
和三个叫做学生,班级,汽车的课程
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<IReports> _reportsTable;
public List<IReports> ReportsTable
{
get { return _reportsTable; }
set { SetProperty(ref (_reportsTable), value); }
}
DataGrid
<DataGrid ItemsSource="{Binding ReportsList}"
Grid.Column="1"
Grid.Row="0"
AutoGenerateColumns="True"
Grid.RowSpan="6"/>
好的,所以重要的是它们都有不同的属性名称,而有些则有更少的属性名称。如何绑定DataGrid以查看不同的属性?如果这有任何不同,这就是MVVM。
更新:这将始终只使用其中一个类。但是当有人更改组合框时,它将触发一个将填充IList<IReports>
的事件。
答案 0 :(得分:3)
这将一次只使用其中一个类。但当有人更改组合框时,它会触发一个将填充IList&lt; IReports&gt;的事件。
我理解上述内容的方法是,您永远不要在列表中混合使用不同的元素(即它包含仅 Classes
,Students
或Cars
)。所有其他答案都假设列表包含混合内容,但如果这是真的,则DataGrid
根本不是此类内容的正确演示者。
如果上述假设是正确的,那么唯一的问题是如何用单个可绑定属性表示不同的列表。从Data Binding Overview可以看出,在处理集合时,数据绑定并不关心它们是否是通用的。可识别的源类型是非通用的IEnumerable
,IList
和IBindingList
。但是,集合视图实现使用一些规则来确定集合的元素类型,方法是通过检查实际数据源类实现的IEnumerable<T>
接口的泛型类型参数。第一个可用项目,或从ITypedList实施等获取信息。所有规则及其优先级都可以在Reference Source中看到。
考虑到所有这些,一种可能的解决方案是更改ReportsTable
属性类型以允许分配List<Classes>
或List<Students
或List<Cars>
。任何公共类/接口都可以工作(记住,数据绑定将检查GetType()
返回的实际类型),例如object
,IEnumerable
,IList
,IEnumerable<IReports>
等。,所以我会选择与List<IReports
IReadOnlyList<IReports>
最近的协变类型:
private IReadOnlyList<IReports> _reportsTable;
public IReadOnlyList<IReports> ReportsTable
{
get { return _reportsTable; }
set { SetProperty(ref (_reportsTable), value); }
}
现在当你这样做
viewModel.ReportsTable = new List<Students>
{
new Students { Name = "A" },
new Students { Name = "B" },
new Students { Name = "C" },
new Students { Name = "D" },
};
你得到了
与此同时
viewModel.ReportsTable = new List<Classes>
{
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<Cars>
{
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<T>
实例。如果你想保持模型不变(即返回List<IReports>
),那么你需要一个不同的解决方案,这次使用ITypedList
。为此,我们将使用System.Collections.ObjectModel.Collection<T>基类创建一个简单的列表包装器:
public class ReportsList : Collection<IReports>, ITypedList
{
public ReportsList(IList<IReports> 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<IReports> _reportsTable;
public IList<IReports> ReportsTable
{
get { return _reportsTable; }
set { SetProperty(ref _reportsTable, value as ReportsList ?? new ReportsList(value)); }
}
你已经完成了。
答案 1 :(得分:1)
据我了解,您希望数据网格显示实现接口的各种类的各个列。如果挂钩DataGrid的LoadingRow事件,您可以看到在运行时处理的对象类型。您可以使用反射从行的datacontext中获取属性,然后检查datagrid以查看该属性是否有列。如果没有,请添加它。
如果列表中有不同的类型且类型没有其他类型的属性(例如,汽车没有名称属性且学生和汽车都在列表)。如果编辑对象上不存在的属性的列,则会引发异常。要解决这个问题,您需要一个将其应用于datagridcells的转换器和样式。为了好玩,我还添加了一个数据触发器,如果禁用它,则将单元格的背景更改为Silver。一个问题是如果您需要更改单元格的样式,那么您必须在代码中执行此操作(或者根据您的样式更改代码中的样式)。
XAML:
<DataGrid ItemsSource="{Binding ReportsTable}" AutoGenerateColumns="True" LoadingRow="DataGrid_LoadingRow" />
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<IReports> ReportsTable { get; set; }
public MainWindow()
{
InitializeComponent();
ReportsTable = new List<IReports>() {
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;
}
截图:
答案 2 :(得分:1)
为什么不在基本接口中添加一个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<IReports>()
{
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<IReports> 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 Grid.Row="0" Grid.Column="0"
ItemsSource="{Binding ReportsTable}"
SelectedItem="{Binding SelectedRptRow}"
MouseDoubleClick="Control_OnMouseDoubleClick"
AutoGenerateColumns="False"
Width="200" Height ="140"
HorizontalAlignment="Left"
VerticalAlignment="Top">
<DataGrid.Columns>
<DataGridTextColumn
Header="Report Item"
Width="180"
IsReadOnly="True"
CanUserSort="False"
Binding="{Binding Path=ShowValue}" />
</DataGrid.Columns>
</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上有一个布尔属性,我可以显示和隐藏不同的控件......可能是管理员和标准用户选项。
我还使用转换器处理日期以进行替代格式化。
答案 3 :(得分:0)
您可以滥用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:
<DataGrid.Resources>
<local:NameValueConverter x:Key="NameValueConverter"></local:NameValueConverter>
</DataGrid.Resources>
并在绑定中指定它:
{Binding Path=., Converter={StaticResource NameValueConverter}}
此解决方案仅适用于只读DataGrids(编辑抛出NotImplementedException)。