[英]DataBinding of DataGridView and List<> with BindingSource
[英]DataGridView Databinding to List<List<T>>
给定代码
class Foo {
public string Value {get; set;}
public int Id {get; set;}
}
List<List<Foo>> fooList = new List<List<Foo>>();
有没有办法将 Multidim ICollection 绑定到属性 Value 上的 DataGridView,当您更改单元格时,object 的 Value 属性会更新?
在这种情况下,列表中的每个 Foo 实例将代表 DataGridView 中的一个单元格,并且行/列将被保留,就像它们在 multidim ICollection 中一样
通过 Multidim,我的意思是影响:
List<List<Foo>] => [
List<Foo> => [0,1,2,3,4,5]
List<Foo> => [0,1,2,3,4,5]
List<Foo> => [0,1,2,3,4,5]
List<Foo> => [0,1,2,3,4,5]
]
嵌套列表中的每个元素实际上都是 Foo 的实例。
您描述的问题可以通过几种不同的方式解决。 一种是“扁平化”每个List<Foo>.
基本上,这会将列表中的所有Foo
项目展平为“单个” string.
正如我所评论的那样,使用这种方法,你最终会得到一列,每一行都是一个“扁平化”的List<Foo>.
每个单元格在字符串中可能有不同数量的Foo
项。
在这种情况下和其他情况下,这可能不是预期的结果。 由于您有一个List
of Lists
,因此使用两 (2) 个网格的“主从”方法可能会使事情变得更容易。在这种方法中,第一个网格(主)将有一个 (1) 列,每行将是一个List<Foo>.
由于我们已经知道网格不会将此 LIST 显示为单个单元格,并且我们不想“扁平化”列表,因此这是第二个(详细)网格发挥作用的地方。第一个网格显示Foo
的所有列表,无论选择哪个“行”,第二个网格(详细信息)将显示所有List<Foo>
项目。
一个例子可能最能说明我的意思。 首先,我们需要制作一个额外的 class。 原因是如果我们使用List<List<Foo>>
作为主网格的DataSource
,它将显示类似...
如图所示,这两列将是List
“容量”和“计数”。 这可能有效; 但是,它可能会使用户感到困惑。 这就是为什么我们想要另一个 class。 它是List<Foo>
周围的简单“包装器”,为了显示它,我们将向此 class 添加一个“Name”属性。 这将显示在主网格中。
鉴于当前修改后的Foo
类...
public class Foo {
public string Value { get; set; }
public int Id { get; set; }
}
这个FooList
class 可能看起来像……
public class FooList {
public string ListName { get; set; }
public List<Foo> TheFooList { get; set; }
}
List<FooList>
会显示类似...
现在,当用户在第一个“主”网格中“选择”一行时,第二个“细节”网格将显示该列表中的所有Foo
项目。下面是一个完整的示例。将两个网格放到一个表单上并复制代码下面跟随。
为了提供帮助,一个返回List<Foo>
的方法,其中每个列表中有随机数量的Foo
项。 除了为每个Foo
object 设置随机Value
之外,此方法可能类似于下面的全局rand
Random
变量,以获取要添加到列表中的随机数Foo
项。
Random rand = new Random();
private List<Foo> GettRandomNumberOfFooList() {
int numberOfFoo = rand.Next(2, 20);
List<Foo> fooList = new List<Foo>();
for (int i = 0; i < numberOfFoo; i++) {
fooList.Add(new Foo { Id = i, Value = rand.Next(1, 100).ToString() });
}
return fooList;
}
我们可以使用此方法创建一个List<FooList>
进行测试。 主网格DataSource
将是这个列表。 然后,要确定在详细信息网格中显示哪个列表,我们将简单地使用 selected FooList.TheFooList
属性。
接下来,我们需要一个触发器来知道何时“更改”详细信息数据源。 在这种情况下,我使用网格, RowEnter
方法来更改详细信息网格数据源。
下面是上面描述的代码。 主网格将有 15 个FooList
项。
List<FooList> FooLists;
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
FooLists = new List<FooList>();
for (int i = 0; i < 15; i++) {
FooLists.Add(new FooList { ListName = "Foo List " + (i + 1), TheFooList = GettRandomNumberOfFooList() });
}
dataGridView1.DataSource = FooLists;
dataGridView2.DataSource = FooLists[0].TheFooList;
}
private void dataGridView1_RowEnter(object sender, DataGridViewCellEventArgs e) {
dataGridView2.DataSource = FooLists[e.RowIndex].TheFooList;
}
这应该产生类似...
最后,这只是一个示例,使用BindingList/BindingSource
可能会使事情变得更容易。 这是一个非常简单的示例,它使用“主从”方法和列表列表。
您可以创建一个实现IListSource的自定义数据源并将其设置为 DataGridView 的数据源。 要正确实现接口以满足您的要求:
DataTable
。DefaultView
属性的ListChanged
事件并将更改应用于原始列表。GetList
方法,返回映射的数据表。然后,当您将 DataGridView 绑定到新数据源时,所有编辑操作都会立即反映在您的原始列表中:
dataGridView1.DataSource = new FooDataSource(yourListOfListOfFoo);
ListListDataSource 实现
public class ListListDataSource<T> : IListSource
{
List<List<T>> data;
DataTable table;
public ListListDataSource(List<List<T>> list)
{
this.data = list;
table = new DataTable();
for (int i = 0; i < list.First().Count(); i++)
{
TypeDescriptor.GetProperties(typeof(T)).Cast<PropertyDescriptor>()
.Where(p => p.IsBrowsable).ToList().ForEach(p =>
{
if (p.IsBrowsable)
{
var c = new DataColumn($"[{i}].{p.Name}", p.PropertyType);
c.ReadOnly = p.IsReadOnly;
table.Columns.Add(c);
}
});
}
foreach (var innerList in list)
{
table.Rows.Add(innerList.SelectMany(
x => TypeDescriptor.GetProperties(typeof(T)).Cast<PropertyDescriptor>()
.Where(p => p.IsBrowsable).Select(p => p.GetValue(x))).ToArray());
}
table.DefaultView.AllowDelete = false;
table.DefaultView.AllowNew = false;
table.DefaultView.ListChanged += DefaultView_ListChanged;
}
public bool ContainsListCollection => false;
public IList GetList()
{
return table.DefaultView;
}
private void DefaultView_ListChanged(object sender, ListChangedEventArgs e)
{
if (e.ListChangedType != ListChangedType.ItemChanged)
throw new NotSupportedException();
var match = Regex.Match(e.PropertyDescriptor.Name, @"\[(\d+)\]\.(\w+)");
var index = int.Parse(match.Groups[1].Value);
var propertyName = match.Groups[2].Value;
typeof(T).GetProperty(propertyName).SetValue(data[e.NewIndex][index],
table.Rows[e.NewIndex][e.PropertyDescriptor.Name]);
}
}
然后将您的列表绑定到DataGridView
,如下所示:
List<List<Foo>> foos;
private void Form1_Load(object sender, EventArgs e)
{
foos = new List<List<Foo>>{
new List<Foo>(){
new Foo() { Id=11, Value="11"}, new Foo() { Id = 12, Value = "12" }
},
new List<Foo>() {
new Foo() { Id=21, Value="21"}, new Foo() { Id = 22, Value = "22" }
},
};
dataGridView1.DataSource = new ListListDataSource<Foo>(foos);
}
而当您在 DataGridView 中编辑数据时,实际上您是在编辑原始列表。
此外,如果您想隐藏某个属性,只需将[Browsable(false)]
添加到该属性即可:
public class Foo
{
[Browsable(false)]
public int Id { get; set; }
public string Value { get; set; }
}
一种有趣的方法是使用自定义TypeDescriptor创建新数据源。
类型描述符提供有关类型的信息,包括属性列表以及获取和设置属性值。 DataTable 也以同样的方式工作,为了在 DataGridView 中显示列列表,它返回包含每列属性的属性描述符列表。
然后,当您将 DataGridView 绑定到新数据源时,您实际上是在编辑原始列表:
dataGridView1.DataSource = new FooDataSource(yourListOfListOfFoo);
使用 TypeDescriptor 实现 ListListDataSource
在这里,我为每个内部列表创建了一个自定义类型描述符,以将其视为具有一些属性的单个 object。 这些属性是内部列表中每个元素的所有属性,我为属性创建了一个属性描述符:
public class ListListDataSource<T> : List<FlatList>
{
public ListListDataSource(List<List<T>> list)
{
this.AddRange(list.Select(x =>
new FlatList(x.Cast<object>().ToList(), typeof(T))));
}
}
public class FlatList : CustomTypeDescriptor
{
private List<object> data;
private Type type;
public FlatList(List<object> data, Type type)
{
this.data = data;
this.type = type;
}
public override PropertyDescriptorCollection GetProperties()
{
return this.GetProperties(new Attribute[] { });
}
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
var properties = new List<PropertyDescriptor>();
for (int i = 0; i < data.Count; i++)
{
foreach (PropertyDescriptor p in TypeDescriptor.GetProperties(type))
properties.Add(new FlatListProperty(i, p));
}
return new PropertyDescriptorCollection(properties.ToArray());
}
public object this[int i]
{
get => data[i];
set => data[i] = value;
}
}
public class FlatListProperty : PropertyDescriptor
{
int index;
PropertyDescriptor originalProperty;
public FlatListProperty(int index, PropertyDescriptor originalProperty)
: base($"[{index}].{originalProperty.Name}",
originalProperty.Attributes.Cast<Attribute>().ToArray())
{
this.index = index;
this.originalProperty = originalProperty;
}
public override Type ComponentType => typeof(FlatList);
public override bool IsReadOnly => false;
public override Type PropertyType => originalProperty.PropertyType;
public override bool CanResetValue(object component) => false;
public override object GetValue(object component) =>
originalProperty.GetValue(((FlatList)component)[index]);
public override void ResetValue(object component) { }
public override void SetValue(object component, object value) =>
originalProperty.SetValue(((FlatList)component)[index], value);
public override bool ShouldSerializeValue(object component) => true;
}
绑定数据:
List<List<Foo>> foos;
private void Form1_Load(object sender, EventArgs e)
{
foos = new List<List<Foo>>{
new List<Foo>(){
new Foo() { Id=11, Value="11"}, new Foo() { Id = 12, Value = "12" }
},
new List<Foo>() {
new Foo() { Id=21, Value="21"}, new Foo() { Id = 22, Value = "22" }
},
};
dataGridView1.DataSource = new ListListDataSource<Foo>(foos);
}
而当您在 DataGridView 中编辑数据时,实际上您是在编辑原始列表。
此外,如果您想隐藏某个属性,只需将[Browsable(false)]
添加到该属性即可:
public class Foo
{
[Browsable(false)]
public int Id { get; set; }
public string Value { get; set; }
}
如果可以接受以扁平结构显示数据以进行编辑,则可以使用:
List<List<Foo>> foos;
private void Form1_Load(object sender, EventArgs e)
{
foos = new List<List<Foo>>{
new List<Foo>(){
new Foo() { Id=11, Value="11"}, new Foo() { Id = 12, Value = "12" }
},
new List<Foo>() {
new Foo() { Id=21, Value="21"}, new Foo() { Id = 22, Value = "22" }
},
};
dataGridView1.DataSource = foos.SelectMany(x=>x).ToList();
}
并在平面列表中编辑数据,如下所示:
当您编辑每一行时,您实际上是在编辑原始列表。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.