user1620696
user1620696

Reputation: 11375

How to display a Dictionary using DataGridView?

I want to display a Dictionary<string, TData> in Windows Forms using a DataGridView. The particular use case that lead me to that was the following: the dictionary key is the column name, representing a month, and the data type is decimal.

So in that case, for each column we have a month and a corresponding decimal number. I wanted the DataGridView to display the columns for the months and perform a data binding to the entries of the dictionary.

Obviously this has another trivial solution: create a view model containing 12 properties, each of type decimal for each month. Create a DataGridView and perform a traditional data binding to this view model, with the data source being a list of such view model objects.

But this is tedious. We need to create a bunch of things that can be automated by using the dictionary.

My problem is that the columns will need to be dynamically created based on the dictionary and the data binding will have to be done in that way.

I googled a little on that and found the Binding class, which allows to create bindings. But I don't know how to use this to bind dynamically created columns to entries in a dictionary.

How can this be done?

Upvotes: 1

Views: 3231

Answers (2)

Martin
Martin

Reputation: 1448

I use this routine for VB.net

 Sub CopyDictToGrid(dict As Dictionary(Of String, String), grid As DataGridView)
    Dim dt As New DataTable("DataTableName")
    Dim column As DataColumn
    column = New DataColumn()
    column.DataType = System.Type.GetType("System.String")
    column.ColumnName = "nr"
    column.Caption = "Nr."
    column.ReadOnly = True
    column.Unique = False
    dt.Columns.Add(column)
    ' Create second column.
    column = New DataColumn()
    column.DataType = System.Type.GetType("System.String")
    column.ColumnName = "naam"
    column.AutoIncrement = False
    column.Caption = "Eigenaar"
    column.ReadOnly = True
    column.Unique = False
    dt.Columns.Add(column)
    ' Create  DataRow objects and add 
    For Each kvp As KeyValuePair(Of String, String) In dict
        Dim row As DataRow = dt.NewRow()
        row("nr") = kvp.Key
        row("naam") = kvp.Value
        dt.Rows.Add(row)
    Next
    grid.DataSource = dt
    grid.Columns("nr").Resizable = 1 ' 0 none, 1 true, 2 false
    grid.Columns("naam").Resizable = 1
End Sub

Upvotes: 0

Reza Aghaei
Reza Aghaei

Reputation: 125197

You need to implement ICustomTypeDescriptor for the dictionary to be editable in DataGridView or PropertyGrid.

Option 1 - Implement ICustomTypeDescriptor

You can implement ICustomTypeDescriptor and then you can edit the dictionary in DataGridView. You can use this implementation with some small changes. Then you can simply edit dictionary this way:

Dictionary<string, int> dictionary;
public void Form1_Load(object sender, EventArgs e)
{
    dictionary = new Dictionary<string, int>() { { "A", 1 }, { "B", 2 }, { "C", 3 } };
    dataGridView1.DataSource =  new BindingSource(new DictionaryAdapter(dictionary) , "");
}

Or if you prefer, you can set it as SelectedObject of a PropertyGrid:

propertyGrid1.SelectedObject =  new DictionaryAdapter(dictionary);

Here is the implementation:

public class DictionaryAdapter : ICustomTypeDescriptor
{
    IDictionary dictionary;
    public DictionaryAdapter(IDictionary d)
    {
        dictionary = d;
    }
    public string GetComponentName()
    {
        return TypeDescriptor.GetComponentName(this, true);
    }
    public EventDescriptor GetDefaultEvent()
    {
        return TypeDescriptor.GetDefaultEvent(this, true);
    }
    public string GetClassName()
    {
        return TypeDescriptor.GetClassName(this, true);
    }
    public EventDescriptorCollection GetEvents(Attribute[] attributes)
    {
        return TypeDescriptor.GetEvents(this, attributes, true);
    }
    EventDescriptorCollection System.ComponentModel.ICustomTypeDescriptor.GetEvents()
    {
        return TypeDescriptor.GetEvents(this, true);
    }
    public TypeConverter GetConverter()
    {
        return TypeDescriptor.GetConverter(this, true);
    }

    public object GetPropertyOwner(PropertyDescriptor pd)
    {
        return dictionary;
    }
    public AttributeCollection GetAttributes()
    {
        return TypeDescriptor.GetAttributes(this, true);
    }
    public object GetEditor(Type editorBaseType)
    {
        return TypeDescriptor.GetEditor(this, editorBaseType, true);
    }
    public PropertyDescriptor GetDefaultProperty()
    {
        return null;
    }
    PropertyDescriptorCollection 
        System.ComponentModel.ICustomTypeDescriptor.GetProperties()
    {
        return ((ICustomTypeDescriptor)this).GetProperties(new Attribute[] { });
    }
    public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
    {
        ArrayList properties = new ArrayList();
        foreach (DictionaryEntry e in dictionary)
        {
            properties.Add(new DictionaryPropertyDescriptor(dictionary, 
                e.Key.ToString()));
        }
        PropertyDescriptor[] props = 
            (PropertyDescriptor[])properties.ToArray(typeof(PropertyDescriptor));
        return new PropertyDescriptorCollection(props);
    }
}
public class DictionaryPropertyDescriptor : PropertyDescriptor
{
    IDictionary dictionary;
    string key;

    internal DictionaryPropertyDescriptor(IDictionary d, string k)
        : base(k.ToString(), null)
    {
        dictionary = d;
        key = k;
    }
    public override Type PropertyType
    {
        get { return dictionary[key].GetType(); }
    }
    public override void SetValue(object component, object value)
    {
        dictionary[key] = value;
    }
    public override object GetValue(object component)
    {
        return dictionary[key];
    }
    public override bool IsReadOnly
    {
        get { return false; }
    }
    public override Type ComponentType
    {
        get { return null; }
    }
    public override bool CanResetValue(object component)
    {
        return false;
    }
    public override void ResetValue(object component)
    {
    }
    public override bool ShouldSerializeValue(object component)
    {
        return false;
    }
}

Option 2 - Use a DataTable

Also as a simple option you can shape the Dictionary to a DataTable and edit data.

You can create extension methods for this task:

public static class DictionaryExtensions
{
    public static DataTable ToDataTable<T>(this Dictionary<string, T> dictionary)
    {
        var dt = new DataTable();
        dictionary.Keys.ToList().ForEach(x => dt.Columns.Add(x, typeof(T)));
        dt.Rows.Add(dictionary.Values.Cast<object>().ToArray());
        return dt;
    }
    public static void UpdateFromDataTable<T>(this Dictionary<string, T> dictionary, 
        DataTable table)
    {
        if (table.Rows.Count == 1)
            table.Columns.Cast<DataColumn>().ToList().ForEach(x => 
                dictionary[x.ColumnName] = table.Rows[0].Field<T>(x.ColumnName));
    }
}

And use these extension methods this way:

Dictionary<string, int> dictionary;
public void Form1_Load(object sender, EventArgs e)
{
    dictionary = new Dictionary<string, int>() { { "A", 1 }, { "B", 2 }, { "C", 3 } };
    dataGridView1.DataSource = dictionary.ToDataTable();
}
private void button1_Click(object sender, EventArgs e)
{
    dictionary.UpdateFromDataTable(dataGridView1.DataSource as DataTable);
}

Upvotes: 2

Related Questions