C#grid DataSource多态

时间:2009-05-29 18:13:32

标签: c# winforms datagridview

我有一个网格,我将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的任何给定列表将由单个基础类型组成。

7 个答案:

答案 0 :(得分:5)

数据绑定到列表遵循以下策略:

  1. 数据源实现IListSource吗?如果是,请转到2,结果为GetList()
  2. 数据源实现IList吗?如果没有,抛出错误;预期列表
  3. 数据源实现ITypedList吗?如果是这样,请将其用于元数据(退出)
  4. 数据源是否有非对象索引器public Foo this[int index](对于某些Foo)?如果是,请使用typeof(Foo)作为元数据
  5. 列表中有什么内容吗?如果是,请使用第一项(list[0])作为元数据
  6. 没有可用的元数据
  7. 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结合使用。

它看起来也比网格更好:)