绑定到字典的 DataGridView

Posted

技术标签:

【中文标题】绑定到字典的 DataGridView【英文标题】:DataGridView bound to a Dictionary 【发布时间】:2018-06-27 14:23:48 【问题描述】:

我有一个包含商品和价格的Dictionary。这些项目是唯一的,但会在应用程序的整个生命周期中慢慢添加和更新(也就是说,我事先不知道项目字符串)。我想将此结构绑定到 DataGridView,这样我就可以在我的表单上显示更新,例如:

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

但不能,因为Dictionary 没有实现IList(或IListSourceIBindingListIBindingListView)。

有没有办法做到这一点?我需要保留唯一的商品列表,还要更新现有商品的价格,因此Dictionary 是我认为的理想数据结构,但我找不到在表单上显示数据的方法。


更新:

Marc 下面的建议效果很好,但我仍然不确定如何在执行期间更新 DataGridView。

我有一个类级变量:

private DictionaryBindingList<string, decimal> bList; 

然后在Main() 中实例化它:

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

那么在程序执行期间,如果一个新条目被添加到字典中:

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

我认为这会刷新 DataGridView。为什么不呢?

【问题讨论】:

顺便说一句 - 对于“价格”,您通常应该使用小数,而不是双数 更新了你的评论;除非我们将 Dictionary 子类化,否则没有简单的方法可以知道您添加了一个项目;而不是这个,我添加了一个重新填充列表的 Reset() 方法(带有示例用法)。有关示例,请参见表单底部的按钮。 【参考方案1】:

作为 Bleiers DictionaryBindingList 的扩展,我做了一个小改动,以允许添加值覆盖现有值。 我正在使用带有 WAMP websocket 的方法,因此它允许我仅通过更新集合来保持值更新,接下来我需要将事件绑定到值上。

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

【讨论】:

【参考方案2】:

作为对 Marc 建议的扩展,我想提出以下解决方案,该解决方案还允许对字典进行运行时操作:

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

【讨论】:

【参考方案3】:

我认为这将解决我几个月前遇到的问题。

在您想要更新商品价格时使用字典,并且当您完成更新并希望在数据网格中显示时,只需执行此操作。希望能帮到你

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

【讨论】:

【参考方案4】:

这可能是最简单的方法:

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

如果使用这个,你的 datagridview 应该是可排序的。

【讨论】:

【参考方案5】:

或者,在 LINQ 中,它又好又快:

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

这应该可以绑定到“Item”和“Price”列。

要将其用作网格视图中的数据源,您只需在其后面加上ToArray()

dataGridView1.DataSource = _priceDataArray.ToArray();

【讨论】:

这适用于将模型priceData 加载到视图dataGridView1 中,但数据变得不可编辑。这种不良副作用是否可以预料? 优秀而干净的方式!显然,由于我们通过 linq 创建的匿名类型,数据现在是只读的。借助CellDoubleClick 事件的手动干预,您可以负责解锁单元格(dataGridView1.CurrentCell.ReadOnly = false;)并开始编辑模式(dataGridView1.BeginEdit(false);)。接下来在CellValidating 事件中,可以抓取新数据并手动存储回字典:_priceData[dataGridView1[0, dataGridView1.CurrentRow.Index].Value.ToString()] = e.FormattedValue; 当然,绑定必须更新。 只是补充一下,为了实现程序生命周期内的datagridview自动更新,声明一个匿名ienumerable类型的表单范围变量为WithEvents(在表单加载事件中分配linq枚举)。注意:不要添加 ToArray() 部分。【参考方案6】:

像这样创建一个类:

class MyRow

    public string key;
    public double value;
    public string Key get return key;
    public string Value get return value;

然后列出它们:

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

然后将它们插入到该列表中,并将数据绑定到该列表中。

顺便说一句,如果你有 LINQ,我认为有一个 ToArray 方法可以简化这一切......

【讨论】:

列表不容易强制唯一性,并且像这样更新列表结构中的值也不容易,这就是我更喜欢使用字典的原因。不过还是谢谢。【参考方案7】:

Dictionary 有几个问题;第一个是(如您所见)它没有实现必要的IList/IListSource。第二个是项目没有保证的顺序(实际上,没有索引器),使得通过索引(而不是按键)进行随机访问是不可能的。

但是...这可能是一些烟雾和镜子是可行的;如下所示:

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

    

请注意,自定义扩展方法的使用完全是可选的,可以在 C# 2.0 等中删除,只需使用 new DictionaryBindingList&lt;string,decimal&gt;(prices) 代替。

【讨论】:

【参考方案8】:

对于Dictionary&lt;TKey, TValue&gt;,您可以使用以下关键字进行绑定:KeyValue

这是ComboBox 绑定的示例,但可以将字典绑定到DataGridView(将DataPropertyName 列设置为KeyValue)。

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

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

【讨论】:

以上是关于绑定到字典的 DataGridView的主要内容,如果未能解决你的问题,请参考以下文章

绑定到字典的 DataGridView

将 SwiftUI Picker 绑定到字典中保存的值

ASP.NET MVC 绑定到字典

字典到 ListView 双向绑定 - 可能吗?

如何在 WinForms 中将字典绑定到 ListBox

在 C# .net winforms 中将字典绑定到 ComboBox