31

I have a Dictionary that contains items and prices. The items are unique but slowly get added and updated through the lifetime of the application (that is, I don't know the item strings in advance). I would like to bind this structure to a DataGridView, so I can show updates on my Form, something like:

Dictionary<string, double> _priceData = new Dictionary<string, double>();
BindingSource _bindingSource = new BindingSource();
dataGridView1.DataSource = _bindingSource;
_bindingSource.DataSource = _priceData;

But cannot, since Dictionary does not implement IList (or IListSource, IBindingList, or IBindingListView).

Is there a way to achieve this? I need to keep a unique list of items, but also update the price for an existing item, so a Dictionary is the ideal data structure I think, but I cannot find a way to display the data on my Form.


Update:

Marc's suggestion below works very nicely, but I'm still not sure how to update the DataGridView during execution.

I have a class-level variable:

private DictionaryBindingList<string, decimal> bList; 

Then instantiate that in Main():

bList = new DictionaryBindingList<string,decimal>(prices); 
dgv.DataSource = bList; 

Then during program execution if a new entry is added to the dictionary:

prices.Add("foobar", 234.56M); bList.ResetBindings(); 

I thought that would refresh the DataGridView. Why not?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
WillH
  • 2,086
  • 6
  • 23
  • 40
  • 4
    as an aside - for "price" you should normally use decimal, not double – Marc Gravell May 12 '09 at 21:35
  • Updated re your comment; unless we subclass Dictionary<,>, there is no simple way to know that you've added an item; rather than this, I've added a Reset() method (with example usage) that re-populates the list. See the button at the bottom of the form for an example. – Marc Gravell May 13 '09 at 09:51

8 Answers8

66

Or, in LINQ, it's nice and quick:

var _priceDataArray = from row in _priceData select new { Item = row.Key, Price = row.Value };

That should then be bindable, to the columns 'Item' and 'Price'.

To use it as a data source in a grid view, you just have to follow it with ToArray().

dataGridView1.DataSource = _priceDataArray.ToArray();
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Chris
  • 39,719
  • 45
  • 189
  • 235
  • 5
    This works for loading the model `priceData` into the view `dataGridView1`, but the data became uneditable. Is this undesired side effect to be expected? – whiteshooz Jun 03 '14 at 21:02
  • Excellent and clean way! Obviously, the data is read only now, due to the anonymous type we created via linq. With the help of manual intervention via `CellDoubleClick` event, one can take care of unlocking the cell (`dataGridView1.CurrentCell.ReadOnly = false;`) and begin the edit mode (`dataGridView1.BeginEdit(false);`). Next in `CellValidating` event, the new data can be snatched and manually stored back to the dictionary: `_priceData[dataGridView1[0, dataGridView1.CurrentRow.Index].Value.ToString()] = e.FormattedValue;` The binding has to renewed than, of course. – Nicolas Feb 05 '16 at 14:19
  • Just to add, in order to achieve automatic datagridview updating in the program lifetime, declare a form wide variable for the anonymous ienumerable type as WithEvents (assigning the linq enumeration inside the form load event). NOTE: Do not add the ToArray() part. – Nepaluz Jan 09 '17 at 16:32
26

There are a couple of issues with Dictionary; the first is (as you've found) it doesn't implement the necessary IList/IListSource. The second is that there is no guaranteed order to the items (and indeed, no indexer), making random access by index (rather than by key) impossible.

However... it is probably doable with some some smoke and mirrors; something like below:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;

static class Program
{
    [STAThread]
    static void Main()
    {
        Dictionary<string, decimal> prices =
            new Dictionary<string, decimal>();
        prices.Add("foo", 123.45M);
        prices.Add("bar", 678.90M);

        Application.EnableVisualStyles();
        Form form = new Form();
        DataGridView dgv = new DataGridView();
        dgv.Dock = DockStyle.Fill;
        form.Controls.Add(dgv);
        var bl = prices.ToBindingList();
        dgv.DataSource = bl;
        Button btn = new Button();
        btn.Dock = DockStyle.Bottom;
        btn.Click += delegate
        {
            prices.Add(new Random().Next().ToString(), 0.1M);
            bl.Reset();
        };
        form.Controls.Add(btn);
        Application.Run(form);
    }

    public static DictionaryBindingList<TKey, TValue>
        ToBindingList<TKey, TValue>(this IDictionary<TKey, TValue> data)
    {
        return new DictionaryBindingList<TKey, TValue>(data);
    }
    public sealed class Pair<TKey, TValue>
    {
        private readonly TKey key;
        private readonly IDictionary<TKey, TValue> data;
        public Pair(TKey key, IDictionary<TKey, TValue> data)
        {
            this.key = key;
            this.data = data;
        }
        public TKey Key { get { return key; } }
        public TValue Value
        {
            get
            {
                TValue value;
                data.TryGetValue(key, out value);
                return value;
            }
            set { data[key] = value; }
        }
    }
    public class DictionaryBindingList<TKey, TValue>
        : BindingList<Pair<TKey, TValue>>
    {
        private readonly IDictionary<TKey, TValue> data;
        public DictionaryBindingList(IDictionary<TKey, TValue> data)
        {
            this.data = data;
            Reset();
        }
        public void Reset()
        {
            bool oldRaise = RaiseListChangedEvents;
            RaiseListChangedEvents = false;
            try
            {
                Clear();
                foreach (TKey key in data.Keys)
                {
                    Add(new Pair<TKey, TValue>(key, data));
                }
            }
            finally
            {
                RaiseListChangedEvents = oldRaise;
                ResetBindings();
            }
        }

    }
}

Note that the use of a custom extension method is entirely optional, and can be removed in C# 2.0, etc. by just using new DictionaryBindingList<string,decimal>(prices) instead.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
7

Probably this is easiest way:

Dictionary<char, double> myList = new Dictionary<char, double>();

        dataGridView1.Columns.Add("Key", "KEY");
        dataGridView1.Columns.Add("Values", "VALUES");

        foreach (KeyValuePair<char,double> item in , myList)
        {
            dataGridView1.Rows.Add(item.Key, item.Value);
        }

If use this you datagridview shall be sortable.

DevQuayle
  • 338
  • 1
  • 4
  • 13
5

i thing this will resolve your problem which i faced few months ago.

use dictionay as you want to update item prices and just when u finish updation and want to show in datagrid just do this. hope will help you

Grd.DataSource=null;
Grd.DataSource = Dictionary.Values.ToList();
adnan umar
  • 127
  • 1
  • 5
2

For Dictionary<TKey, TValue> you can use these keywords for binding: Key and Value.

Here is example for ComboBox Binding, but it's possible to bind dictionary to DataGridView (set DataPropertyName for column to Key or Value).

    ComboBox1.DataSource =
        new BindingSource(Pricelevel.GetPricelevels(), null); // GetPricelevels() returns Dictionary<string, string>

    ComboBox1.ValueMember = "Key";
    ComboBox1.DisplayMember = "Value";
Vladislav
  • 1,696
  • 27
  • 37
1

As an extension to Marc's suggestion, I would like to propose the following solution that will also allow run-time manipulation of the dictionary:

public class DictionaryBindingList<TKey, TValue> : BindingList<KeyValuePair<TKey, TValue>>
{
    public readonly IDictionary<TKey, TValue> Dictionary;
    public DictionaryBindingList()
    {
        Dictionary = new Dictionary<TKey, TValue>();
    }

    public void Add(TKey key, TValue value)
    {
        base.Add(new KeyValuePair<TKey, TValue>(key, value));
    }

    public void Remove(TKey key)
    {
        var item = this.First(x => x.Key.Equals(key));
        base.Remove(item);
    }

    protected override void InsertItem(int index, KeyValuePair<TKey, TValue> item)
    {
        Dictionary.Add(item.Key, item.Value);
        base.InsertItem(index, item);
    }

    protected override void RemoveItem(int index)
    {
        Dictionary.Remove(this[index].Key);
        base.RemoveItem(index);
    }

    public int IndexOf(TKey key)
    {
        var item = this.FirstOrDefault(x => x.Key.Equals(key));
        return item.Equals(null) ? -1 : base.IndexOf(item);
    }
}
S. Bleier
  • 79
  • 4
1

Make a class like so:

class MyRow
{
    public string key;
    public double value;
    public string Key {get {return key;}}
    public string Value {get {return value;}}
}

Then make a list of them:

List<MyRow> rows = new List<MyRow>();

Then insert them into that list, and databind to the list.

As an aside, if you've got LINQ, I think there's a ToArray method that'll simplify all this...

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Chris
  • 39,719
  • 45
  • 189
  • 235
  • Lists don't easily enforce uniqueness, and it isn't easy to update a value in a list structure like that which is why I'd prefer to use a Dictionary. Thanks though. – WillH May 13 '09 at 09:29
0

As an extension of Bleiers DictionaryBindingList I made a small alteration to allow Add values to overwrite existing values. I'm using the method with a WAMP websocket so it would allow me to keep values updated just by updating the collection, next I need to tie events onto the values.

    public void Add(TKey key, TValue value)
    {
        if (Dictionary.ContainsKey(key))
        {
            int position = IndexOf(key);
            Dictionary.Remove(key);
            Remove(key);
            InsertItem(position, new KeyValuePair<TKey, TValue>(key, value));
            return;
        }
        base.Add(new KeyValuePair<TKey, TValue>(key, value));
    }
David Rath
  • 31
  • 4