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?
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();
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.
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.
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();
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";
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...
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);
}
}
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));
}
Related
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?
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();
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.
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.
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();
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";
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...
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);
}
}
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));
}
I want to create a new column and then add values to only that new column on a button click event. Is that possible? The column might have a few rows underneath it, depending the amount of items in a quote.
What I have achieved so far:
My class where all my information is stored in
public class ViewQuoteItemList
{
...
public double SupplierCost { get; set; }
...
}
I can create my column and bind it to my ViewQuoteItemList class
DataGridTextColumn columnFeedbackSupplier = new DataGridTextColumn();
columnFeedbackSupplier.Binding = new Binding("SupplierCost");
//The header of the column gets it's value from a combobox where you select a company to be added to the datagrid
columnFeedbackSupplier.Header = (cmbFeedbackSelectSupplier.SelectedItem as DisplayItems).Name;
From here I get my quote items from a different datagrid and I add them to my second datagrid where I want to add the new column and it's values
IList list = dgFeedbackAddCost.SelectedItems as IList;
IEnumerable<ViewQuoteItemList> items = list.Cast<ViewQuoteItemList>();
var collection = (from i in items
let a = new ViewQuoteItemList { SupplierCost = 0 }
select a).ToList();
Lastly, I add the new column to the second datagrid and set the collection as the datagrid's ItemSource
dgFeedbackSelectSupplier.Columns.Add(columnFeedbackSupplier);
dgFeedbackSelectSupplier.ItemsSource = collection;
My problem is that once I edit a cell of data from one of the suppliers, the whole row gets updated with that one value, because it's all bound to one class/item source. Can this be fixed?
EDIT:
"The whole row gets updated" means that every time I insert a value into one cell in a row, every single cell in that row gets updated with the same value. Here are some pictures showing what I mean. I want to edit all the data and this all happens on my second datagrid(dgFeedbackSupplier).
Here, I have two companies added with the 4 items that I want to compare prices with. Now I want to click on a single cell underneath a company and add a value for a certain item.
Here I double click on a cell to edit/change the value.
Then when I change my value in the selected cell, every other company's value for that specific item in the same row gets updated with that same value.
That's my problem and I need to have only that one cell's value changed, and not the whole row.
EDIT 2:
How can I convert this collection to ExpandoObject?
var collection = (from i in items
let a = new ViewQuoteItemList { Item = i.Item, Supplier = 25 }
select a).ToList();
EDIT 3:
My XAML:
<DataGrid x:Name="dgFeedbackSelectSupplier" Margin="245,266,0,32" BorderBrush="#FFADADAD" ColumnWidth="*" AutoGenerateColumns="True" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn x:Name="columnFeedbackSupplierItem" IsReadOnly="True" Header="Item" Binding="{Binding Item}"/>
</DataGrid.Columns>
</DataGrid>
And this is how my whole method looks like at the moment where I add my Columns and where I get the items from my other datagrid:
private void btnFeedbackSelectSupplier_Click(object sender, RoutedEventArgs e)
{
supplier.Id = (cmbFeedbackSelectSupplier.SelectedItem as DisplayItems).Id;//Not using yet
supplier.Name = (cmbFeedbackSelectSupplier.SelectedItem as DisplayItems).Name;
DataGridTextColumn columnFeedbackSupplier = new DataGridTextColumn();
columnFeedbackSupplier.Binding = new Binding("Supplier") { Mode = BindingMode.TwoWay };
columnFeedbackSupplier.CanUserReorder = true;
columnFeedbackSupplier.CanUserResize = true;
columnFeedbackSupplier.IsReadOnly = false;
columnFeedbackSupplier.Header = supplier.Name;
dgFeedbackAddCost.SelectAll();
IList list = dgFeedbackAddCost.SelectedItems as IList;
IEnumerable<ViewQuoteItemList> items = list.Cast<ViewQuoteItemList>();
var collection = new List<ExpandoObject>();
foreach (var i in items)
{
dynamic a = new ExpandoObject();
a.Id = (cmbFeedbackSelectSupplier.SelectedItem as DisplayItems).Id;
a.Item = i.Item;
a.Supplier = 25;
collection.Add(a);
}
dgFeedbackSelectSupplier.Columns.Add(columnFeedbackSupplier);
dgFeedbackSelectSupplier.ItemsSource = collection;
}
May I propose another approach? From what I understand;
You have suppliers and these suppliers have items. You can add new suppliers. I assumed suppliers may not have all the items (for a challenge :)).
You want to present this data structure in a grid like view.
The problem here is that you are trying to display a non-tabular data with a tabular view component. What you have is hierarchical data. Before I continue with my solution here are some screen shots.
What I basically did here is to create new views on the hierarchical data, filling the gaps and turn it into a tabular form. I used fake classes for the empty slots so I could easily select the proper datatemplates in XAML. I avoided to use any custom code and kept everything in MVVM+XAML way. So bindings and such works as expected.
The new views had to be updated when the collections changed, so I used the messenger class from MVVMLight for easy implementation, and called RaisePropertyChanged events manually.
In XAML I used ItemsControl and UniformGrid components to create the grid like view.
I put the complete solution on GitHub: https://github.com/orhtun/GridLikeViewWithDynamicColumns
It creates random data on each run. If you get build errors, try right click on the solution and Restore NuGet Packages.
You can't use a collection to bind with your second DataGrid cause your view is dynamic can can have variable no of columns.
You should use a DataTable as a source to your DataGrid. Instead of adding a column in grid add a column in DataTable and AutoGenerate the column in the Grid.
A method to transform item source(any type) to DataTable is(may be you need this first time):
public static DataTable DataGridtoDataTable(DataGrid dg)
{
dg.SelectAllCells();
dg.ClipboardCopyMode = DataGridClipboardCopyMode.IncludeHeader;
ApplicationCommands.Copy.Execute(null, dg);
dg.UnselectAllCells();
String result = (string)Clipboard.GetData(DataFormats.CommaSeparatedValue);
string[] Lines = result.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None);
string[] Fields;
Fields = Lines[0].Split(new char[] { ',' });
int Cols = Fields.GetLength(0);
DataTable dt = new DataTable();
for (int i = 0; i < Cols; i++)
dt.Columns.Add(Fields[i].ToUpper(), typeof(string));
DataRow Row;
for (int i = 1; i < Lines.GetLength(0) - 1; i++)
{
Fields = Lines[i].Split(new char[] { ',' });
Row = dt.NewRow();
for (int f = 0; f < Cols; f++)
{
Row[f] = Fields[f];
}
dt.Rows.Add(Row);
}
return dt;
}
then later when you have to add a column in the DataGrid, add a column in a table by iterating on collection to fill your column in data table.
There is no simple way to do your problem as your scenario is unique and only a dynamic structure can fullfill your need.
Since by using the DataTable your all cell will be bound to unique entities. Only one cell will get updated on a single change of DataGrid cell.(Unlike collection where multiple cells are bound to same property)
For your requirement I would suggest you to use Microsoft Dynamics ExpandoObject
You need to create a List<ExpandoObject> which is nothing but a Dictionary<string,object> wherein your key is your property name and object is your value. So in your case,
All the properties in ViewQuoteItem are added to this new Object and when you need to add a column you can add another property to your object. Then just update your view and see new column added to grid.
To use Expando, you can either do it the easy way -
var dynamicItem = New ExpandoObject();
dynamicItem.Item = "Test";
dynamicItem.BarlwoodCityDeep = (int)350;
or you can treat dynamicItem as dictionary like this -
IDictionary<String, Object> dynamicDict = dynamicItem
dynamicDict.Add("MyNewProperty","MySomeValue")
I personally find converting to Dict easy as it gives me flexibility to add properties explicitly and then use keys to refer to them but its just an ease not compulsion.
I would also recommend you to have a method that maps your dataList to new expandoList and bind expandlist to your view, please make sure that you use AutoGeneration of columns in WPF so that newly added columns are visible.
You can have a look at my solution here that worked for me in somewhat similar case.
If you are new to dynamics, you might find it bit tricky but Expandos are treat to work with and ease to work with them is amazing.
to convert from ViewQuoteItemList to Expando --
var collection = new List<ExpandoObject>();
foreach (var i in items)
{
dynamic a = new ExpandoObject();
a.Item = i.item;
a.Supplier = 25;
collection.Add(a);
}
Related article here and another interesting one here
Personally, I would keep away from actually instantiating individual columns, instead add them to the DataGridView directly, and then manipulate their properties.
List<MyClass> myList = new List<MyClass>();
BindingList<MyClass> bList = new BindingList<MyClass>(myList);
myDataGridView.DataSource = new BindingSource(bList,null);
//Now Lets add a custom column..
myDataGridView.Columns.Add("Text","Text");
//Now lets edit it's properties
myDataGridView.Columns["Text"].ReadOnly = false;
myDataGridView.EditMode = DataGridViewEditMode.EditOnKeystroke;
//Now lets walk through and throw some data in each cell..
if(myDataGridView.Rows.Count > 1)
{
for(int i = 0;i < myDataGridView.Rows.Count;i++)
{
myDataGridView.Rows[i].Cells["Text"].Value = "My Super Useful Text";
}
}
Avoiding use of instantiating the column, and then adding it has helped me in the past with weird linkage issues, for instance editing one cell, and others change. As for sub-views etc, I can't say I can comment much on that.
I see that this question is getting quite a bit of attention, so I will post the solution to my answer. I hope this will help someone out there to get some idea on how to add data only to a specific column. :)
Just as a side note - This is a test application that I created, therefore the coding will not be the same as the coding in my original question. Also I used a dictionary to solve my problem. It works great!
Creating my Item and Supplier lists:
//I create my dummy suppliers
private string[] CONST_Supplies = { "Supplier 1", "Supplier 2", "Supplier 3", "Supplier 4" };
public MainWindow()
{
InitializeComponent();
//I add my dummy items into my datagrid
//These are the items that I want to compare prices with
List<ViewQuoteItemList> list = new List<ViewQuoteItemList>();
list.Add(new ViewQuoteItemList() { Item = "Item 1" });
list.Add(new ViewQuoteItemList() { Item = "Item 2" });
list.Add(new ViewQuoteItemList() { Item = "Item 3" });
list.Add(new ViewQuoteItemList() { Item = "Item 4" });
list.Add(new ViewQuoteItemList() { Item = "Item 5" });
list.Add(new ViewQuoteItemList() { Item = "Item 6" });
//Loading the items into the datagrid on application start
DataGridTest.ItemsSource = list;
//Adding my dummy suppliers to my supplier selection combobox
foreach (var supplier in CONST_Supplies)
ComboBoxTest.Items.Add(supplier);
}
My button click event:
private void Add_Click(object sender, RoutedEventArgs e)
{
//Select my supplier from my Supplier's combobox
var supplier = ComboBoxTest.SelectedItem as string;
//Create the Supplier column and bind it to my 'ViewQuoteItemList' class +
//I'm binding it to the unique supplier selected from my combobox
DataGridTextColumn columnFeedbackSupplier = new DataGridTextColumn();
columnFeedbackSupplier.Binding = new Binding("Suppliers[" + supplier + "]");
columnFeedbackSupplier.Binding.FallbackValue = "Binding failed";
columnFeedbackSupplier.CanUserReorder = true;
columnFeedbackSupplier.CanUserResize = true;
columnFeedbackSupplier.IsReadOnly = false;
columnFeedbackSupplier.Header = ComboBoxTest.SelectedItem as string;
foreach (var item in DataGridTest.ItemsSource as List<ViewQuoteItemList>)
if (!item.Suppliers.ContainsKey(supplier))
item.Suppliers.Add(supplier, string.Empty);
DataGridTest.Columns.Add(columnFeedbackSupplier);
}
My class:
public class ViewQuoteItemList
{
public ViewQuoteItemList()
{
Suppliers = new ObservableDictionary<string, string>();
}
public int Id { get; set; }
public string Item { get; set; }
public ObservableDictionary<string, string> Suppliers { get; set; }
}
And my Observable Dictionary where a lot of the work happens:
public class ObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged
{
private const string CountString = "Count";
private const string IndexerName = "Item[]";
private const string KeysName = "Keys";
private const string ValuesName = "Values";
private IDictionary<TKey, TValue> _Dictionary;
protected IDictionary<TKey, TValue> Dictionary
{
get { return _Dictionary; }
}
#region Constructors
public ObservableDictionary()
{
_Dictionary = new Dictionary<TKey, TValue>();
}
public ObservableDictionary(IDictionary<TKey, TValue> dictionary)
{
_Dictionary = new Dictionary<TKey, TValue>(dictionary);
}
public ObservableDictionary(IEqualityComparer<TKey> comparer)
{
_Dictionary = new Dictionary<TKey, TValue>(comparer);
}
public ObservableDictionary(int capacity)
{
_Dictionary = new Dictionary<TKey, TValue>(capacity);
}
public ObservableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer)
{
_Dictionary = new Dictionary<TKey, TValue>(dictionary, comparer);
}
public ObservableDictionary(int capacity, IEqualityComparer<TKey> comparer)
{
_Dictionary = new Dictionary<TKey, TValue>(capacity, comparer);
}
#endregion
#region IDictionary<TKey,TValue> Members
public void Add(TKey key, TValue value)
{
Insert(key, value, true);
}
public bool ContainsKey(TKey key)
{
return Dictionary.ContainsKey(key);
}
public ICollection<TKey> Keys
{
get { return Dictionary.Keys; }
}
public bool Remove(TKey key)
{
if (key == null) throw new ArgumentNullException("key");
TValue value;
Dictionary.TryGetValue(key, out value);
var removed = Dictionary.Remove(key);
if (removed)
//OnCollectionChanged(NotifyCollectionChangedAction.Remove, new KeyValuePair<TKey, TValue>(key, value));
OnCollectionChanged();
return removed;
}
public bool TryGetValue(TKey key, out TValue value)
{
return Dictionary.TryGetValue(key, out value);
}
public ICollection<TValue> Values
{
get { return Dictionary.Values; }
}
public TValue this[TKey key]
{
get
{
return Dictionary[key];
}
set
{
Insert(key, value, false);
}
}
#endregion
#region ICollection<KeyValuePair<TKey,TValue>> Members
public void Add(KeyValuePair<TKey, TValue> item)
{
Insert(item.Key, item.Value, true);
}
public void Clear()
{
if (Dictionary.Count > 0)
{
Dictionary.Clear();
OnCollectionChanged();
}
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return Dictionary.Contains(item);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
Dictionary.CopyTo(array, arrayIndex);
}
public int Count
{
get { return Dictionary.Count; }
}
public bool IsReadOnly
{
get { return Dictionary.IsReadOnly; }
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return Remove(item.Key);
}
#endregion
#region IEnumerable<KeyValuePair<TKey,TValue>> Members
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return Dictionary.GetEnumerator();
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)Dictionary).GetEnumerator();
}
#endregion
#region INotifyCollectionChanged Members
public event NotifyCollectionChangedEventHandler CollectionChanged;
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
public void AddRange(IDictionary<TKey, TValue> items)
{
if (items == null) throw new ArgumentNullException("items");
if (items.Count > 0)
{
if (Dictionary.Count > 0)
{
if (items.Keys.Any((k) => Dictionary.ContainsKey(k)))
throw new ArgumentException("An item with the same key has already been added.");
else
foreach (var item in items) Dictionary.Add(item);
}
else
_Dictionary = new Dictionary<TKey, TValue>(items);
OnCollectionChanged(NotifyCollectionChangedAction.Add, items.ToArray());
}
}
private void Insert(TKey key, TValue value, bool add)
{
if (key == null) throw new ArgumentNullException("key");
TValue item;
if (Dictionary.TryGetValue(key, out item))
{
if (add) throw new ArgumentException("An item with the same key has already been added.");
if (Equals(item, value)) return;
Dictionary[key] = value;
OnCollectionChanged(NotifyCollectionChangedAction.Replace, new KeyValuePair<TKey, TValue>(key, value), new KeyValuePair<TKey, TValue>(key, item));
}
else
{
Dictionary[key] = value;
OnCollectionChanged(NotifyCollectionChangedAction.Add, new KeyValuePair<TKey, TValue>(key, value));
}
}
private void OnPropertyChanged()
{
OnPropertyChanged(CountString);
OnPropertyChanged(IndexerName);
OnPropertyChanged(KeysName);
OnPropertyChanged(ValuesName);
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private void OnCollectionChanged()
{
OnPropertyChanged();
if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> changedItem)
{
OnPropertyChanged();
if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, changedItem));
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> newItem, KeyValuePair<TKey, TValue> oldItem)
{
OnPropertyChanged();
if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, newItem, oldItem));
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, IList newItems)
{
OnPropertyChanged();
if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, newItems));
}
}
It's a lot of code, I know. Sorry I don't have the time to explain everything and how it works. I hope that you guys can figure this out by yourself ;) There is also a lot of extra functions in the class that you can use for various purposes.
Lastly, here is my XAML:
<Button x:Name="Add" Content="Add" HorizontalAlignment="Left" Margin="65,143,0,0" VerticalAlignment="Top" Width="75" Click="Add_Click"/>
<DataGrid x:Name="DataGridTest" CanUserAddRows="False" AutoGenerateColumns="False" HorizontalAlignment="Left" Margin="165,115,0,0" VerticalAlignment="Top" Height="279" Width="611" ColumnWidth="*">
<DataGrid.Columns>
<DataGridTextColumn x:Name="columnFeedbackSupplierItem" Header="Item" Binding="{Binding Item}"/>
</DataGrid.Columns>
</DataGrid>
<ComboBox x:Name="ComboBoxTest" HorizontalAlignment="Left" Margin="20,115,0,0" VerticalAlignment="Top" Width="120"/>
Note - The values in the datagrid can be edited when you double-click on a cell. Thank you for all the people that added to my question or helped me in the right direction. I hope this can help someone out there.
I am building a WPF UserControl. For this I implemented an ItemSource DependecyProperty like this:
private IEnumerable MisItems;
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(TextBoxAutoComplete), new PropertyMetadata(new PropertyChangedCallback(OnItemsSourcePropertyChanged)));
private static void OnItemsSourcePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var control = sender as TextBoxAutoComplete;
if (control != null)
control.OnItemsSourceChanged((IEnumerable)e.OldValue, (IEnumerable)e.NewValue);
}
private void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
MisItems = newValue;
// Remove handler for oldValue.CollectionChanged
var oldValueINotifyCollectionChanged = oldValue as INotifyCollectionChanged;
if (null != oldValueINotifyCollectionChanged)
{
oldValueINotifyCollectionChanged.CollectionChanged -= new NotifyCollectionChangedEventHandler(newValueINotifyCollectionChanged_CollectionChanged);
}
// Add handler for newValue.CollectionChanged (if possible)
var newValueINotifyCollectionChanged = newValue as INotifyCollectionChanged;
if (null != newValueINotifyCollectionChanged)
{
newValueINotifyCollectionChanged.CollectionChanged += new NotifyCollectionChangedEventHandler(newValueINotifyCollectionChanged_CollectionChanged);
}
}
void newValueINotifyCollectionChanged_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
//Do your stuff here.
}
The ItemsSource property is represented by a IEnumerable Object. Now I need to convert it to a Dictionary<object,string> in this function:
protected SearchResult DoSearch(string searchTerm)
{
if (!string.IsNullOrEmpty(searchTerm))
{
SearchResult sr = new SearchResult();
//var ItemsText = MisItems.GetType();
var p = (List<string>)MisItems;
/*sr.Results = ItemsText.Select((x, i) => new { x, i }).Where(x=>x.ToString().ToUpper().Contains(searchTerm.ToUpper()))
.ToDictionary(a => (object)a.i, a => a.x);*/
return sr;
}
else return new SearchResult();
}
How can i make the transition?
EDIT
More info:
My viewmodel has this property:
public List<EnumeradorWCFModel> Clientes { get; set; }
The data for this property is returned by a WCF service:
Clientes = _svc.Clientes_Enum(sTicket, "");
Then I wanted my UserControl to bind to this property. I create my control like this:
<autocomplete:TextBoxAutoComplete x:Name="Clientes" ItemsSource = "{Binding Path=Clientes}" DisplayMemberPath="Descripcion" Height="25"/>
[s]Alright. You posted a lot of code (that I personally think is unnecessary for what you're trying to do).
Let's slim it down.
You have an IEnumerable<string> to start out, correct? Good.
There's a ToDictionary() extension method in the LINQ libraries. Documentation is here.
So what you need to do is the following:
IEnumerable<string> myEnumerableOfStrings = new List<string>();
Dictionary<object, string> dictionary = myEnumerableOfStrings.ToDictionary(value => (object) value);
And here's a Fiddle as an example.
Alright, so we have just an IEnumerable with no strong type. (First I've ever seen or heard of this being done, but the same principles should apply.)
We need to create a local dictionary and iterate over that collection.
var myDictionary = new Dictionary<object, string>();
IEnumerable myCollection = new List<string>();
foreach(var item in myCollection)
{
// This might be fun if you get two of the same object in the collection.
// Since this key is based off of the results of the GetHashCode() object in the base object class.
myDictionary.Add((object) item, item.ToString());
}
Here's an example of that.
The above answer's example for ToDictionary extension, is a List of a primitive type (string), I would like to demonstrate converting a List of a complex type (class) into dictionary.
This overload is constructing a dictionary out of keySelector function and elementSelector function (docs):
public static Dictionary<TKey, TElement> ToDictionary<TSource, TKey, TElement>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector);
For example:
public class FooClass
{
public int FooKey { get; set; }
public string FooValue { get; set; }
}
IENumerable<FooClass> foos = new List<FooClass>();
IDictionary<int, string> dict = foos.ToDictionary<int, string>(x=>x.FooKey, x=>x.FooValue);
Is there an easy way to add value to a nested dictionary. I am looking for a way to replace the following type of code with an easy one.
if (NestedDictionary.ContainsKey(key1))
{
if (NestedDictionary[key1].ContainsKey(key2))
{
if (NestedDictionary[key1][key2].ContainsKey(key3))
{
//do nothing
}
else
{
NestedDictionary[key1][key2].Add(key3,1);
}
}
else
{
NestedDictionary[key1].Add(key2, new Dictionary<int,int>() { { key3, 1 } });
}
}
else
{
NestedDictionary.Add(key1, new Dictionary<int, Dictionary<int,int>>() { { key2, new Dictionary<int,int>() { { key3, 1} } } });
}
We can write a GetOrAdd method that either gets the value for a particular key if it's there, or assigns a new value if there is none:
public static TValue GetOrAdd<TKey, TValue>(
this Dictionary<TKey, TValue> dictionary,
TKey key,
TValue newValue)
{
TValue oldValue;
if (dictionary.TryGetValue(key, out oldValue))
return oldValue;
else
{
dictionary.Add(key, newValue);
return newValue;
}
}
(Note you can create a second overload that accepts a Func<TValue> instead of a TValue, which is useful if the value is either expensive to create or causes side effects.)
Now this problem becomes very easy:
var dictionary = new Dictionary<int, Dictionary<int, string>>();
dictionary.GetOrAdd(key1, new Dictionary<int, string>())[key2] = value;
We get the inner dictionary for the outer key, or create a new blank one if it doesn't exist, and then we assign the new value to the dictionary returned. Note that the indexer will add an item if it doesn't exist or update the item if it already does.
This of course scales reasonably well as we add dimensions as well:
var dictionary = new Dictionary<int, Dictionary<int, Dictionary<int, string>>>();
dictionary.GetOrAdd(key1, new Dictionary<int, Dictionary<int, string>>())
.GetOrAdd(key2, new Dictionary<int, string>())[key3] = value;
In our case we are actually fine always adding the default value of TValue using our GetOrAdd method, so if we add an overload to support that:
public static TValue GetOrAdd<TKey, TValue>(
this Dictionary<TKey, TValue> dictionary,
TKey key)
where TValue : new()
{
TValue oldValue;
if (dictionary.TryGetValue(key, out oldValue))
return oldValue;
else
{
var newValue = new TValue();
dictionary.Add(key, newValue);
return newValue;
}
}
It simplifies the code even more:
dictionary.GetOrAdd(key1).GetOrAdd(key2)[key3] = value;
And if you really end up doing this particular operation a lot, you can just create a method to do the whole thing:
public static void AddMany<TKey1, TKey2, TKey3, TValue>(
this Dictionary<TKey1, Dictionary<TKey2, Dictionary<TKey3, TValue>>> dictionary,
TKey1 key1,
TKey2 key2,
TKey3 key3,
TValue newValue)
{
dictionary.GetOrAdd(key1).GetOrAdd(key2)[key3] = newValue;
}
Allowing you to write:
dictionary.AddMany(key1, key2, key3, value);
Of course, you need to create a new AddMany overload for each number of keys you want to support, and it has to be a number known at compile time, but that does appear to be the case in your example.
You can simplify the inner part:
if (NestedDictionary.ContainsKey(key1))
{
if (NestedDictionary[key1].ContainsKey(key2))
{
NestedDictionary[key1][key2][key3]=1;
}
else
{
NestedDictionary[key1].Add(key2, new Dictionary<int,int>() { { key3, 1 } });
}
}
else
{
NestedDictionary.Add(key1, new Dictionary<int, Dictionary<int,int>>() { { key2, new Dictionary<int,int>() { { key3, 1} } } });
}
But that's about it.
But what's the point of the structure? You only ever add a constant value (1) to the innermost dictionary, so there's no real "value" there. You might as well use a List<string> at that level.
I have
Dictionary<string, List<int>> myDict = new Dictionary<string, List<int>>();
and at some points I want to add numbers to myDict for a specific Dictionary key.
I am currently doing
if (!myDict.ContainsKey(newKey)){
myDict[newKey] = new List<int>();
}
myDict[newKey].Add(myNumber);
but that seems to be error prone to forgetting the ContainsKey check at some point.
I have searched for a way to make Dictionaries return a new List in case myDict["entry"] doesn't exist yet, but I couldn't find anything.
Here's a relatively simple implementation of the LazyLookup example I mentioned. It only implements IEnumerable out of brevity/simplicity to answer the question.
Essentially, upon accessing an index, it will make sure it has already been initialized to a new instance of the List<T> class.
public class LazyLookup<TKey, TValue> : IEnumerable<List<TValue>>
{
private readonly Dictionary<TKey, List<TValue>> CachedEntries;
private readonly Func<List<TValue>> LazyListCreator;
public LazyLookup()
: this(() => new List<TValue>())
{
}
public LazyLookup(Func<List<TValue>> lazyListCreator)
{
this.LazyListCreator = lazyListCreator;
this.CachedEntries = new Dictionary<TKey, List<TValue>>();
}
public List<TValue> this[TKey key]
{
get
{
return GetOrCreateValue(key);
}
}
private List<TValue> GetOrCreateValue(TKey key)
{
List<TValue> returnValue;
if (!CachedEntries.TryGetValue(key, out returnValue))
{
returnValue = LazyListCreator();
CachedEntries[key] = returnValue;
}
return returnValue;
}
public IEnumerator<List<TValue>> GetEnumerator()
{
return CachedEntries.Values.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
With some usage:
var lazyLookup = new LazyLookup<string, int>();
lazyLookup["nocheck"].Add(9001);
//outputs 9001
Console.WriteLine(lazyLookup["nocheck"][0]);
//outputs 0 as it's a newly initialized list
Console.WriteLine(lazyLookup["someOtherLookup"].Count);
At this point, you could update it to be threadsafe (as GetOrCreateValue currently is not threadsafe), or generalize it so it doesn't assume it's of List<T> but any type, or extend it to implement the full IDictionary<TKey, TValue> interface. But at minimum, if the above pattern you posted is used often, you may consider swapping direct usage of the dictionaries with some encapsulation which trivializes the task for you and eliminates code duplication.
You can use TryGetValue:
List<int> list;
if(!myDict.TryGetValue(newKey, out list))
{
list = new List<int>();
myDict.Add(newKey, list);
}
list.Add(myNumber);
If the Dictionary is a field i would encapsulate the acces in a method:
Dictionary<string, List<int>> myDict = new Dictionary<string, List<int>>();
public void AddNumber(string key, int value)
{
List<int> list;
if(!myDict.TryGetValue(key, out list))
{
list = new List<int>();
myDict.Add(key, list);
}
list.Add(value);
}
If you use ConcurrentDictionary<T>, you can do this:
myDict.GetOrAdd(newKey, new List<int>()).Add(myNumber);
You can actually use the others' suggestions. By encapsulating the access in a method or even using ConcurrentDictionary.
But for me, I would have a custom dictionary so you can actually implement what myDict["entry"] does if it did not see an element.
Good thing with this is you have full control on how you would like this dictionary to behave.
class MyCustomDictionary<TKey, TValue> : IDictionary<TKey, TValue>
where TValue : class, new()
{
private Dictionary<TKey, TValue> _dictionary;
public MyCustomDictionary()
{
_dictionary = new Dictionary<TKey, TValue>();
}
public TValue this[TKey key] // this is what's important
{
get
{
TValue val;
if (!_dictionary.TryGetValue(key, out val)) // if there is no element for that key, add a new element and return it
{
_dictionary.Add(key, new TValue());
return _dictionary[key];
}
else // else return the found element
{
return val;
}
}
set
{
_dictionary[key] = value;
}
}
public void Add(TKey key, TValue value)
{
_dictionary.Add(key, value);
}
public bool ContainsKey(TKey key)
{
return _dictionary.ContainsKey(key);
}
public ICollection<TKey> Keys
{
get { return _dictionary.Keys; }
}
public bool Remove(TKey key)
{
return _dictionary.Remove(key);
}
public bool TryGetValue(TKey key, out TValue value)
{
return _dictionary.TryGetValue(key, out value);
}
public ICollection<TValue> Values
{
get { return _dictionary.Values; }
}
public void Add(KeyValuePair<TKey, TValue> item)
{
_dictionary.Add(item.Key, item.Value);
}
public void Clear()
{
_dictionary.Clear();
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return _dictionary.Contains(item);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
_dictionary.ToList().CopyTo(array, arrayIndex); // do you need this? you can leave this :)
}
public int Count
{
get { return _dictionary.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return _dictionary.Remove(item.Key);
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return _dictionary.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _dictionary.GetEnumerator();
}
}
Then you use it like:
MyCustomDictionary<string, List<int>> myCustomDict = new MyCustomDictionary<int, List<int>>();
// return a new List of int
var someElementThatIsNotFound = myCustomDict["keyThatIsNonExistent"];
You can use TryGetValue method: if there's the key in the dictionary
you should just add the value into the list; otherwise you should
add a list with a value:
List<int> list
if (myDict.TryGetValue(newKey, out list))
list.Add(myNumber);
else
myDict.Add(newKey, new List<int>() { myNumber });
Lots of good answers already. I implemented an extension method for this exact reason:
public static TVALUE GetOrSet<TKEY, TVALUE>(this IDictionary<TKEY, TVALUE> self,
TKEY key,
Func<TVALUE> defaultValue)
{
TVALUE value;
if (!self.TryGetValue(key, out value))
{
value = defaultValue();
self[key] = value;
}
return value;
} // eo GetOrSet
Note that it takes a function to assign the value if it is not present. Either way, the value will be returned. Usage:
var dict = new Dictionary<string, List<int>>();
List<int> ints = dict.GetOrSet("list1", () => return new List<int>());
ints.Add(1);
If you're not referencing it again, you could potentially be less verbose:
dict.GetOrSet("list1", () => return new List<int>()).Add(1);