我有一个网格,我将DataSource
设置为List<IListItem>
。我想要的是让列表绑定到底层类型,并显示这些属性,而不是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; };
}
如何绑定到网格,以便如果我的List<IListItem>
包含用户,我会看到特定于用户的字段?编辑:请注意,我想绑定到Datagrid的任何给定列表将由单个基础类型组成。
答案 0 :(得分:5)
数据绑定到列表遵循以下策略:
IListSource
吗?如果是,请转到2,结果为GetList()
IList
吗?如果没有,抛出错误;预期列表ITypedList
吗?如果是这样,请将其用于元数据(退出)public Foo this[int index]
(对于某些Foo
)?如果是,请使用typeof(Foo)
作为元数据list[0]
)作为元数据 List<IListItem>
属于上面的“4”,因为它有一个类型为IListItem
的类型化索引器 - 因此它将通过TypeDescriptor.GetProperties(typeof(IListItem))
获取元数据。
现在,您有三个选择:
TypeDescriptionProvider
,它返回IListItem
的属性 - 我不确定这是否可行,因为你不可能知道IListItem
给出的具体类型是什么List<User>
等) - 只是将IList
与非对象索引器一起使用的简单方法ITypedList
包装器(很多工作)ArrayList
这样的东西(即没有公共非对象索引器) - 非常hacky!我的偏好是使用正确类型的List<>
...这是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
}
}
});
}
答案 1 :(得分:1)
只要您确定List&lt; IListItem&gt;的成员即可。所有这些都是相同的派生类型,然后就是如何做到这一点,用“我的机器上的作品”批准印章。
首先,下载BindingListView,它允许您将通用列表绑定到DataGridViews。
对于这个例子,我只使用DataGridView创建了一个简单的表单,并随机调用代码来加载Form1_Load()中的用户或位置列表。
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));
如果已知列表中的所有项目都是某种派生类型,只需调用ConvertAll将它们转换为该类型。
答案 2 :(得分:0)
您需要使用网格模板列。在模板字段中,您需要检查对象的类型,然后获取正确的属性 - 我建议在代码隐藏中创建一个方法来处理这个问题。因此:
<asp:TemplateField HeaderText="PolymorphicField">
<ItemTemplate>
<%#GetUserSpecificProperty(Container.DataItem)%>
</ItemTemplate>
</asp:TemplateField>
在您的代码隐藏中:
protected string GetUserSpecificProperty(IListItem obj) {
if (obj is User) {
return ((User) obj).UserSpecificField
} else if (obj is Location) {
return ((Location obj).LocationSpecificField;
} else {
return "";
}
}
答案 3 :(得分:0)
我尝试了投影,并尝试使用Convert.ChangeType获取基础类型的列表,但DataGrid不会显示字段。我最终决定在每种类型中创建静态方法以返回标头,实例方法返回显示字段(作为字符串列表)并将它们放在一起放入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;
}
答案 4 :(得分:0)
当您使用autogeneratecolumns时,它不会自动为您执行此操作吗?
答案 5 :(得分:0)
我的建议是在网格中动态创建额外属性的列,并在IListItem中创建一个提供可用列列表的函数 - 或者使用对象检查来标识可用于该类型的列。
GUI将更加通用,并且您不会对额外的列进行尽可能多的UI控制 - 但它们将是动态的。
未经检查/编译的“伪代码”;
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 );
}
答案 6 :(得分:0)
如果您愿意使用基于ListView的解决方案,那么数据绑定版本ObjectListView将允许您执行此操作。它读取DataSource的公开属性并创建列以显示每个属性。您可以将它与BindingListView结合使用。
它看起来也比网格更好:)