簡體   English   中英

根據對象的collection屬性自動創建DataGridViewComboBoxCell

[英]Automatically create DataGridViewComboBoxCell from object's collection property

使用WinForms,我試圖編寫一種方法來檢查綁定到DataGridView中的行的數據項是否包含IList作為屬性,然后自動將DataGridViewTextBoxCell轉換為綁定到該列表的DataGridViewComboBoxCell作為數據源。 目的是要有一個下拉菜單,每行的值都不同,這取決於找到的對象的list屬性中的元素。 因此,例如在第一行中,下拉列表可能具有3個ObjA類型的ObjA作為選項,第二行可能具有5個ObjC類型的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的代碼:

        IBindingList source = new BindingList<Models.InputValue>(datasource);
        inputsDgv.AutoGenerateColumns = true;
        inputsDgv.AllowUserToAddRows = false;
        inputsDgv.AllowUserToDeleteRows = false;
        inputsDgv.DataSource = source;

問題:加載DataGridView時出現“ DataGridViewComboBoxCell值無效”錯誤。 我注意到在運行該行之后(row.Cells[list.Name]) = cell; cell.ValueTypeSystem.Collections.Generic.IList1[[roco.Models.ISnapshots]]更改為System.Int32 我認為那一定是問題所在。

有誰知道我該如何解決這個錯誤?

謝謝!

PS: row.DataBoundItem的類型為InputValue ,列表屬性為ProjectionSnapshot對象的集合

public class InputValue : IValues
{
    private int _id;
    public int id { get { return _id; } }

    private IList<ISnapshots> _snapshots;
    public IList<ISnapshots> snapshots
    {
        get { return _snapshots; }
        set { _snapshots = value; }
    }
}

public class ProjectionSnapshot : ISnapshots
{
    private int _id;
    public int id { get { return _id; } }

    private string _name;
    public string name
    {
        get { return _name; }
        set
        {
            if (value.Length > 255)
                Console.WriteLine("Error! SKU snapshot name must be less than 256 characters!");
            else
                _name = value;
        }
    }
}

public interface ISnapshots
{
    int id { get; }
    string name { get; set; }
}

TL; DR:跳到“ 解決方案”部分。


更改DataGridViewTextBoxCellDataGridViewComboBoxCell是可能的,但會造成問題,如果組合框的值有不同的類型不是ValueType的列集。 這是因為,如@Loathing和@Mohit Shrivastava所述, DataGridViewColumns與對應的Cell類緊密耦合。

閱讀@Loathing的代碼示例后,我嘗試在對DataGridViewTextBoxCell進行更改之前將Column的ValueType設置為typeof(Object) 這是行不通的,因為當您將對象綁定到DataGridView並使用AutoGenerateColumns有一種機制會自動設置Column的ValueType以反映綁定對象中屬性的類型。 如果您生成自己的列並將Column的DataPropertyName設置為對象屬性名稱,情況也是如此。


解決方案

1)生成自己的列映射對象屬性到DataGridViewTextBoxCell但不至DataGridViewComboBoxCell

PS:我不再檢查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; }
}

正如@Loathing所建議的, DataGridViewColumns與相應的Cell類緊密耦合。 在文本列中不能使用組合單元格。

您可以使用GridView ,該GridView用於按列排列數據,並為ListView添加布局和設計支持。 GridView用作ListView的補充控件,以提供樣式和布局。 GridView沒有與控件相關的屬性,例如背景和前景色,字體屬性,大小和位置。 容器ListView用於提供所有控件相關的屬性。 閱讀有關WPF中GridView的更多信息

如果要基於選定的行在下拉列表中具有不同的值,則將需要使用CellBeginEdit事件並更改組合框編輯控件的DataSource

參見以下示例答案: 如何為每個記錄的DataGridViewComboBox分配不同的數據源?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM