将数据添加到 DataGrid 中的特定列

Posted

技术标签:

【中文标题】将数据添加到 DataGrid 中的特定列【英文标题】:Adding data to a specific column in a DataGrid 【发布时间】:2016-02-05 10:22:13 【问题描述】:

我想创建一个新列,然后在按钮单击事件中向该新列添加值。那可能吗?该列下面可能有几行,具体取决于报价中的项目数量。

到目前为止我所取得的成就:

我的所有信息都存储在我的班级

public class ViewQuoteItemList

    ...
    public double SupplierCost  get; set; 
    ...

我可以创建我的专栏并将其绑定到我的ViewQuoteItemList

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;

从这里我从不同的数据网格中获取我的报价项目,并将它们添加到我想要添加新列及其值的第二个数据网格中

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

最后,我将新列添加到第二个数据网格并将collection 设置为数据网格的ItemSource

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

我的问题是,一旦我从一个供应商处编辑了一个数据单元格,整行就会用那个值更新,因为它都绑定到一个类/项目源。可以解决吗?

编辑:

“整行得到更新”意味着每次我将一个值插入一行中的一个单元格时,该行中的每个单元格都会更新为相同的值。这里有一些图片显示我的意思。我想编辑所有数据,这一切都发生在我的第二个数据网格(dgFeedbackSupplier)上。

在这里,我添加了两家公司,其中包含我想要比较价格的 4 件商品。现在我想单击公司下方的单个单元格并为某个项目添加值。

在这里我双击一个单元格来编辑/更改值。

然后,当我更改所选单元格中的值时,同一行中该特定项目的每个其他公司的值都会更新为相同的值。

这是我的问题,我只需要更改一个单元格的值,而不是整行。

编辑 2:

如何将此collection 转换为ExpandoObject

var collection = (from i in items
                  let a = new ViewQuoteItemList  Item = i.Item, Supplier = 25 
                  select a).ToList();

编辑 3:

我的 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>

这就是我的整个方法在我添加列以及从其他数据网格中获取项目时的样子:

    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;
    

【问题讨论】:

对不起,我听不懂。 “整行更新”是什么意思?您编辑的哪个单元格会产生问题?在哪个数据网格上?您正在添加或编辑数据?你的“按钮”的行为是什么?在我看来你需要完善这个问题 @tede24 感谢您的评论。我已经编辑了我的问题以提供更多详细信息,希望您能理解我想要更好地实现的目标。 您的两列都绑定到 ViewQuoteItemList 类的相同属性? @KyloRen 是的。我不知道如何将它们分开。 好的。而且每次都可以添加多列。对吗? 【参考方案1】:

我可以提出另一种方法吗?据我了解;

您有供应商,而这些供应商有商品。您可以添加新的供应商。我假设供应商可能没有所有物品(挑战:))。

您想在类似网格的视图中显示此数据结构。

这里的问题是您试图使用表格视图组件显示非表格数据。您拥有的是分层数据。在我继续我的解决方案之前,这里有一些屏幕截图。

我在这里所做的基本上是在分层数据上创建新视图,填补空白并将其转换为表格形式。我为空槽使用了假类,因此我可以轻松地在 XAML 中选择正确的数据模板。我避免使用任何自定义代码,并以 MVVM+XAML 方式保存所有内容。所以绑定和这样的工作符合预期。

当集合发生变化时,必须更新新视图,因此我使用 MVVMLight 中的 messenger 类来轻松实现,并手动调用 RaisePropertyChanged 事件。

在 XAML 中,我使用 ItemsControl 和 UniformGrid 组件来创建类似网格的视图。

我把完整的解决方案放在GitHub:https://github.com/orhtun/GridLikeViewWithDynamicColumns

它在每次运行时创建随机数据。如果您遇到构建错误,请尝试右键单击解决方案并还原 NuGet 包。

【讨论】:

嗨,伙计!谢谢你的回答。这有点太晚了。我已经想出了如何在我的数据网格中做到这一点。我会在今天晚些时候有机会发布答案!再次感谢您的所有努力。 :) 没问题,这是一个有趣的问题。无论如何,我认为自己将来可能需要它:) 好人!我发布了一个答案,我在其中展示了我的编码以及我是如何解决这个谜语的。希望以后能帮到你。【参考方案2】:

您不能使用集合与第二个 DataGrid 绑定,因为您的视图是动态的,可以有可变的列数。

您应该使用 DataTable 作为 DataGrid 的源。不要在网格中添加一列,而是在 DataTable 中添加一列,然后在网格中自动生成该列。

将项目源(任何类型)转换为 DataTable 的方法是(可能是你第一次需要这个):

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;

    

然后当您必须在 DataGrid 中添加一列时,通过迭代 collection 在表中添加一列以填充数据表中的列。

没有简单的方法来解决您的问题,因为您的场景是独一无二的,只有动态结构才能满足您的需求。

由于使用 DataTable,您的所有单元格都将绑定到唯一实体。 DataGrid 单元格的单个更改只会更新一个单元格。(与多个单元格绑定到同一属性的集合不同)

【讨论】:

感谢您的回答!我会尽快向您介绍我的进度;) 全局剪贴板数据在此操作期间被修改。最好在操作之前弹出当前剪贴板数据,然后将其推回剪贴板。【参考方案3】:

根据您的要求,我建议您使用Microsoft Dynamics ExpandoObject

您需要创建一个List&lt;ExpandoObject&gt;,它只不过是一个Dictionary&lt;string,object&gt;,其中您的键是您的属性名称,对象是您的值。所以在你的情况下,

ViewQuoteItem 中的所有属性都添加到这个新对象中,当您需要添加列时,您可以向对象添加另一个属性。然后只需更新您的视图并查看添加到网格的新列。

要使用 Expando,您可以使用简单的方法 -

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

或者您可以像这样将 dynamicItem 视为字典 -

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

我个人觉得转换为 Dict 很容易,因为它让我可以灵活地显式添加属性,然后使用键来引用它们,但这只是一种简单而不是强制。

我还建议您使用一种方法将您的 dataList 映射到新的 expandoList 并将 expandlist 绑定到您的视图,请确保您在 WPF 中使用 AutoGeneration of columns 以便新添加的列可见。

您可以查看我的解决方案 here,它在类似的情况下对我有用。

如果您是 dynamics 的新手,您可能会觉得这有点棘手,但 Expandos 很适合使用,而且使用起来很轻松令人惊叹。


从 ViewQuoteItemList 转换为 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);
            

相关文章here 和另一篇有趣的文章here

【讨论】:

谢谢你的回答!一切正常后,我会尽快通知您。 这个 ExpandoObjects 真的很酷(或者我到目前为止所学的),但我正在努力解决的一件事是:“ViewQuoteItem 中的所有属性都添加到这个新的对象”。如何将我的ViewQuoteItemList 设置为ExpandoObject? 0_o 我添加了一个简化的代码来将你的对象转换为 Expando 感谢您迄今为止的所有帮助。不过,我还有几个问题。对此很抱歉,但看起来你的编码正在引导我朝着正确的方向前进。我的新列已正确添加,我可以看到公司下每个项目的显示值 25。我仍然无法弄清楚的主要问题是,这些值仍然连接到网格中的每个公司,当我更改一个值时,所选行中的所有值都会更改。所以我的问题是,你能告诉我如何拆分每个添加的列以拥有自己的值吗? 对于给您带来的不便和缓慢的回复,我深表歉意,但我花了这么长时间才弄清楚一切,因为我对 c# 编码还是很陌生,并不完全理解你的意思正试图在你的回答中向我解释。【参考方案4】:

就个人而言,我会避免实际实例化单个列,而是直接将它们添加到 DataGridView,然后操作它们的属性。

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

避免使用实例化列,然后添加它在过去帮助我解决了奇怪的链接问题,例如编辑一个单元格,而其他单元格发生变化。至于子视图等,我不能说我可以对此发表太多评论。

【讨论】:

【参考方案5】:

我看到这个问题引起了相当多的关注,所以我会发布我的答案的解决方案。我希望这将帮助那里的人了解如何仅将数据添加到特定列。 :)

顺便说一句 - 这是我创建的一个测试应用程序,因此编码与我原始问题中的编码不同。我也用字典来解决我的问题。效果很好!

创建我的项目和供应商列表:

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

我的按钮点击事件:

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

我的班级:

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; 

还有我的 Observable Dictionary,其中有很多工作发生:

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

这是很多代码,我知道。抱歉,我没有时间解释一切以及它是如何工作的。我希望你们能自己解决这个问题;)类中还有很多额外的功能可以用于各种目的。

最后,这是我的 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"/>

注意 - 双击单元格时可以编辑数据网格中的值。感谢所有添加到我的问题或帮助我朝着正确方向前进的人。我希望这可以帮助那里的人。

【讨论】:

以上是关于将数据添加到 DataGrid 中的特定列的主要内容,如果未能解决你的问题,请参考以下文章

将按钮列添加到 datagrid 视图导致问题

将DataTable数据绑定到DataGrid - 代码背后

使用 writerows,lambda 将数据添加到特定列

如何用特定值填充列中的一系列单元格?

如何将列表项附加到数据框中的特定列?

将行号列添加到 jquery 数据表