简体   繁体   中英

binding to an Observable Collection in a usercontrol and getting notifications from it

I've been looking through some other questions here that look similar to my issue, but none have the answer. So, here goes...

I'm developing a C# application using WPF and the MVVM pattern. I have a UserControl (called GroupTree) that contains a dependency property:

  public static DependencyProperty ChartGroupsProperty = DependencyProperty.Register("ChartGroups", typeof(ChartGroupCollection), typeof(GroupTree),
                                                      new FrameworkPropertyMetadata() { DefaultValue=new ChartGroupCollection(),  PropertyChangedCallback = OnChartGroupsChanged, BindsTwoWayByDefault = true });
    public ChartGroupCollection ChartGroups
    {
        get { return (ChartGroupCollection)GetValue(ChartGroupsProperty); }
        set
        {
            SetValue(ChartGroupsProperty, value);
        }
    }

    private static void OnChartGroupsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (e.OldValue != e.NewValue)
        {
            // Nothing at the moment
        }
    }

Its of type ChartGroupCollection which is defined so:

public class ChartGroupCollection : ObservableCollection<ChartGroup>
{
}

The ChartGroup contains properties that use INotifyProperty and I've independently verified that all the change events that should fire for the main collection do so properly.

The MainWindow.Xaml that uses the control:

   <Controls:GroupTree x:Name="Groups" ChartGroups="{Binding MainGroups}" />

I have another control that binds to the ChartGroups Value in the GroupTree Control:

   <Controls:ChartsControl x:Name="Charts1_1" Tree="{Binding ElementName=Groups, Path=ChartGroups}" />

What I want to happen is when the GroupTree ChartGroups Value changes, for this to notify the ChartsControl dynamically of those changes and update accordingly. But at the moment, no joy. The data is there, because if force a refresh of the ChartsControl manually the changes show. I've tried binding to the MainGroups property hoping that would work, but that doesn't either.

I've probably missed something blindingly obvious, but I'm not sure what. Any ideas?

Rob

ObservableCollection only listens to the changes occur in the collection like items being added or removed and it does not notify about any changes happened in the individual items of its collection.

The following class enhances the funtionality of ObservableCollection by registering itself to the INofityPropertyChanged event of the items being added and raises the CollectionChanged event of the ObservableCollection when a property of an item of the collection changes.

Calling the ClearItems at the end releases all event handlers.

More details can be found from the below link.

How to Listen to Property Changes of Items of an ObservableCollection

using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Collections;

namespace VJCollections
{
    /// <summary>
    ///     This class adds the ability to refresh the list when any property of
    ///     the objects changes in the list which implements the INotifyPropertyChanged. 
    /// </summary>
    /// <typeparam name="T">
    public class ItemsChangeObservableCollection<T> :
           ObservableCollection<T> where T : INotifyPropertyChanged
    {
        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Add)
            {
                RegisterPropertyChanged(e.NewItems);
            }
            else if (e.Action == NotifyCollectionChangedAction.Remove)
            {
                UnRegisterPropertyChanged(e.OldItems);
            }
            else if (e.Action == NotifyCollectionChangedAction.Replace)
            {
                UnRegisterPropertyChanged(e.OldItems);
                RegisterPropertyChanged(e.NewItems);
            }

            base.OnCollectionChanged(e);
        }

        protected override void ClearItems()
        {
            UnRegisterPropertyChanged(this);
            base.ClearItems();
        }

        private void RegisterPropertyChanged(IList items)
        {
            foreach (INotifyPropertyChanged item in items)
            {
                if (item != null)
                {
                    item.PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
                }
            }
        }

        private void UnRegisterPropertyChanged(IList items)
        {
            foreach (INotifyPropertyChanged item in items)
            {
                if (item != null)
                {
                    item.PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
                }
            }
        }

        private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }
    }
}

Unfortunately, the PropertyChanged callback is only invoked when PropertyChanged is raised against the bound property (thus causing the binding to evalutate).

You need the CollectionChanged event provided by INotifyCollectionChanged . So you have two options:

  1. Register for that event in OnChartGroupsChanged and handle it yourself (make sure to de-register for the old one, so you don't leak the handle!)

  2. Derive from something like ItemsControl and use its Items property. This removes your dependency property and allows ItemsControl to handle all the collection changed stuff for you.

If its feasable, I would go with the second one. If not, utilizing the CollectionChanged event shouldn't be too bad.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM