C # grid DataSource полиморфизм - PullRequest
3 голосов
/ 29 мая 2009

У меня есть сетка, и я устанавливаю 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 ]

5 голосов
/ 05 июня 2009

Привязка данных к спискам осуществляется по следующей стратегии:

  1. источник данных реализует IListSource? если это так, переходите к 2 с результатом GetList()
  2. реализует ли источник данных IList? если нет, выведите ошибку; список ожидается
  3. источник данных реализует ITypedList? если это так, используйте это для метаданных (выход)
  4. имеет ли источник данных неиндексатор, public Foo this[int index] (для некоторых Foo)? если это так, используйте typeof(Foo) для метаданных
  5. есть что-нибудь в списке? если это так, используйте первый элемент (list[0]) для метаданных
  6. метаданные недоступны

List<IListItem> попадает в «4» выше, поскольку он имеет типизированный индексатор типа IListItem - и поэтому он будет получать метаданные через TypeDescriptor.GetProperties(typeof(IListItem)).

Итак, теперь у вас есть три варианта:

  • напишите TypeDescriptionProvider, который возвращает свойства для IListItem - я не уверен, что это осуществимо, так как вы не можете знать, какой конкретный тип задан только IListItem
  • использовать правильно типизированный список (List<User> и т. Д.) - просто как простой способ получения IList с необъектным индексатором
  • написать ITypedList упаковщик (много работы)
  • использовать что-то вроде ArrayList (т. Е. Нет общедоступного не объектного индексатора) - очень хакер!

Я предпочитаю использовать правильный тип 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 голос
/ 02 июня 2009

Если вы точно знаете, что все члены Списка будут одного и того же производного типа, то вот как это сделать с печатью одобрения "Работы на моей машине".

Сначала загрузите 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, чтобы привести их к этому типу.

0 голосов
/ 05 июня 2009

Если вы хотите использовать решение на основе ListView, версия с привязкой к данным ObjectListView позволит вам сделать это. Он считывает открытые свойства источника данных и создает столбцы для отображения каждого свойства. Вы можете комбинировать это с BindingListView.

Это также выглядит лучше, чем сетка:)

0 голосов
/ 05 июня 2009

Я бы предложил динамически создавать столбцы в сетке для дополнительных свойств и создать либо функцию в IListItem, которая выдает список доступных столбцов, либо использовать проверку объекта для определения столбцов, доступных для типа.

Тогда графический интерфейс будет гораздо более общим, и у вас не будет такого большого контроля над дополнительными столбцами, но они будут динамическими.

Не проверенный / скомпилированный «код psuedo»;

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 );
}
0 голосов
/ 04 июня 2009

Когда вы используете autogeneratecolumns, он не делает это автоматически для вас?

0 голосов
/ 30 мая 2009

Я пробовал проекции и пытался использовать 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;
    }
0 голосов
/ 29 мая 2009

Для этого вам нужно будет использовать столбец шаблона Grid. Внутри поля шаблона вам необходимо проверить тип объекта и затем получить правильное свойство - я рекомендую создать метод в вашем коде, который позаботится об этом. Таким образом:

<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 "";
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...