![](/img/trans.png)
[英]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.