Adding data to a specific column in a DataGrid

I want to create a new column and then add values ​​to only this new column when the button is clicked. Is it possible? A column may contain several rows below it, depending on the number of elements in the 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 the ViewQuoteItemList class

 DataGridTextColumn columnFeedbackSupplier = new DataGridTextColumn(); columnFeedbackSupplier.Binding = new Binding("SupplierCost"); //The header of the column gets it 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 quotes from another datagrid, and I add them to my second datagrid, where I want to add a new column, and it will mean

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

Finally, I add a new column to the second datagrid and set collection as datagrid ItemSource

 dgFeedbackSelectSupplier.Columns.Add(columnFeedbackSupplier); dgFeedbackSelectSupplier.ItemsSource = collection; 

My problem is that as soon as I edit the data cell of one of the providers, the whole row is updated with one value, because everything is connected to the same source of the class / element. Could this be fixed?

EDIT:

β€œThe whole row is updated” means that every time I insert a value in one cell in a row, each individual cell in this row is updated with the same value. Here are some photos showing what I mean. I want to edit all the data, and all this happens on my second datagrid (dgFeedbackSupplier).

Here I have two companies that have added 4 items with which I want to compare prices. Now I want to click on one cell under the company and add a value for a specific item.

enter image description here

Here I double click on the cell to edit / change the value. enter image description here

Then, when I change my value in the selected cell, each other company value for this particular item on the same line is updated with the same value. enter image description here

So that my problem and I needed to change only one cell value, and not the entire 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 at the moment when I add my columns and where I get the elements 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; } 
+5
source share
5 answers

I see that this question gets quite a lot of attention, so I will post a solution for my answer. I hope this helps someone to get an idea on how to add data only to a specific column. :)

As a side note, this is a test application that I created, so the coding will not be the same as in my original question. I also used a dictionary to solve my problem. It works great!

Creating lists of goods and suppliers:

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

Mouse event:

 private void Add_Click(object sender, RoutedEventArgs e) { //Select my supplier from my Supplier 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 observation dictionary, where most of the work is going on:

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

This is a lot of code, I know. Sorry, I don’t have time to explain everything and how it works. I hope you guys can figure it out for yourself;) The class also has a lot of extra features that you can use for various purposes.

Finally, 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. Values ​​in the datagrid can be edited by double-clicking on the cell. Thanks to all the people who added me your question or helped me in the right direction. I hope this can help someone out there.

0
source

Can a different approach be proposed? From what I understand

You have suppliers, and these suppliers have items. You can add new suppliers. I suggested that suppliers may not have all the items (for a call :)).

You want to present this data structure in a table.

The problem is that you are trying to display non-standard data with a table view component. You have hierarchical data. Before I continue my decision, take some screenshots.

enter image description here

enter image description here

enter image description here

I’m mainly here to create new views on hierarchical data, fill in the blanks and turn them into a tabular form. I used fake classes for empty slots, so I could easily select the appropriate data templates in XAML. I avoided using any custom code and saved everything in MVVM + XAML. So the bindings and such work as expected.

New views had to be updated when collections changed, so I used the Messenger class from MVVMLight for easy implementation and called RaisePropertyChanged events manually.

In XAML, I used the ItemsControl and UniformGrid components to create a grid view.

I put the complete solution on GitHub : <a4>

It creates random data for each run. If you get build errors, try right-clicking on the solution and Repair NuGet Packages.

+3
source

You cannot use the collection to bind with your second DataGrid, because your view is dynamic, it can have the variable no from the columns.

You should use a DataTable as the source for your DataGrid. Instead of adding a column to the grid, add a column to the DataTable and an AutoGenerate column to the Grid.

Method to convert the source of an element (of any type) to a DataTable (maybe 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; } 

and then when you need to add a column to the DataGrid, add the column to the table, iterating over collection to populate the column in the data table.

There is no easy way to solve your problem as your scenario is unique and only a dynamic structure can fully satisfy your needs.

Because, using a DataTable, your cell will be bound to unique objects. Only one cell will be updated with one change to the DataGrid cell. (Unlike a collection in which several cells are tied to the same property)

0
source

For your requirement, I suggest you use Microsoft Dynamics ExpandoObject

You need to create a List<ExpandoObject> , which is nothing more than a Dictionary<string,object> , in which your key is your property name and the object is your value. So in your case

All 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 refresh your view and see a new column added to the grid.

To use Expando, you can do it in a simple way -

 var dynamicItem = New ExpandoObject(); dynamicItem.Item = "Test"; dynamicItem.BarlwoodCityDeep = (int)350; 

or you can consider dynamicItem as a dictionary like this -

 IDictionary<String, Object> dynamicDict = dynamicItem dynamicDict.Add("MyNewProperty","MySomeValue") 

I personally find that converting to Dict easy, as it gives me the ability to add properties explicitly and then use the keys to refer to them, but this is just ease, not coercion.

I would also recommend that you have a method that maps your data list to a new expandoList and associates the list of extensions with your view, make sure you use AutoGeneration columns in WPF so that the new columns are visible.

You can see my solution here , which worked for me in a somewhat similar case.

If you are new to dynamics , it may seem difficult to you, but Expandos are happy to work and the ease of working 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 here

0
source

Personally, I would stay away from actually creating individual columns, instead add them directly to the DataGridView, 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 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"; } } 

Avoid using an instance of the column, and then adding it has helped me in the past with unusual connection problems, such as editing one cell and others. As for the subqueries, etc., I cannot say that I can comment on this.

0
source

Source: https://habr.com/ru/post/1242362/


All Articles