C#网格数据源多态性
我有一个网格,我正在将C#网格数据源多态性,c#,winforms,datagridview,C#,Winforms,Datagridview,我有一个网格,我正在将数据源设置为列表。我想要的是让列表绑定到底层类型,并显示这些属性,而不是IListItem中定义的属性。因此: public interface IListItem { string Id; string Name; } public class User : IListItem { string Id { get; set; }; string Name { get; set; }; string UserSpecificFiel
数据源
设置为列表
。我想要的是让列表绑定到底层类型,并显示这些属性,而不是IListItem
中定义的属性。因此:
public interface IListItem
{
string Id;
string Name;
}
public class User : IListItem
{
string Id { get; set; };
string Name { get; set; };
string UserSpecificField { get; set; };
}
public class Location : IListItem
{
string Id { get; set; };
string Name { get; set; };
string LocationSpecificField { get; set; };
}
如何绑定到网格,以便如果我的
列表
包含用户,我将看到特定于用户的字段?编辑:请注意,我要绑定到Datagrid的任何给定列表都将由单个基础类型组成。为此,您需要使用网格模板列。在template字段中,您需要检查对象的类型,然后获得正确的属性-我建议在代码中创建一个方法来处理这个问题。因此:
<asp:TemplateField HeaderText="PolymorphicField">
<ItemTemplate>
<%#GetUserSpecificProperty(Container.DataItem)%>
</ItemTemplate>
</asp:TemplateField>
我尝试了投影,并尝试使用Convert.ChangeType获取基础类型的列表,但DataGrid不会显示字段。我最终决定在每种类型中创建静态方法以返回标题,实例方法以返回显示字段(作为字符串列表)并将它们组合到一个DataTable中,然后绑定到该DataTable。相当干净,并且它保持了我想要的数据类型和显示之间的分离 下面是我用来创建表的代码:
DataTable GetConflictTable()
{
Type type = _conflictEnumerator.Current[0].GetType();
List<string> headers = null;
foreach (var mi in type.GetMethods(BindingFlags.Static | BindingFlags.Public))
{
if (mi.Name == "GetHeaders")
{
headers = mi.Invoke(null, null) as List<string>;
break;
}
}
var table = new DataTable();
if (headers != null)
{
foreach (var h in headers)
{
table.Columns.Add(h);
}
foreach (var c in _conflictEnumerator.Current)
{
table.Rows.Add(c.GetFieldsForDisplay());
}
}
return table;
}
DataTable GetConflictTable()
{
类型类型=_conflictEnumerator.Current[0]。GetType();
列表头=空;
foreach(type.GetMethods(BindingFlags.Static | BindingFlags.Public)中的var mi)
{
如果(mi.Name==“GetHeaders”)
{
headers=mi.Invoke(null,null)作为列表;
打破
}
}
var table=新数据表();
如果(标题!=null)
{
foreach(标题中的var h)
{
表.列.添加(h);
}
foreach(变量c在_conflictEnumerator.Current中)
{
table.Rows.Add(c.GetFieldsForDisplay());
}
}
返回表;
}
只要您确定列表中的所有成员都将是相同的派生类型,下面介绍如何使用“在我的机器上工作”的批准印章
首先,下载,这将允许您将通用列表绑定到DataGridView
对于本例,我只是使用DataGridView创建了一个简单表单,并随机调用代码来加载Form1_load()中的用户或位置列表
如果已知列表中的所有项都是特定的派生类型,只需调用ConvertAll将它们转换为该类型。当您使用autogeneratecolumns时,它不会自动为您执行此操作?与列表的数据绑定遵循以下策略:
GetList()
ITypedList
?如果是这样,则将其用于元数据(退出)public Foo this[int index]
(对于某些Foo
)?如果是,请使用typeof(Foo)
作为元数据列表[0]
)作为元数据List
属于上面的“4”,因为它有一个类型为IListItem
的类型索引器,因此它将通过TypeDescriptor.GetProperties(typeof(IListItem))
获取元数据
现在,您有三个选择:
- 编写一个
,返回TypeDescriptionProvider
的属性-我不确定这是否可行,因为您不可能只知道给出的具体类型是什么IListItem
IListItem
- 使用正确键入的列表(
etc)-这是一种使用非对象索引器获取list
的简单方法IList
- 编写一个
包装器(大量工作)ITypedList
- 使用类似于
(即没有公共的非对象索引器)的东西-非常黑客李>ArrayList
列表
。。。这里有一个AutoCast
方法,可以为您执行此操作,而不必知道类型(使用示例)
请注意,这仅适用于同质数据(即所有对象都相同),并且它需要列表中至少一个对象来推断类型
// infers the correct list type from the contents
static IList AutoCast(this IList list) {
if (list == null) throw new ArgumentNullException("list");
if (list.Count == 0) throw new InvalidOperationException(
"Cannot AutoCast an empty list");
Type type = list[0].GetType();
IList result = (IList) Activator.CreateInstance(typeof(List<>)
.MakeGenericType(type), list.Count);
foreach (object obj in list) result.Add(obj);
return result;
}
// usage
[STAThread]
static void Main() {
Application.EnableVisualStyles();
List<IListItem> data = new List<IListItem> {
new User { Id = "1", Name = "abc", UserSpecificField = "def"},
new User { Id = "2", Name = "ghi", UserSpecificField = "jkl"},
};
ShowData(data, "Before change - no UserSpecifiedField");
ShowData(data.AutoCast(), "After change - has UserSpecifiedField");
}
static void ShowData(object dataSource, string caption) {
Application.Run(new Form {
Text = caption,
Controls = {
new DataGridView {
Dock = DockStyle.Fill,
DataSource = dataSource,
AllowUserToAddRows = false,
AllowUserToDeleteRows = false
}
}
});
}
//从内容推断正确的列表类型
静态IList AutoCast(此IList列表){
如果(list==null)抛出新的ArgumentNullException(“list”);
如果(list.Count==0)抛出新的InvalidOperationException(
“无法自动广播空列表”);
类型类型=列表[0]。GetType();
IList result=(IList)Activator.CreateInstance(typeof(List)
.MakeGenericType(类型),list.Count);
foreach(列表中的对象obj)result.Add(obj);
返回结果;
}
//用法
[状态线程]
静态void Main(){
Application.EnableVisualStyles();
列表数据=新列表{
新用户{Id=“1”,Name=“abc”,UserSpecificField=“def”},
新用户{Id=“2”,Name=“ghi”,UserSpecificField=“jkl”},
};
ShowData(数据,“更改前-无用户指定字段”);
ShowData(data.AutoCast(),“更改后-具有UserSpecifiedField”);
}
静态void ShowData(对象数据源、字符串标题){
Application.Run(新表单{
文本=标题,
控件={
新DataGridView{
Dock=DockStyle.Fill,
数据源=数据源,
AllowUserToAddress=false,
AllowUserToDeleteRows=false
}
}
});
}
我的建议是在网格中为额外属性动态创建列,并在IListItem中创建一个函数,提供可用列的列表,或者使用对象检查来识别colu
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using Equin.ApplicationFramework;
namespace DGVTest
{
public interface IListItem
{
string Id { get; }
string Name { get; }
}
public class User : IListItem
{
public string UserSpecificField { get; set; }
public string Id { get; set; }
public string Name { get; set; }
}
public class Location : IListItem
{
public string LocationSpecificField { get; set; }
public string Id { get; set; }
public string Name { get; set; }
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void InitColumns(bool useUsers)
{
if (dataGridView1.ColumnCount > 0)
{
return;
}
DataGridViewCellStyle gridViewCellStyle = new DataGridViewCellStyle();
DataGridViewTextBoxColumn IDColumn = new DataGridViewTextBoxColumn();
DataGridViewTextBoxColumn NameColumn = new DataGridViewTextBoxColumn();
DataGridViewTextBoxColumn DerivedSpecificColumn = new DataGridViewTextBoxColumn();
IDColumn.DataPropertyName = "ID";
IDColumn.HeaderText = "ID";
IDColumn.Name = "IDColumn";
NameColumn.DataPropertyName = "Name";
NameColumn.HeaderText = "Name";
NameColumn.Name = "NameColumn";
DerivedSpecificColumn.DataPropertyName = useUsers ? "UserSpecificField" : "LocationSpecificField";
DerivedSpecificColumn.HeaderText = "Derived Specific";
DerivedSpecificColumn.Name = "DerivedSpecificColumn";
dataGridView1.Columns.AddRange(
new DataGridViewColumn[]
{
IDColumn,
NameColumn,
DerivedSpecificColumn
});
gridViewCellStyle.SelectionBackColor = Color.LightGray;
gridViewCellStyle.SelectionForeColor = Color.Black;
dataGridView1.RowsDefaultCellStyle = gridViewCellStyle;
}
public static void BindGenericList<T>(DataGridView gridView, List<T> list)
{
gridView.DataSource = new BindingListView<T>(list);
}
private void Form1_Load(object sender, EventArgs e)
{
dataGridView1.AutoGenerateColumns = false;
Random rand = new Random();
bool useUsers = rand.Next(0, 2) == 0;
InitColumns(useUsers);
if(useUsers)
{
TestUsers();
}
else
{
TestLocations();
}
}
private void TestUsers()
{
List<IListItem> items =
new List<IListItem>
{
new User {Id = "1", Name = "User1", UserSpecificField = "Test User 1"},
new User {Id = "2", Name = "User2", UserSpecificField = "Test User 2"},
new User {Id = "3", Name = "User3", UserSpecificField = "Test User 3"},
new User {Id = "4", Name = "User4", UserSpecificField = "Test User 4"}
};
BindGenericList(dataGridView1, items.ConvertAll(item => (User)item));
}
private void TestLocations()
{
List<IListItem> items =
new List<IListItem>
{
new Location {Id = "1", Name = "Location1", LocationSpecificField = "Test Location 1"},
new Location {Id = "2", Name = "Location2", LocationSpecificField = "Test Location 2"},
new Location {Id = "3", Name = "Location3", LocationSpecificField = "Test Location 3"},
new Location {Id = "4", Name = "Location4", LocationSpecificField = "Test Location 4"}
};
BindGenericList(dataGridView1, items.ConvertAll(item => (Location)item));
}
}
}
DerivedSpecificColumn.DataPropertyName = useUsers ? "UserSpecificField" : "LocationSpecificField"; // obviously need to bind to the derived field
public static void BindGenericList<T>(DataGridView gridView, List<T> list)
{
gridView.DataSource = new BindingListView<T>(list);
}
dataGridView1.AutoGenerateColumns = false; // Be specific about which columns to show
BindGenericList(dataGridView1, items.ConvertAll(item => (User)item));
BindGenericList(dataGridView1, items.ConvertAll(item => (Location)item));
// infers the correct list type from the contents
static IList AutoCast(this IList list) {
if (list == null) throw new ArgumentNullException("list");
if (list.Count == 0) throw new InvalidOperationException(
"Cannot AutoCast an empty list");
Type type = list[0].GetType();
IList result = (IList) Activator.CreateInstance(typeof(List<>)
.MakeGenericType(type), list.Count);
foreach (object obj in list) result.Add(obj);
return result;
}
// usage
[STAThread]
static void Main() {
Application.EnableVisualStyles();
List<IListItem> data = new List<IListItem> {
new User { Id = "1", Name = "abc", UserSpecificField = "def"},
new User { Id = "2", Name = "ghi", UserSpecificField = "jkl"},
};
ShowData(data, "Before change - no UserSpecifiedField");
ShowData(data.AutoCast(), "After change - has UserSpecifiedField");
}
static void ShowData(object dataSource, string caption) {
Application.Run(new Form {
Text = caption,
Controls = {
new DataGridView {
Dock = DockStyle.Fill,
DataSource = dataSource,
AllowUserToAddRows = false,
AllowUserToDeleteRows = false
}
}
});
}
public interface IListItem
{
IList<string> ExtraProperties;
... your old code.
}
public class User : IListItem
{
.. your old code
public IList<string> ExtraProperties { return new List { "UserSpecificField" } }
}
foreach(string columnName in firstListItem.ExtraProperties)
{
dataGridView.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = columnName, HeaderText = columnName );
}