问题
I have a WPF application with MVVM pattern. In one of my view, I have to bind an ObservableCollection
to view. In that view, I have one ListBox
and one DataGrid
both bind to the same ObservableCollection
but doing different things like events, style etc..
I need only one of these controls displayed at a time and what I did is created two user controls, one for DataGrid
and other for ListBox
. And I switched between them by placing a ContentControl
on the main view(something similar to this blog. The default view is DataGrid
and when click on a button the other view is displayed(i.e. ListBox
). Up to this are working fine.
One more thing to keep in mind that the Data Grid columns are generated dynamically by using the solution described in the following link. So when I go back to DataGrid
view it's throwing an error while adding columns to Data Grid in foreach
statement (pls refer the answer of the previous link) like
"DataGridColumn with Header 'Ord' already exists in the Columns collection of a
DataGrid
. DataGrids cannot share columns and cannot contain duplicate column instances."
But I'm sure that before adding columns to DataGrid
its Count
property is zero(dataGrid.Columns.Count()). So how the DataGrid
header properties are persisted? Is there any way to clear the header values?.
Please suggest...
回答1:
I had have the same error after using the behavior in the mentioned link. The question is old but in case someone else has the same problem, I solved it by adding a 'bridge' class to use instead of adding the columns directly.
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows.Controls;
namespace AlyElHaddad.Stackoverflow
{
public class DataGridColumnCollection : ObservableCollection<DataGridColumn>
{
public DataGridColumnCollection()
: base()
{ }
public DataGridColumnCollection(IEnumerable<DataGridColumn> collection)
: base(collection)
{ }
public DataGridColumnCollection(List<DataGridColumn> list)
: base(list)
{ }
}
}
In XAML, instead of adding the columns directly, add them inside a DataGridColumnCollection
.
<aly:DataGridColumnCollection xmlns:aly="clr-namespace:AlyElHaddad.Stackoverflow">
<DataGridTextColumn Header="Column1" Binding="{Binding Column1}"/>
<DataGridTextColumn Header="Column2" Binding="{Binding Column2}"/>
<DataGridTextColumn Header="Column3" Binding="{Binding Column3}"/>
</aly:DataGridColumnCollection>
回答2:
I'm using the Bindable Column. My grid uses CollectionViewSource
for data source and I had same problem with columns being shared. I've fixed with reflection.
So inside BindableColumnsPropertyChanged
change your logic like below:
// 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);
}
Full Code:
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);
}
}
}
回答3:
When adding an instance to a control or element in WPF you should allways clean the added control's parent, since, when you add a control to a collection of childs, the parent control is added to the new child as it's parent, this is what the message is telling you
回答4:
If you are using triggers to swap the views set the content as a dynamic resource so the datagrid is always resolved at runtime.
回答5:
If the data grid and its binding is set once then don't hamper with that instance of data columns created, if the observable collection is not changing, instead play with the visibility property for user control created for both list box and data grid using triggers.
来源:https://stackoverflow.com/questions/17986380/datagridcolumn-with-header-already-exists-in-the-columns-collection-of-a-dat