标头为“*”的 DataGridColumn 已存在于 DataGrid 的 Columns 集合中

Posted

技术标签:

【中文标题】标头为“*”的 DataGridColumn 已存在于 DataGrid 的 Columns 集合中【英文标题】:DataGridColumn with Header '*' already exists in the Columns collection of a DataGrid 【发布时间】:2013-08-01 22:09:06 【问题描述】:

我有一个带有 MVVM 模式的 WPF 应用程序。在我看来,我必须绑定一个ObservableCollection 才能查看。在这种情况下,我有一个ListBox 和一个DataGrid 都绑定到同一个ObservableCollection,但是做不同的事情,比如事件、风格等。

我一次只需要显示其中一个控件,我所做的是创建了两个用户控件,一个用于DataGrid,另一个用于ListBox。我通过在主视图上放置一个ContentControl 在它们之间进行切换(类似于blog。默认视图是DataGrid,当单击一个按钮时会显示另一个视图(即ListBox)。向上到此工作正常。

还有一点要记住,数据网格列是使用以下link 中描述的解决方案动态生成的。因此,当我返回DataGrid 查看时,在foreach 语句中将列添加到数据网格时会引发错误(请参考上一个链接的答案),例如

“标题为 'Ord' 的 DataGridColumn 已存在于 DataGrid 的 Columns 集合中。DataGrids 不能共享列并且不能包含重复的列实例。”

但我确定在向DataGrid 添加列之前,其Count 属性为零(dataGrid.Columns.Count())。那么DataGrid 标头属性是如何被持久化的呢?有什么方法可以清除标头值?

请推荐...

【问题讨论】:

您可以尝试使用Snoop 之类的工具来检查可视化树。 我们能看到 ViewModel 和 Model 的这个或至少一个等效的例子吗? 问题可能与您从 args 填充的 columns 变量有关。使用调试器并检查您所指的 foreach 语句之前的内容。副本可能在里面 【参考方案1】:

我正在使用Bindable Column。我的网格使用CollectionViewSource 作为数据源,我在共享列时遇到了同样的问题。我已经用反射修复了。

所以在BindableColumnsPropertyChanged 内部改变你的逻辑如下:

// Add columns from this source.
                foreach (var column in newColumns)
                    if (column != null)
                    
                        var dg = (DataGrid)column.GetType().GetProperty("DataGridOwner", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(column, null);
                        dg?.Columns.Clear();
                        dataGrid.Columns.Add(column);
                    

完整代码:

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;

namespace SGRE.WOS.Common.UI

    public class DataGridColumnsBehavior
    
        public static readonly DependencyProperty BindableColumnsProperty = DependencyProperty.RegisterAttached("BindableColumns", typeof(ObservableCollection<DataGridColumn>), typeof(DataGridColumnsBehavior), new UIPropertyMetadata(null, BindableColumnsPropertyChanged));
        /// <summary>Collection to store collection change handlers - to be able to unsubscribe later.</summary>
        private static readonly Dictionary<DataGrid, NotifyCollectionChangedEventHandler> _handlers;

        static DataGridColumnsBehavior()
        
            _handlers = new Dictionary<DataGrid, NotifyCollectionChangedEventHandler>();
        
        private static void BindableColumnsPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
        
            if (!(source is DataGrid dataGrid)) return;
            if (e.OldValue is ObservableCollection<DataGridColumn> oldColumns)
            
                // Remove all columns.
                dataGrid.Columns.Clear();

                // Unsubscribe from old collection.
                if (_handlers.TryGetValue(dataGrid, out var h))
                
                    oldColumns.CollectionChanged -= h;
                    _handlers.Remove(dataGrid);
                
            

            var newColumns = e.NewValue as ObservableCollection<DataGridColumn>;
            dataGrid.Columns.Clear();
            if (newColumns != null)
            
                // Add columns from this source.
                foreach (var column in newColumns)
                    if (column != null)
                    
                        var dg = (DataGrid)column.GetType().GetProperty("DataGridOwner", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(column, null);
                        dg?.Columns.Clear();
                        dataGrid.Columns.Add(column);
                    


                // Subscribe to future changes.
                NotifyCollectionChangedEventHandler h = (_, ne) => OnCollectionChanged(ne, dataGrid);
                _handlers[dataGrid] = h;
                newColumns.CollectionChanged += h;
            
        

        private static void OnCollectionChanged(NotifyCollectionChangedEventArgs ne, DataGrid dataGrid)
        
            switch (ne.Action)
            
                case NotifyCollectionChangedAction.Reset:
                    dataGrid.Columns.Clear();
                    if (ne.NewItems != null && ne.NewItems.Count > 0)
                        foreach (DataGridColumn column in ne.NewItems)
                            dataGrid.Columns.Add(column);
                    break;
                case NotifyCollectionChangedAction.Add:
                    foreach (DataGridColumn column in ne.NewItems)
                        dataGrid.Columns.Add(column);
                    break;
                case NotifyCollectionChangedAction.Move:
                    dataGrid.Columns.Move(ne.OldStartingIndex, ne.NewStartingIndex);
                    break;
                case NotifyCollectionChangedAction.Remove:
                    foreach (DataGridColumn column in ne.OldItems)
                        dataGrid.Columns.Remove(column);
                    break;
                case NotifyCollectionChangedAction.Replace:
                    dataGrid.Columns[ne.NewStartingIndex] = ne.NewItems[0] as DataGridColumn;
                    break;
            
        
        public static void SetBindableColumns(DependencyObject element, ObservableCollection<DataGridColumn> value)
        
            element.SetValue(BindableColumnsProperty, value);
        
        public static ObservableCollection<DataGridColumn> GetBindableColumns(DependencyObject element)
        
            return (ObservableCollection<DataGridColumn>)element.GetValue(BindableColumnsProperty);
        
    

【讨论】:

一千感谢您为我省去了很多麻烦!这应该是公认的答案。完整代码,包含对另一个 *** 解决方案的修复。【参考方案2】:

在使用mentioned link 中的行为后,我遇到了同样的错误。这个问题很老,但如果其他人有同样的问题,我通过添加一个“桥”类来解决它,而不是直接添加列。

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows.Controls;

namespace AlyElHaddad.***

    public class DataGridColumnCollection : ObservableCollection<DataGridColumn>
    
        public DataGridColumnCollection()
            : base()
         
        public DataGridColumnCollection(IEnumerable<DataGridColumn> collection)
            : base(collection)
         
        public DataGridColumnCollection(List<DataGridColumn> list)
            : base(list)
         
    

在 XAML 中,不要直接添加列,而是将它们添加到 DataGridColumnCollection 中。

<aly:DataGridColumnCollection xmlns:aly="clr-namespace:AlyElHaddad.***">
    <DataGridTextColumn Header="Column1" Binding="Binding Column1"/>
    <DataGridTextColumn Header="Column2" Binding="Binding Column2"/>
    <DataGridTextColumn Header="Column3" Binding="Binding Column3"/>
</aly:DataGridColumnCollection>

【讨论】:

【参考方案3】:

如果数据网格及其绑定被设置一次,那么不要妨碍创建数据列的实例,如果可观察集合没有改变,而是使用为列表框和数据创建的用户控件的可见性属性使用触发器的网格。

【讨论】:

【参考方案4】:

如果您使用触发器来交换视图,请将内容设置为动态资源,以便始终在运行时解析数据网格。

【讨论】:

【参考方案5】:

在 WPF 中将实例添加到控件或元素时,您应该始终清除添加的控件的父控件,因为当您将控件添加到子集合时,父控件会作为父控件添加到新子控件,这是消息告诉你的内容

【讨论】:

以上是关于标头为“*”的 DataGridColumn 已存在于 DataGrid 的 Columns 集合中的主要内容,如果未能解决你的问题,请参考以下文章

分别验证 DataGridColumn 单元格

WPF 自定义 DatagridColumn 绑定问题

如何同时输入 , 和 .然后将其格式化为 .用于所有文化中的 DataGridColumn?

使用转换器的DataGridColumn可见性

为DataGrid 写一个 DropDownListColumn

动态设置dataGridColumn的ItemRenderer