web-dev-qa-db-fra.com

DataGridView lié à un dictionnaire

J'ai une Dictionary qui contient des articles et des prix. Les éléments sont uniques mais sont lentement ajoutés et mis à jour tout au long de la vie de l’application (c’est-à-dire que je ne connais pas les chaînes d’élément à l’avance). J'aimerais lier cette structure à un DataGridView afin de pouvoir afficher les mises à jour de mon formulaire, par exemple:

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

Mais ne le pouvez pas, car Dictionary n'implémente pas IList (ou IListSource, IBindingList ou IBindingListView).

Y a-t-il un moyen d'y parvenir? Je dois conserver une liste d'éléments unique, mais également mettre à jour le prix d'un élément existant. La structure Dictionary est donc la structure de données idéale, mais je ne trouve pas le moyen d'afficher les données de mon formulaire.


Mettre à jour:

La suggestion de Marc ci-dessous fonctionne très bien, mais je ne sais toujours pas comment mettre à jour DataGridView pendant son exécution.

J'ai une variable de niveau de classe: 

private DictionaryBindingList<string, decimal> bList; 

Puis instancier cela dans Main():

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

Ensuite, pendant l'exécution du programme, si une nouvelle entrée est ajoutée au dictionnaire: 

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

Je pensais que cela rafraîchirait le DataGridView. Pourquoi pas?

27
WillH

Il y a quelques problèmes avec Dictionary; le premier est (comme vous l'avez constaté) qu'il n'implémente pas la variable IList/IListSource nécessaire. La seconde est qu'il n'y a pas d'ordre garanti pour les éléments (et en fait, pas d'indexeur), rendant l'accès aléatoire par index (plutôt que par clé) impossible.

Cependant ... c'est probablement faisable avec de la fumée et des miroirs; quelque chose comme ci-dessous:

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();
            }
        }

    }
}

Notez que l'utilisation d'une méthode d'extension personnalisée est entièrement facultative et peut être supprimée en C # 2.0, etc. en utilisant simplement new DictionaryBindingList<string,decimal>(prices) à la place.

22
Marc Gravell

Ou, dans LINQ, c'est gentil et rapide:

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

Cela devrait alors être lié aux colonnes "Item" et "Price".

Pour l'utiliser comme source de données dans une vue en grille, il vous suffit de le suivre avec ToArray().

dataGridView1.DataSource = _priceDataArray.ToArray();
55
Chris

C'est probablement le moyen le plus simple:

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);
        }

Si vous utilisez this, datagridview doit pouvoir être trié.

5
DevQuayle

je pense que cela résoudra votre problème auquel j’ai été confronté il ya quelques mois.

utilisez dictionay pour mettre à jour les prix des articles et lorsque vous avez terminé la mise à jour et que vous souhaitez afficher dans la grille de données, faites simplement cela. l'espoir vous aidera

Grd.DataSource=null;
Grd.DataSource = Dictionary.Values.ToList();
3
adnan umar

Pour Dictionary<TKey, TValue>, vous pouvez utiliser ces mots clés pour lier: Key et Value.

Voici un exemple pour ComboBox Binding, mais il est possible de lier dictionnaire à DataGridView (définissez DataPropertyName pour la colonne sur Key ou Value.

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

    ComboBox1.ValueMember = "Key";
    ComboBox1.DisplayMember = "Value";
2
Vladislav

Faites un cours comme ça:

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

Puis faites-en une liste:

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

Ensuite, insérez-les dans cette liste, et databind à la liste.

En passant, si vous avez LINQ, je pense qu'il existe une méthode ToArray qui simplifiera tout cela ...

1
Chris

Dans le prolongement de la suggestion de Marc, je voudrais proposer la solution suivante qui permettra également une manipulation du dictionnaire au moment de l'exécution:

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);
    }
}
1
S. Bleier

En tant qu'extension de Bleiers DictionaryBindingList, j'ai apporté une petite modification pour permettre à Add de remplacer les valeurs existantes . J'utilise la méthode avec un Websocket WAMP afin de me permettre de conserver les valeurs mises à jour simplement en mettant à jour la collection. I suivant besoin de lier les événements sur les valeurs.

    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));
    }
0
David Rath