2009-05-12 17 views
25

Ho un Dictionary che contiene articoli e prezzi. Gli articoli sono unici ma vengono lentamente aggiunti e aggiornati durante la vita dell'applicazione (ovvero, non conosco le stringhe dell'articolo in anticipo). Vorrei legare questa struttura ad un DataGridView, in modo da poter mostrare gli aggiornamenti sulla mia forma, qualcosa di simile:DataGridView associato a un dizionario

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

ma non può, in quanto Dictionary non implementa IList (o IListSource, IBindingList o IBindingListView).

C'è un modo per raggiungere questo obiettivo? Devo mantenere un elenco univoco di articoli, ma anche aggiornare il prezzo per un articolo esistente, quindi una Dictionary è la struttura dati ideale, ma non riesco a trovare un modo per visualizzare i dati sul mio modulo.


Aggiornamento: suggerimento

di Marc sotto funziona molto bene, ma non sono ancora sicuro di come aggiornare il DataGridView durante l'esecuzione.

Ho una variabile a livello di classe:

private DictionaryBindingList<string, decimal> bList; 

Poi istanziare che nel Main():

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

Poi, durante l'esecuzione del programma, se una nuova voce si aggiunge al dizionario:

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

Ho pensato che avrebbe aggiornato il DataGridView. Perchè no?

+3

come una parte - per "prezzo" si dovrebbe normalmente utilizzare decimale, non doppio –

+0

Aggiornato re il tuo commento; a meno che non ci sottoclassi il dizionario <,>, non esiste un modo semplice per sapere che hai aggiunto un elemento; piuttosto che questo, ho aggiunto un metodo Reset() (con l'uso di esempio) che ri-popola la lista. Vedere il pulsante nella parte inferiore del modulo per un esempio. –

risposta

21

Ci sono un paio di problemi con Dictionary; il primo è (come hai trovato) non implementa il necessario IList/IListSource. Il secondo è che non esiste un ordine garantito per gli oggetti (e in effetti, nessun indicizzatore), rendendo impossibile l'accesso casuale per indice (piuttosto che per chiave).

Tuttavia ... è probabilmente fattibile con un po 'di fumo e specchi; qualcosa come di seguito:

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

    } 
} 

Si noti che l'uso di un metodo di estensione personalizzata è del tutto facoltativo, e può essere rimossa in C# 2.0, ecc semplicemente utilizzando new DictionaryBindingList<string,decimal>(prices) invece.

1

fare una classe in questo modo:

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

poi fare una lista di loro:

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

Poi inserirle in quella lista, e DataBind alla lista.

Per inciso, se hai LINQ, penso che ci sia un metodo ToArray che ti semplifica tutto questo ...

+0

Gli elenchi non impongono facilmente l'univocità e non è facile aggiornare un valore in una struttura di lista come quella, per cui preferirei usare un dizionario. Grazie però. – WillH

47

Oppure, in LINQ, è bello e veloce:

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

Questo dovrebbe poi essere associabile, alle colonne 'voce' e 'prezzo'.

Per utilizzarlo come origine dati in una vista griglia, è sufficiente seguirlo con ToArray().

dataGridView1.DataSource = _priceDataArray.ToArray(); 
+7

+1 Molto più semplice e pulito rispetto alla risposta accettata. – GuiSim

+2

+1 ha funzionato per me! Facile come torta (non pi)! –

+0

Facile come torta? In realtà trovo la torta abbastanza difficile da ottenere a destra: P –

2

Per Dictionary<TKey, TValue> è possibile utilizzare queste parole chiave per il legame: Key e Value.

Ecco esempio per ComboBox Binding, ma è possibile associare dizionario per DataGridView (impostare DataPropertyName per la colonna a Key o Value).

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

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

io cosa risolverà il problema che ho affrontato pochi mesi fa.

usare dizione come si desidera aggiornare i prezzi degli articoli e solo quando si termina l'aggiornamento e si desidera visualizzare in datagrid basta fare questo. spero vi aiuterà a

Grd.DataSource=null; 
Grd.DataSource = Dictionary.Values.ToList(); 
5

Probabilmente questo è modo più semplice:

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

Se uso questo si datagridview sarà ordinabile.

1

Come estensione suggerimento di Marc, desidero proporre la seguente soluzione che consentirà anche la manipolazione runtime del dizionario:

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

Come estensione Bleiers DictionaryBindingList feci un piccolo alterazione consenti Aggiungi valori per sovrascrivere i valori esistenti. Sto usando il metodo con una websocket WAMP in modo da permettermi di mantenere i valori aggiornati semplicemente aggiornando la collezione, quindi ho bisogno di legare gli eventi ai valori.

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