Reputation: 1300
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;
}
Upvotes: 6
Views: 9266
Reputation: 1300
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.
Upvotes: 0
Reputation: 154
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.
Upvotes: 4
Reputation: 842
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.
Upvotes: 0
Reputation: 4116
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
Upvotes: 0
Reputation: 8813
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)
Upvotes: 0