13

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...

Community
  • 1
  • 1
Dennis Jose
  • 1,589
  • 15
  • 35
  • You could try a tool like [Snoop](http://snoopwpf.codeplex.com/) to examine the visual tree. – oddparity Aug 01 '13 at 13:55
  • Can we see the ViewModel and Model for this or at least an equivalent example? – Tyler Morrow Aug 01 '13 at 14:59
  • The problem may be to do with the `columns` variable you populate from the args. Use the debugger and examine the contents of that prior to foreach statement you are referring to. The duplication could be in there – Joseph Devlin Aug 02 '13 at 09:02

5 Answers5

3

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>
Community
  • 1
  • 1
Aly Elhaddad
  • 1,913
  • 1
  • 15
  • 31
3

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);
        }
    }
}
Jones
  • 548
  • 15
  • 33
  • A thousand thanks for saving me lots of headaches! This should be the accepted answer. Full code, containing the fix for another StackOverflow's solution. – Jessica Feb 09 '21 at 07:04
0

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

mtopp
  • 31
  • 4
0

If you are using triggers to swap the views set the content as a dynamic resource so the datagrid is always resolved at runtime.

0

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.

yashashwi
  • 50
  • 10