5

I have a custom control that has a DependencyProperty of type ObservableCollection that is bound to an observableCollection:

<MyControl MyCollectionProperty = {Binding MyObservableCollection} ...

Problem is adding to MyObservableCollection does not update MyCollectionProperty.

I need to completly replace the MyObservableCollection to make it work e.g.

MyObservableCollection = null;
MyObservableCollection = new ObservableCollection(){...}

Is there a better way to deal with this?

EDIT:

    public ObservableCollection<string> Columns
    {
        get { return (ObservableCollection<string>)GetValue(ColumnsProperty); }
        set { SetValue(ColumnsProperty, value); }
    }

    public static readonly DependencyProperty ColumnsProperty =
        DependencyProperty.Register("Columns", typeof(ObservableCollection<string>), typeof(MyControl),
                                    new PropertyMetadata(new ObservableCollection<string>(), OnChanged));
Anatoliy Nikolaev
  • 22,370
  • 15
  • 69
  • 68
user1604008
  • 995
  • 9
  • 17
  • Would you post the definition of your dependency property? Could be something off in there... – Dan J Feb 22 '13 at 01:13
  • Is your "MyControl" monitoring PropertyChanged or CollectionChanged and are you monitoring PropertyChanged on the objects within your observable collection? – grantnz Feb 22 '13 at 01:15
  • yes there is a callback in the defintion and handler for CollectionChanged never gets fired. I don't see where listening for PropertyChanged on the items is relevent since we are talking about adding. – user1604008 Feb 22 '13 at 01:34
  • Can you post the OnChanged method? Btw it isn't a good idea to provide a defaultvalue for your dp. Every instance of the control will reference the same collection instance by default. – AndrewS Feb 22 '13 at 02:27
  • Ah.. good point about the default value. I could post the handler but its kind of irrelevant because it never gets called. – user1604008 Feb 22 '13 at 02:34
  • Similar problem asked and answered here http://stackoverflow.com/questions/4362278/observablecollection-dependency-property-does-not-update-when-item-in-collection – failedprogramming Feb 22 '13 at 03:59

2 Answers2

19

In addition to what grantz has answered, I would suggest to declare the property with type IEnumerable<string> and check at runtime if the collection object implements the INotifyCollectionChanged interface. This provides greater flexibility as to which concrete collection implementation may be used as property value. A user may then decide to have their own specialized implementation of an observable collection.

Note also that in the ColumnsPropertyChanged callback the CollectionChanged event handler is attached to the new collection, but also removed from the old one.

public static readonly DependencyProperty ColumnsProperty =
    DependencyProperty.Register(
        "Columns", typeof(IEnumerable<string>), typeof(MyControl),
        new PropertyMetadata(null, ColumnsPropertyChanged));

public IEnumerable<string> Columns
{
    get { return (IEnumerable<string>)GetValue(ColumnsProperty); }
    set { SetValue(ColumnsProperty, value); }
}

private static void ColumnsPropertyChanged(
    DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
    var control= (MyControl)obj;
    var oldCollection = e.OldValue as INotifyCollectionChanged;
    var newCollection = e.NewValue as INotifyCollectionChanged;

    if (oldCollection != null)
    {
        oldCollection.CollectionChanged -= control.ColumnsCollectionChanged;
    }

    if (newCollection != null)
    {
        newCollection.CollectionChanged += control.ColumnsCollectionChanged;
    }

    control.UpdateColumns();
}

private void ColumnsCollectionChanged(
    object sender, NotifyCollectionChangedEventArgs e)
{
    // optionally take e.Action into account
    UpdateColumns();
}

private void UpdateColumns()
{
    ...
}
Clemens
  • 123,504
  • 12
  • 155
  • 268
6

Below is a working example that may help.

In this example, the method OnChanged is called immediately, when the Add button is clicked "Changed" is written to the console.

The Control

public class MyControl : Control
{

    public ObservableCollection<string> ExtraColumns
    {
        get { return (ObservableCollection<string>)GetValue(ExtraColumnsProperty); }
        set { SetValue(ExtraColumnsProperty, value); }
    }

    public static readonly DependencyProperty ExtraColumnsProperty =
        DependencyProperty.Register("ExtraColumns", typeof(ObservableCollection<string>), typeof(MyControl),
                                    new PropertyMetadata(new ObservableCollection<string>(), OnChanged));

    static void OnChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        (sender as MyControl).OnChanged();

    }

    void OnChanged()
    {
        if ( ExtraColumns != null )
            ExtraColumns.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(ExtraColumns_CollectionChanged);
    }

    void ExtraColumns_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        Console.WriteLine("Changed");    
    }
}

The Window

<Window x:Class="WpfApplication18.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication18"
    Title="MainWindow" Height="350" Width="525">
  <StackPanel>
    <local:MyControl ExtraColumns="{Binding Extras}"/>
    <Button Click="Button_Click">Add</Button>
  </StackPanel>
</Window>

Window Code Behind

public partial class MainWindow : Window
{
    private ObservableCollection<string> _extras = new ObservableCollection<string>( );
    public ObservableCollection<string> Extras
    {
        get { return _extras; }
        set
        {
            if (value != _extras)
            {
                _extras = value;
            }
        }
    }


    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        Extras.Add("Additional");
    }
}
grantnz
  • 7,322
  • 1
  • 31
  • 38
  • 5
    You shouldn't use code `new PropertyMetadata(new ObservableCollection()` in registering dependency property. See [here](https://msdn.microsoft.com/en-us/library/vstudio/aa970563%28v=vs.100%29.aspx) for explanation – ghord Jun 26 '15 at 15:24
  • Doesn't this cause a problem where the underlying collection gets shared by all instances of MyControl? According to this link you have to manually set a new collection in the constructor of MyControl to avoid this. https://msdn.microsoft.com/en-us/library/vstudio/aa970563%28v=vs.100%29.aspx – StayOnTarget May 21 '20 at 11:52