从对象的集合属性自动创建DataGridViewComboBoxCell

时间:2015-09-25 01:10:48

标签: c# winforms datagridview datagridviewcomboboxcell

使用WinForms,我正在尝试编写一个方法来检查绑定到DataGridView中的行的数据项是否包含IList作为属性,然后自动将DataGridViewTextBoxCell转换为{ {1}}将该列表绑定为数据源。目标是根据所找到对象的list属性中的元素,为每一行提供一个具有不同值的下拉菜单。因此,例如在第一行中,下拉列表可以有3个DataGridViewComboBoxCell类型的对象作为选项,第二行可以有5个类型为ObjA的对象,依此类推。

这就是我所拥有的:

ObjC

这是将初始数据绑定到private void dgv_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e) { foreach (DataGridViewRow row in dgv.Rows) { object obj = row.DataBoundItem; if (obj != null) { IEnumerable listProperties = obj.GetType().GetProperties().Where(p => p.GetValue(obj) is IList); foreach (PropertyInfo list in listProperties) { DataGridViewComboBoxCell cell = new DataGridViewComboBoxCell(); IList source = (IList)list.GetValue(obj, null); cell.DataSource = source; cell.ValueMember = "id"; cell.DisplayMember = "name"; cell.ValueType = source.GetType().GetProperty("Item").PropertyType; cell.Value = source[0].GetType().GetProperty("id").GetValue(source[0]); (row.Cells[list.Name]) = cell; } } } }

的代码
DataGridView

问题:加载DataGridView时,我收到“DataGridViewComboBoxCell值无效”错误。我注意到,在运行第 IBindingList source = new BindingList<Models.InputValue>(datasource); inputsDgv.AutoGenerateColumns = true; inputsDgv.AllowUserToAddRows = false; inputsDgv.AllowUserToDeleteRows = false; inputsDgv.DataSource = source; 行后,(row.Cells[list.Name]) = cell;cell.ValueType更改为System.Collections.Generic.IList1[[roco.Models.ISnapshots]]。我认为必须解决这个问题。

有没有人知道如何绕过这个错误?

谢谢!

P.S。:System.Int32的类型为row.DataBoundItem,list属性是InputValue个对象的集合

ProjectionSnapshot

3 个答案:

答案 0 :(得分:1)

TL; DR:跳至解决方案部分。

DataGridViewTextBoxCell更改为DataGridViewComboBoxCell是可能的,但如果组合框值的类型与列的ValueType类型不同,则会导致问题。这是因为,正如@Loathing和@Mohit Shrivastava所提到的,DataGridViewColumns与相应的Cell类紧密耦合。

在阅读@ Loathing的代码示例后,我尝试将列ValueType设置为typeof(Object),然后再对DataGridViewTextBoxCell进行更改。这不起作用,因为当您将对象绑定到DataGridView并使用AutoGenerateColumns时,有一种机制会自动设置列的ValueType以反映属性的类型在绑定对象中。如果您生成自己的列并将列DataPropertyName设置为对象属性名称,也是如此。

解决方案

1)生成自己的列,将对象属性映射到DataGridViewTextBoxCell,但不映射到DataGridViewComboBoxCell

P.S。:我不再检查IList,但是对于任何IEnumerable(请参阅此答案以获取更多详细信息:https://stackoverflow.com/a/9434921/5374324

public void loadGrid<T>(IList<T> datasource)
{
    generateDataGridViewColumns<T>(datasource);

    IBindingList source = new BindingList<T>(datasource);
    inputsDgv.AutoGenerateColumns = false;
    inputsDgv.AllowUserToAddRows = false;
    inputsDgv.AllowUserToDeleteRows = false;
    inputsDgv.DataSource = source;
}

private void generateDataGridViewColumns<T>(IList<T> datasource)
{
    dgv.Columns.Clear();

    if (datasource != null)
    {
        foreach (PropertyInfo property in typeof(T).GetProperties())
        {
            DataGridViewColumn col;
            var displayNameObj = property.GetCustomAttributes(typeof(DisplayNameAttribute), true).Cast<DisplayNameAttribute>().FirstOrDefault();
            string displayName = (displayNameObj == null) ? property.Name : displayNameObj.DisplayName;

            if (property.PropertyType.GetInterface(typeof(IEnumerable<>).FullName) != null && property.PropertyType != typeof(string))
            {
                col = new DataGridViewComboBoxColumn();
                (col as DataGridViewComboBoxColumn).AutoComplete = false;
                (col as DataGridViewComboBoxColumn).ValueType = typeof(Object);
            }
            else
            {
                col = new DataGridViewTextBoxColumn() { DataPropertyName = property.Name };
            }

            col.Name = property.Name;
            col.HeaderText = displayName;
            ReadOnlyAttribute attrib = Attribute.GetCustomAttribute(property, typeof(ReadOnlyAttribute)) as ReadOnlyAttribute;
            col.ReadOnly = (!property.CanWrite || (attrib != null && attrib.IsReadOnly));
            inputsDgv.Columns.Add(col);
        }
    }
}

2)使用DataBindingComplete事件填充组合框:

private void dgv_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
    foreach (DataGridViewRow row in dgv.Rows)
    {
        object obj = row.DataBoundItem;
        if (obj != null)
        {
            IEnumerable listProperties = obj.GetType().GetProperties().Where(p => p.GetValue(obj) is IList);
            foreach (PropertyInfo list in listProperties)
            {
                IList source = (IList)list.GetValue(obj, null);
                DataGridViewComboBoxCell cell = (row.Cells[list.Name] as DataGridViewComboBoxCell);
                cell.DataSource = source;
                cell.ValueType = source.GetType().GetProperty("Item").PropertyType;

                ValueMember valueMember = (ValueMember)obj.GetType().GetProperty(list.Name).GetCustomAttribute(typeof(ValueMember));
                DisplayMember displayMember = (DisplayMember)obj.GetType().GetProperty(list.Name).GetCustomAttribute(typeof(DisplayMember));
                if(valueMember != null && displayMember != null)
                {
                    cell.ValueMember = valueMember.Value;
                    cell.DisplayMember = displayMember.Value;
                }

                cell.Value = source[0].GetType().GetProperty("id").GetValue(source[0]);
            }
        }
    }
}

3)创建ValueMember和DisplayMember属性类:

[System.AttributeUsage(System.AttributeTargets.Property)]
public class ValueMember : System.Attribute
{
    public string Value { get; private set; }

    public ValueMember(string valueMember)
    {
        this.Value = valueMember;
    }
}

[System.AttributeUsage(System.AttributeTargets.Property)]
public class DisplayMember : System.Attribute
{
    public string Value { get; private set; }

    public DisplayMember(string displayMember)
    {
        this.Value = displayMember;
    }
}

4)使用属性:

public class InputValue
{
    public string id{ get; set; }
    public string name{ get; set; }

    [DisplayName("Values")]
    [ValueMember("id")]
    [DisplayMember("name")]
    public IList<IValue> values{ get; set; }
}

答案 1 :(得分:0)

正如@Loathing所建议的 DataGridViewColumns 与相应的Cell类紧密耦合。无法在文本列中使用组合单元格。

您可以使用 GridView ,用于在列中排列数据并为ListView添加布局和设计支持。 GridView用作ListView的补充控件,以提供样式和布局。 GridView没有自己的控件相关属性,如背景和前景色,字体属性,大小和位置。容器ListView用于提供所有与控件相关的属性。 Read More about GridView in WPF

答案 2 :(得分:0)

如果您希望根据所选行在下拉列表中使用不同的值,则需要使用CellBeginEdit事件并更改组合框编辑控件的DataSource。< / p>

请参阅此答案以获取示例:How do you assign different data sources for a DataGridViewComboBox for each record?