C# grid DataSource polymorphism

前端 未结 7 1714
我在风中等你
我在风中等你 2021-01-06 06:28

I have a grid, and I\'m setting the DataSource to a List. What I want is to have the list bind to the underlying type, and disply

相关标签:
7条回答
  • 2021-01-06 06:38

    When you use autogeneratecolumns it doesnt automatically do this for you?

    0 讨论(0)
  • 2021-01-06 06:40

    As long as you know for sure that the members of the List<IListItem> are all going to be of the same derived type, then here's how to do it, with the "Works on my machine" seal of approval.

    First, download BindingListView, which will let you bind generic lists to your DataGridViews.

    For this example, I just made a simple form with a DataGridView and randomly either called code to load a list of Users or Locations in 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));
            }
        }
    }
    

    The important lines of code are these:

    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
    

    and the most important are these:

    BindGenericList(dataGridView1, items.ConvertAll(item => (User)item));
    BindGenericList(dataGridView1, items.ConvertAll(item => (Location)item));
    

    If all items in the list are known to be of the certain derived type, just call ConvertAll to cast them to that type.

    0 讨论(0)
  • 2021-01-06 06:40

    My suggestion would be to dynamically create the columns in the grid for the extra properties and create either a function in IListItem that gives a list of available columns - or use object inspection to identify the columns available for the type.

    The GUI would then be much more generic, and you would not have as much UI control over the extra columns - but they would be dynamic.

    Non-checked/compiled 'psuedo code';

    public interface IListItem
    {
        IList<string> ExtraProperties;
    
    
        ... your old code.
    }
    
    public class User : IListItem
    {
       .. your old code
        public IList<string> ExtraProperties { return new List { "UserSpecificField" } }
    }
    

    and in form loading

    foreach(string columnName in firstListItem.ExtraProperties)
    {
         dataGridView.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = columnName, HeaderText = columnName );
    }
    
    0 讨论(0)
  • 2021-01-06 06:44

    I tried projections, and I tried using Convert.ChangeType to get a list of the underlying type, but the DataGrid wouldn't display the fields. I finally settled on creating static methods in each type to return the headers, instance methods to return the display fields (as a list of string) and put them together into a DataTable, and then bind to that. Reasonably clean, and it maintains the separation I wanted between the data types and the display.

    Here's the code I use to create the table:

        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;
        }
    
    0 讨论(0)
  • 2021-01-06 06:51

    If you are willing to use a ListView based solution, the data-bindable version ObjectListView will let you do this. It reads the exposed properties of the DataSource and creates columns to show each property. You can combine it with BindingListView.

    It also looks nicer than a grid :)

    0 讨论(0)
  • 2021-01-06 06:54

    You'll need to use a Grid template column for this. Inside the template field you'll need to check what the type of the object is and then get the correct property - I recommend creating a method in your code-behind which takes care of this. Thus:

    <asp:TemplateField HeaderText="PolymorphicField">
        <ItemTemplate>
            <%#GetUserSpecificProperty(Container.DataItem)%>
        </ItemTemplate>
    </asp:TemplateField>
    

    In your code-behind:

    protected string GetUserSpecificProperty(IListItem obj) {
        if (obj is User) {
            return ((User) obj).UserSpecificField
        } else if (obj is Location) {
            return ((Location obj).LocationSpecificField;
        } else { 
            return "";
        }
    }
    
    0 讨论(0)
提交回复
热议问题