Skip to main content
Tweeted twitter.com/StackCodeReview/status/811908732363870208
added 4 characters in body
Source Link
Cool Blue
  • 256
  • 2
  • 10
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;

namespace CollectionBinding
{
    public class TestParent : Label
    {
        #region refresh
        private static void Refresh (TestParent instance)
        {
            instance.Content = string.Format("parent value:\t{0}", instance.AttachedString);
            foreach (var myItem in instance.MyItems)
            {
                instance.Content += string.Format("\n  child value:\t{0}\t{1}",
                                myItem.AttachedString, myItem.UnAttachedString);
            }
            instance.Content += string.Format("\n Attached DP References are{0} equal",
                TestParent.AttachedStringProperty == TestChild.AttachedStringProperty
                ? "" : " NOT");

        }

        public static void RefreshAsync(TestParent tp)
        {
            tp.Dispatcher.InvokeAsync(() => Refresh(tp));
        }

        private void OnPropertyChanged (object oldvalue, object newvalue)
        {
            if (oldvalue != null && oldvalue.Equals(newvalue)) return;
            RefreshAsync(this);
        }

        #endregion

        #region Inheritable AP string AttachedString

        public static readonly
            DependencyProperty AttachedStringProperty =
                DependencyProperty.RegisterAttached(
                    "AttachedString", typeof(string),
                    typeof(TestParent),
                    new FrameworkPropertyMetadata("Not Set in Parent",
                        FrameworkPropertyMetadataOptions.Inherits, OnStringChanged));

        public static void SetAttachedString(DependencyObject target, string value)
        {
            target.SetValue(AttachedStringProperty, value);
        }

        public static string GetAttachedString(DependencyObject target)
        {
            return (string) target.GetValue(AttachedStringProperty);
        }

        private static void OnStringChanged(DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var instance = d as TestParent;
            if (instance == null) return;
            instance.OnPropertyChanged(e.OldValue, e.NewValue);
        }

        public string AttachedString
        {
            get { return GetAttachedString(this); }
            set { SetAttachedString(this, (string) value); }
        }

        #endregion

        #region DP ObservableCollection<TestChild> MyItems

        public static readonly DependencyProperty MyItemsProperty =
            DependencyProperty.Register(
                "MyItems", typeof(ObservableCollection<TestChild>),
                typeof(TestParent),
                new PropertyMetadata(default(ObservableCollection<TestChild>)));

        public ObservableCollection<TestChild> MyItems
        {
            get { return (ObservableCollection<TestChild>) GetValue(MyItemsProperty); }
            set { SetValue(MyItemsProperty, value); }
        }

        private void MyItem_Changed(object s, PropertyChangedEventArgs args )
        {
            var e = args as MyPropertyChangedEventArgs;
            if (e == null)
                OnPropertyChanged(new object(), new object());
            else
                OnPropertyChanged(e.OldValue, e.NewValue);
        }

        void MyItems_Changed(object d, NotifyCollectionChangedEventArgs e)
        {
            var addedItems = e.NewItems as IList;
            var deletedItems = e.OldItems as IList;

            if (addedItems != null)
            {
                foreach (var addedItem in addedItems)
                {
                    this.AddLogicalChild((TestChild) addedItem);
                    ((TestChild)addedItem).PropertyChanged += MyItem_Changed;
                }
            }
            if (deletedItems != null)
            {
                foreach (var deletedItem in deletedItems)
                {
                    this.RemoveLogicalChild((TestChild) deletedItem);
                    ((TestChild)deletedItem).PropertyChanged -= MyItem_Changed;
                }
            }
        }

        #endregion

        // Connect to Logical Tree
        protected override IEnumerator LogicalChildren
        {
            get { return MyItems.GetEnumerator(); }
        }

        public TestParent()
        {
            MyItems = new ObservableCollection<TestChild>();
            MyItems.CollectionChanged += MyItems_Changed;
        }

        static TestParent()
        {
            // Use standard label template
            DefaultStyleKeyProperty.OverrideMetadata(typeof(TestParent),
                new FrameworkPropertyMetadata(typeof(Label)));
        }
    }

    public class TestChild : FrameworkElement, INotifyPropertyChanged
    {
        #region DP string UnAttachedString

        public static readonly DependencyProperty UnAttachedStringProperty = 
            DependencyProperty.Register(
                "UnAttachedString", typeof(string), 
                typeof(TestChild),
                new PropertyMetadata(default(string), OnStringChanged));

        public string UnAttachedString
        {
            get { return (string) GetValue(UnAttachedStringProperty); }
            set { SetValue(UnAttachedStringProperty, value); }
        }

        #endregion

        #region Inherited AP string AttachedString

        public static readonly DependencyProperty AttachedStringProperty =
            TestParent.AttachedStringProperty.AddOwner(typeof(TestChild),
                new FrameworkPropertyMetadata(
                    (object) TestParent.AttachedStringProperty.DefaultMetadata.DefaultValue,
                        OnStringChangedOnPropertyChanged));

        private static void OnStringChangedOnPropertyChanged(DependencyObject d,
                                            DependencyPropertyChangedEventArgs e)
        {
            ((TestChild) d).OnPropertyChanged(e.OldValue, e.NewValue, e.Property.Name);
        }

        public string AttachedString
        {
            get { return (string) GetValue(AttachedStringProperty); }
            set { SetValue(AttachedStringProperty, (string) value); }
        }

        #endregion

        #region INotifyPropertyChanged implimentation
        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged(object oldValue, object newValue,
            [CallerMemberName] string propertyName = null)
        {
            var handler = PropertyChanged;
            if (handler != null)
                handler(this,
                    new MyPropertyChangedEventArgs(propertyName, oldValue, newValue));
        }

        #endregion
    }

    public class MyPropertyChangedEventArgs : PropertyChangedEventArgs
    {
        public MyPropertyChangedEventArgs(string propertyName, object oldValue, 
            object newValue) : base(propertyName)
        {
            OldValue = oldValue;
            NewValue = newValue;
        }

        public object OldValue;
        public object NewValue;
    }
}
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;

namespace CollectionBinding
{
    public class TestParent : Label
    {
        #region refresh
        private static void Refresh (TestParent instance)
        {
            instance.Content = string.Format("parent value:\t{0}", instance.AttachedString);
            foreach (var myItem in instance.MyItems)
            {
                instance.Content += string.Format("\n  child value:\t{0}\t{1}",
                                myItem.AttachedString, myItem.UnAttachedString);
            }
            instance.Content += string.Format("\n Attached DP References are{0} equal",
                TestParent.AttachedStringProperty == TestChild.AttachedStringProperty
                ? "" : " NOT");

        }

        public static void RefreshAsync(TestParent tp)
        {
            tp.Dispatcher.InvokeAsync(() => Refresh(tp));
        }

        private void OnPropertyChanged (object oldvalue, object newvalue)
        {
            if (oldvalue != null && oldvalue.Equals(newvalue)) return;
            RefreshAsync(this);
        }

        #endregion

        #region Inheritable AP string AttachedString

        public static readonly
            DependencyProperty AttachedStringProperty =
                DependencyProperty.RegisterAttached(
                    "AttachedString", typeof(string),
                    typeof(TestParent),
                    new FrameworkPropertyMetadata("Not Set in Parent",
                        FrameworkPropertyMetadataOptions.Inherits, OnStringChanged));

        public static void SetAttachedString(DependencyObject target, string value)
        {
            target.SetValue(AttachedStringProperty, value);
        }

        public static string GetAttachedString(DependencyObject target)
        {
            return (string) target.GetValue(AttachedStringProperty);
        }

        private static void OnStringChanged(DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var instance = d as TestParent;
            if (instance == null) return;
            instance.OnPropertyChanged(e.OldValue, e.NewValue);
        }

        public string AttachedString
        {
            get { return GetAttachedString(this); }
            set { SetAttachedString(this, (string) value); }
        }

        #endregion

        #region DP ObservableCollection<TestChild> MyItems

        public static readonly DependencyProperty MyItemsProperty =
            DependencyProperty.Register(
                "MyItems", typeof(ObservableCollection<TestChild>),
                typeof(TestParent),
                new PropertyMetadata(default(ObservableCollection<TestChild>)));

        public ObservableCollection<TestChild> MyItems
        {
            get { return (ObservableCollection<TestChild>) GetValue(MyItemsProperty); }
            set { SetValue(MyItemsProperty, value); }
        }

        private void MyItem_Changed(object s, PropertyChangedEventArgs args )
        {
            var e = args as MyPropertyChangedEventArgs;
            if (e == null)
                OnPropertyChanged(new object(), new object());
            else
                OnPropertyChanged(e.OldValue, e.NewValue);
        }

        void MyItems_Changed(object d, NotifyCollectionChangedEventArgs e)
        {
            var addedItems = e.NewItems as IList;
            var deletedItems = e.OldItems as IList;

            if (addedItems != null)
            {
                foreach (var addedItem in addedItems)
                {
                    this.AddLogicalChild((TestChild) addedItem);
                    ((TestChild)addedItem).PropertyChanged += MyItem_Changed;
                }
            }
            if (deletedItems != null)
            {
                foreach (var deletedItem in deletedItems)
                {
                    this.RemoveLogicalChild((TestChild) deletedItem);
                    ((TestChild)deletedItem).PropertyChanged -= MyItem_Changed;
                }
            }
        }

        #endregion

        // Connect to Logical Tree
        protected override IEnumerator LogicalChildren
        {
            get { return MyItems.GetEnumerator(); }
        }

        public TestParent()
        {
            MyItems = new ObservableCollection<TestChild>();
            MyItems.CollectionChanged += MyItems_Changed;
        }

        static TestParent()
        {
            // Use standard label template
            DefaultStyleKeyProperty.OverrideMetadata(typeof(TestParent),
                new FrameworkPropertyMetadata(typeof(Label)));
        }
    }

    public class TestChild : FrameworkElement, INotifyPropertyChanged
    {
        #region DP string UnAttachedString

        public static readonly DependencyProperty UnAttachedStringProperty = 
            DependencyProperty.Register(
                "UnAttachedString", typeof(string), 
                typeof(TestChild),
                new PropertyMetadata(default(string), OnStringChanged));

        public string UnAttachedString
        {
            get { return (string) GetValue(UnAttachedStringProperty); }
            set { SetValue(UnAttachedStringProperty, value); }
        }

        #endregion

        #region Inherited AP string AttachedString

        public static readonly DependencyProperty AttachedStringProperty =
            TestParent.AttachedStringProperty.AddOwner(typeof(TestChild),
                new FrameworkPropertyMetadata(
                    (object) TestParent.AttachedStringProperty.DefaultMetadata.DefaultValue,
                        OnStringChanged));

        private static void OnStringChanged(DependencyObject d,
                                            DependencyPropertyChangedEventArgs e)
        {
            ((TestChild) d).OnPropertyChanged(e.OldValue, e.NewValue, e.Property.Name);
        }

        public string AttachedString
        {
            get { return (string) GetValue(AttachedStringProperty); }
            set { SetValue(AttachedStringProperty, (string) value); }
        }

        #endregion

        #region INotifyPropertyChanged implimentation
        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged(object oldValue, object newValue,
            [CallerMemberName] string propertyName = null)
        {
            var handler = PropertyChanged;
            if (handler != null)
                handler(this,
                    new MyPropertyChangedEventArgs(propertyName, oldValue, newValue));
        }

        #endregion
    }

    public class MyPropertyChangedEventArgs : PropertyChangedEventArgs
    {
        public MyPropertyChangedEventArgs(string propertyName, object oldValue, 
            object newValue) : base(propertyName)
        {
            OldValue = oldValue;
            NewValue = newValue;
        }

        public object OldValue;
        public object NewValue;
    }
}
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;

namespace CollectionBinding
{
    public class TestParent : Label
    {
        #region refresh
        private static void Refresh (TestParent instance)
        {
            instance.Content = string.Format("parent value:\t{0}", instance.AttachedString);
            foreach (var myItem in instance.MyItems)
            {
                instance.Content += string.Format("\n  child value:\t{0}\t{1}",
                                myItem.AttachedString, myItem.UnAttachedString);
            }
            instance.Content += string.Format("\n Attached DP References are{0} equal",
                TestParent.AttachedStringProperty == TestChild.AttachedStringProperty
                ? "" : " NOT");

        }

        public static void RefreshAsync(TestParent tp)
        {
            tp.Dispatcher.InvokeAsync(() => Refresh(tp));
        }

        private void OnPropertyChanged (object oldvalue, object newvalue)
        {
            if (oldvalue != null && oldvalue.Equals(newvalue)) return;
            RefreshAsync(this);
        }

        #endregion

        #region Inheritable AP string AttachedString

        public static readonly
            DependencyProperty AttachedStringProperty =
                DependencyProperty.RegisterAttached(
                    "AttachedString", typeof(string),
                    typeof(TestParent),
                    new FrameworkPropertyMetadata("Not Set in Parent",
                        FrameworkPropertyMetadataOptions.Inherits, OnStringChanged));

        public static void SetAttachedString(DependencyObject target, string value)
        {
            target.SetValue(AttachedStringProperty, value);
        }

        public static string GetAttachedString(DependencyObject target)
        {
            return (string) target.GetValue(AttachedStringProperty);
        }

        private static void OnStringChanged(DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var instance = d as TestParent;
            if (instance == null) return;
            instance.OnPropertyChanged(e.OldValue, e.NewValue);
        }

        public string AttachedString
        {
            get { return GetAttachedString(this); }
            set { SetAttachedString(this, (string) value); }
        }

        #endregion

        #region DP ObservableCollection<TestChild> MyItems

        public static readonly DependencyProperty MyItemsProperty =
            DependencyProperty.Register(
                "MyItems", typeof(ObservableCollection<TestChild>),
                typeof(TestParent),
                new PropertyMetadata(default(ObservableCollection<TestChild>)));

        public ObservableCollection<TestChild> MyItems
        {
            get { return (ObservableCollection<TestChild>) GetValue(MyItemsProperty); }
            set { SetValue(MyItemsProperty, value); }
        }

        private void MyItem_Changed(object s, PropertyChangedEventArgs args )
        {
            var e = args as MyPropertyChangedEventArgs;
            if (e == null)
                OnPropertyChanged(new object(), new object());
            else
                OnPropertyChanged(e.OldValue, e.NewValue);
        }

        void MyItems_Changed(object d, NotifyCollectionChangedEventArgs e)
        {
            var addedItems = e.NewItems as IList;
            var deletedItems = e.OldItems as IList;

            if (addedItems != null)
            {
                foreach (var addedItem in addedItems)
                {
                    this.AddLogicalChild((TestChild) addedItem);
                    ((TestChild)addedItem).PropertyChanged += MyItem_Changed;
                }
            }
            if (deletedItems != null)
            {
                foreach (var deletedItem in deletedItems)
                {
                    this.RemoveLogicalChild((TestChild) deletedItem);
                    ((TestChild)deletedItem).PropertyChanged -= MyItem_Changed;
                }
            }
        }

        #endregion

        // Connect to Logical Tree
        protected override IEnumerator LogicalChildren
        {
            get { return MyItems.GetEnumerator(); }
        }

        public TestParent()
        {
            MyItems = new ObservableCollection<TestChild>();
            MyItems.CollectionChanged += MyItems_Changed;
        }

        static TestParent()
        {
            // Use standard label template
            DefaultStyleKeyProperty.OverrideMetadata(typeof(TestParent),
                new FrameworkPropertyMetadata(typeof(Label)));
        }
    }

    public class TestChild : FrameworkElement, INotifyPropertyChanged
    {
        #region DP string UnAttachedString

        public static readonly DependencyProperty UnAttachedStringProperty = 
            DependencyProperty.Register(
                "UnAttachedString", typeof(string), 
                typeof(TestChild),
                new PropertyMetadata(default(string), OnStringChanged));

        public string UnAttachedString
        {
            get { return (string) GetValue(UnAttachedStringProperty); }
            set { SetValue(UnAttachedStringProperty, value); }
        }

        #endregion

        #region Inherited AP string AttachedString

        public static readonly DependencyProperty AttachedStringProperty =
            TestParent.AttachedStringProperty.AddOwner(typeof(TestChild),
                new FrameworkPropertyMetadata(
                    (object) TestParent.AttachedStringProperty.DefaultMetadata.DefaultValue,
                        OnPropertyChanged));

        private static void OnPropertyChanged(DependencyObject d,
                                            DependencyPropertyChangedEventArgs e)
        {
            ((TestChild) d).OnPropertyChanged(e.OldValue, e.NewValue, e.Property.Name);
        }

        public string AttachedString
        {
            get { return (string) GetValue(AttachedStringProperty); }
            set { SetValue(AttachedStringProperty, (string) value); }
        }

        #endregion

        #region INotifyPropertyChanged implimentation
        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged(object oldValue, object newValue,
            [CallerMemberName] string propertyName = null)
        {
            var handler = PropertyChanged;
            if (handler != null)
                handler(this,
                    new MyPropertyChangedEventArgs(propertyName, oldValue, newValue));
        }

        #endregion
    }

    public class MyPropertyChangedEventArgs : PropertyChangedEventArgs
    {
        public MyPropertyChangedEventArgs(string propertyName, object oldValue, 
            object newValue) : base(propertyName)
        {
            OldValue = oldValue;
            NewValue = newValue;
        }

        public object OldValue;
        public object NewValue;
    }
}
added 79 characters in body
Source Link
Cool Blue
  • 256
  • 2
  • 10

I'm aware that I'm re-inventing a ListControl here but I wanted to try to understand how the framework element plumbing works. There were a few things that felt strange, like having to filter out the inherited changes (although this was eliminated by invoking the Refresh method asynchronously) and implementing INotifyPropertyChanged on dependency properties for example and I'm not sure about the whole child collection approach. Anyway, all feed-back and critique is most welcome.

I'm aware that I'm re-inventing a ListControl here but I wanted to try to understand how the framework element plumbing works. There were a few things that felt strange, like having to filter out the inherited changes and implementing INotifyPropertyChanged on dependency properties for example and I'm not sure about the whole child collection approach. Anyway, all feed-back and critique is most welcome.

I'm aware that I'm re-inventing a ListControl here but I wanted to try to understand how the framework element plumbing works. There were a few things that felt strange, like having to filter out the inherited changes (although this was eliminated by invoking the Refresh method asynchronously) and implementing INotifyPropertyChanged on dependency properties for example and I'm not sure about the whole child collection approach. Anyway, all feed-back and critique is most welcome.

revised to eliminate reflection and filtering of change notifications by making the Refresh async
Source Link
Cool Blue
  • 256
  • 2
  • 10

The DependencyPropertyChangedEventArgs class has usefull state (such as OldValue and NewValue) that is not supported by InotifyPropertyChanged. In order to harmonise the property changed notifications, PropertyChangedEventArgs (from the InotifyPropertyChanged interface) was subclassed to add the additional state.

There was also a notification race condition, due to inheritance, that had to be quenched. Two notification paths are started when the parent AP is changed: notification from the parent and notification from the inheriting childrenchildren; and both paths ended with the Refresh method which needed to modify the parent content property. This The first path was trying to do this while the other was accessing the Logical Tree and this caused an error: changes to state are not allowed while a tree walk is ongoing.

My first attempt to managed this was by using the DependencyPropertyHelper, in the parent, to filter outdetect if the change in value was inherited changesor not and to ignore them if they were. The current version got rid of all of that logic and instead, used the parent instance Dispatcher to queue the updates, running them asynchronously, so that they don't clash. This also eliminated the problem but without using reflection.

using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
using Util;

namespace CollectionBinding
{
    public class TestParent : Label
    {
        public#region refresh
        private static void Refresh (TestParent instance)
        {
            thisinstance.Content = string.Format("parent value:\t{0}", thisinstance.AttachedString);
            foreach (var myItem in thisinstance.MyItems)
            {
                thisinstance.Content += string.Format("\n  child value:\t{0}\t{1}", 
                                myItem.AttachedString, myItem.UnAttachedString);
            }
            thisinstance.Content += string.Format("\n Attached DP References are{0} equal",
                TestParent.AttachedStringProperty == TestChild.AttachedStringProperty
                ? "" : " NOT");

        }

        public static void RefreshAsync(TestParent tp)
        {
            tp.Dispatcher.InvokeAsync(() => Refresh(tp));
        }

        private void OnPropertyChanged (object oldvalue, object newvalue)
        {
            if (oldvalue != null && oldvalue.Equals(newvalue)) return;
            RefreshAsync(this);
        }

        #endregion

        #region Inheritable AP string AttachedString

        public static readonly
            DependencyProperty AttachedStringProperty =
                DependencyProperty.RegisterAttached(
                    "AttachedString", typeof(string),
                    typeof(TestParent),
                    new FrameworkPropertyMetadata("Not Set in Parent",
                        FrameworkPropertyMetadataOptions.Inherits, OnStringChanged));

        public static void SetAttachedString(DependencyObject target, string value)
        {
            target.SetValue(AttachedStringProperty, value);
        }

        public static string GetAttachedString(DependencyObject target)
        {
            return (string) target.GetValue(AttachedStringProperty);
        }

        private static void OnStringChanged(DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var instance = d as TestParent;
            if (instance == null) return;
            instance.OnStringChangedOnPropertyChanged(e.OldValue, e.NewValue);
        }

        public string AttachedString
        {
            get { return GetAttachedString(this); }
            set { SetAttachedString(this, (string) value); }
        }

        private void OnStringChanged (object oldvalue, object newvalue)
        {
            this.Refresh();
        }

        #endregion

        #region DP ObservableCollection<TestChild> MyItems

        public static readonly DependencyProperty MyItemsProperty =
            DependencyProperty.Register(
                "MyItems", typeof(ObservableCollection<TestChild>),
                typeof(TestParent),
                new PropertyMetadata(default(ObservableCollection<TestChild>)));

        public ObservableCollection<TestChild> MyItems
        {
            get { return (ObservableCollection<TestChild>) GetValue(MyItemsProperty); }
            set { SetValue(MyItemsProperty, value); }
        }

        private void MyItem_Changed(object s, PropertyChangedEventArgs args )
        {
            var item = s as TestChild;
            var fielde = Utility.GetMemberByName(item, args.PropertyName + "Property")
                            as FieldInfo;MyPropertyChangedEventArgs;
            if (item == null || fielde == null) return;
            var prop = field.GetValue(item) as DependencyProperty;
            if OnPropertyChanged(prop == null) return;
            // Ignore Inherited changes
            ifnew object(DependencyPropertyHelper
                  ), new .GetValueSourceobject(item, prop)
                    .BaseValueSource == BaseValueSource.Inherited);
            else
    return;
            this.OnStringChangedOnPropertyChanged(thise.OldValue, argse.NewValue);
        }

        void MyItems_Changed(object d, NotifyCollectionChangedEventArgs e)
        {
            var addedItems = e.NewItems as IList;
            var deletedItems = e.OldItems as IList;

            if (addedItems != null)
            {
                foreach (var addedItem in addedItems)
                {
                    this.AddLogicalChild((TestChild) addedItem);
                    ((TestChild)addedItem).PropertyChanged += MyItem_Changed;
                }
            }
            if (deletedItems != null)
            {
                foreach (var deletedItem in deletedItems)
                {
                    this.RemoveLogicalChild((TestChild) deletedItem);
                    ((TestChild)deletedItem).PropertyChanged -= MyItem_Changed;
                }
            }
        }

        #endregion

        // Connect to Logical Tree
        protected override IEnumerator LogicalChildren
        {
            get { return MyItems.GetEnumerator(); }
        }

        public TestParent()
        {
            MyItems = new ObservableCollection<TestChild>();
            ((INotifyCollectionChanged) MyItems).CollectionChanged += MyItems_Changed;
        }

        static TestParent()
        {
            // Use standard label template
            DefaultStyleKeyProperty.OverrideMetadata(typeof(TestParent),
                new FrameworkPropertyMetadata(typeof(Label)));
        }
    }

    public class TestChild : FrameworkElement, INotifyPropertyChanged
    {
        #region DP string UnAttachedString

        public static readonly DependencyProperty UnAttachedStringProperty = 
            DependencyProperty.Register(
                "UnAttachedString", typeof(string), 
                typeof(TestChild),
                new PropertyMetadata(default(string), OnStringChanged));

        public string UnAttachedString
        {
            get { return (string) GetValue(UnAttachedStringProperty); }
            set { SetValue(UnAttachedStringProperty, value); }
        }

        #endregion

        #region Inherited AP string AttachedString

        public static readonly DependencyProperty AttachedStringProperty =
            TestParent.AttachedStringProperty.AddOwner(typeof(TestChild),
                new FrameworkPropertyMetadata(
                    (object) TestParent.AttachedStringProperty.DefaultMetadata.DefaultValue,
                        OnStringChanged));

        private static void OnStringChanged(DependencyObject d,
                                            DependencyPropertyChangedEventArgs e)
        {
            ((TestChild) d).OnPropertyChanged(e.OldValue, e.NewValue, e.Property.Name);
        }

        public string AttachedString
        {
            get { return (string) GetValue(AttachedStringProperty); }
            set { SetValue(AttachedStringProperty, (string) value); }
        }

        #endregion

        #region INotifyPropertyChanged implimentation
        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged(object oldValue, object newValue,
            [CallerMemberName] string propertyName = null)
        {
            var handler = PropertyChanged;
            if (handler != null)
                handler(this,
                    new PropertyChangedEventArgsMyPropertyChangedEventArgs(propertyName, oldValue, newValue));
        }

        #endregion
    }

    public class MyPropertyChangedEventArgs : PropertyChangedEventArgs
    {
        public MyPropertyChangedEventArgs(string propertyName, object oldValue, 
            object newValue) : base(propertyName)
        {
            OldValue = oldValue;
            NewValue = newValue;
        }

        public object OldValue;
        public object NewValue;
    }
}

There was also a notification race condition, due to inheritance, that had to be quenched. Two notification paths are started when the parent AP is changed: notification from the parent and notification from the inheriting children. This was managed by using the DependencyPropertyHelper, in the parent, to filter out inherited changes.

using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
using Util;

namespace CollectionBinding
{
    public class TestParent : Label
    {
        public void Refresh()
        {
            this.Content = string.Format("parent value:\t{0}", this.AttachedString);
            foreach (var myItem in this.MyItems)
            {
                this.Content += string.Format("\n  child value:\t{0}\t{1}", 
                                myItem.AttachedString, myItem.UnAttachedString);
            }
            this.Content += string.Format("\n Attached DP References are{0} equal",
                TestParent.AttachedStringProperty == TestChild.AttachedStringProperty
                ? "" : " NOT");
        }

        #region Inheritable AP string AttachedString

        public static readonly
            DependencyProperty AttachedStringProperty =
                DependencyProperty.RegisterAttached(
                    "AttachedString", typeof(string),
                    typeof(TestParent),
                    new FrameworkPropertyMetadata("Not Set in Parent",
                        FrameworkPropertyMetadataOptions.Inherits, OnStringChanged));

        public static void SetAttachedString(DependencyObject target, string value)
        {
            target.SetValue(AttachedStringProperty, value);
        }

        public static string GetAttachedString(DependencyObject target)
        {
            return (string) target.GetValue(AttachedStringProperty);
        }

        private static void OnStringChanged(DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var instance = d as TestParent;
            if (instance == null) return;
            instance.OnStringChanged(e.OldValue, e.NewValue);
        }

        public string AttachedString
        {
            get { return GetAttachedString(this); }
            set { SetAttachedString(this, (string) value); }
        }

        private void OnStringChanged (object oldvalue, object newvalue)
        {
            this.Refresh();
        }

        #endregion

        #region DP ObservableCollection<TestChild> MyItems

        public static readonly DependencyProperty MyItemsProperty =
            DependencyProperty.Register(
                "MyItems", typeof(ObservableCollection<TestChild>),
                typeof(TestParent),
                new PropertyMetadata(default(ObservableCollection<TestChild>)));

        public ObservableCollection<TestChild> MyItems
        {
            get { return (ObservableCollection<TestChild>) GetValue(MyItemsProperty); }
            set { SetValue(MyItemsProperty, value); }
        }

        private void MyItem_Changed(object s, PropertyChangedEventArgs args )
        {
            var item = s as TestChild;
            var field = Utility.GetMemberByName(item, args.PropertyName + "Property")
                            as FieldInfo;
            if (item == null || field == null) return;
            var prop = field.GetValue(item) as DependencyProperty;
            if (prop == null) return;
            // Ignore Inherited changes
            if (DependencyPropertyHelper
                    .GetValueSource(item, prop)
                    .BaseValueSource == BaseValueSource.Inherited)
                return;
            this.OnStringChanged(this, args);
        }

        void MyItems_Changed(object d, NotifyCollectionChangedEventArgs e)
        {
            var addedItems = e.NewItems as IList;
            var deletedItems = e.OldItems as IList;

            if (addedItems != null)
            {
                foreach (var addedItem in addedItems)
                {
                    this.AddLogicalChild((TestChild) addedItem);
                    ((TestChild)addedItem).PropertyChanged += MyItem_Changed;
                }
            }
            if (deletedItems != null)
            {
                foreach (var deletedItem in deletedItems)
                {
                    this.RemoveLogicalChild((TestChild) deletedItem);
                    ((TestChild)deletedItem).PropertyChanged -= MyItem_Changed;
                }
            }
        }

        #endregion

        // Connect to Logical Tree
        protected override IEnumerator LogicalChildren
        {
            get { return MyItems.GetEnumerator(); }
        }

        public TestParent()
        {
            MyItems = new ObservableCollection<TestChild>();
            ((INotifyCollectionChanged) MyItems).CollectionChanged += MyItems_Changed;
        }

        static TestParent()
        {
            // Use standard label template
            DefaultStyleKeyProperty.OverrideMetadata(typeof(TestParent),
                new FrameworkPropertyMetadata(typeof(Label)));
        }
    }

    public class TestChild : FrameworkElement, INotifyPropertyChanged
    {
        #region DP string UnAttachedString

        public static readonly DependencyProperty UnAttachedStringProperty = 
            DependencyProperty.Register(
                "UnAttachedString", typeof(string), 
                typeof(TestChild),
                new PropertyMetadata(default(string), OnStringChanged));

        public string UnAttachedString
        {
            get { return (string) GetValue(UnAttachedStringProperty); }
            set { SetValue(UnAttachedStringProperty, value); }
        }

        #endregion

        #region Inherited AP string AttachedString

        public static readonly DependencyProperty AttachedStringProperty =
            TestParent.AttachedStringProperty.AddOwner(typeof(TestChild),
                new FrameworkPropertyMetadata(
                    (object) TestParent.AttachedStringProperty.DefaultMetadata.DefaultValue,
                        OnStringChanged));

        private static void OnStringChanged(DependencyObject d,
                                            DependencyPropertyChangedEventArgs e)
        {
            ((TestChild) d).OnPropertyChanged(e.Property.Name);
        }

        public string AttachedString
        {
            get { return (string) GetValue(AttachedStringProperty); }
            set { SetValue(AttachedStringProperty, (string) value); }
        }

        #endregion

        #region INotifyPropertyChanged implimentation
        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged(
            [CallerMemberName] string propertyName = null)
        {
            var handler = PropertyChanged;
            if (handler != null)
                handler(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion
    }
}

The DependencyPropertyChangedEventArgs class has usefull state (such as OldValue and NewValue) that is not supported by InotifyPropertyChanged. In order to harmonise the property changed notifications, PropertyChangedEventArgs (from the InotifyPropertyChanged interface) was subclassed to add the additional state.

There was also a notification race condition, due to inheritance, that had to be quenched. Two notification paths are started when the parent AP is changed: notification from the parent and notification from the inheriting children; and both paths ended with the Refresh method which needed to modify the parent content property. The first path was trying to do this while the other was accessing the Logical Tree and this caused an error: changes to state are not allowed while a tree walk is ongoing.

My first attempt to managed this was by using the DependencyPropertyHelper, in the parent, to detect if the change in value was inherited or not and to ignore them if they were. The current version got rid of all of that logic and instead, used the parent instance Dispatcher to queue the updates, running them asynchronously, so that they don't clash. This also eliminated the problem but without using reflection.

using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;

namespace CollectionBinding
{
    public class TestParent : Label
    {
        #region refresh
        private static void Refresh (TestParent instance)
        {
            instance.Content = string.Format("parent value:\t{0}", instance.AttachedString);
            foreach (var myItem in instance.MyItems)
            {
                instance.Content += string.Format("\n  child value:\t{0}\t{1}",
                                myItem.AttachedString, myItem.UnAttachedString);
            }
            instance.Content += string.Format("\n Attached DP References are{0} equal",
                TestParent.AttachedStringProperty == TestChild.AttachedStringProperty
                ? "" : " NOT");

        }

        public static void RefreshAsync(TestParent tp)
        {
            tp.Dispatcher.InvokeAsync(() => Refresh(tp));
        }

        private void OnPropertyChanged (object oldvalue, object newvalue)
        {
            if (oldvalue != null && oldvalue.Equals(newvalue)) return;
            RefreshAsync(this);
        }

        #endregion

        #region Inheritable AP string AttachedString

        public static readonly
            DependencyProperty AttachedStringProperty =
                DependencyProperty.RegisterAttached(
                    "AttachedString", typeof(string),
                    typeof(TestParent),
                    new FrameworkPropertyMetadata("Not Set in Parent",
                        FrameworkPropertyMetadataOptions.Inherits, OnStringChanged));

        public static void SetAttachedString(DependencyObject target, string value)
        {
            target.SetValue(AttachedStringProperty, value);
        }

        public static string GetAttachedString(DependencyObject target)
        {
            return (string) target.GetValue(AttachedStringProperty);
        }

        private static void OnStringChanged(DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var instance = d as TestParent;
            if (instance == null) return;
            instance.OnPropertyChanged(e.OldValue, e.NewValue);
        }

        public string AttachedString
        {
            get { return GetAttachedString(this); }
            set { SetAttachedString(this, (string) value); }
        }

        #endregion

        #region DP ObservableCollection<TestChild> MyItems

        public static readonly DependencyProperty MyItemsProperty =
            DependencyProperty.Register(
                "MyItems", typeof(ObservableCollection<TestChild>),
                typeof(TestParent),
                new PropertyMetadata(default(ObservableCollection<TestChild>)));

        public ObservableCollection<TestChild> MyItems
        {
            get { return (ObservableCollection<TestChild>) GetValue(MyItemsProperty); }
            set { SetValue(MyItemsProperty, value); }
        }

        private void MyItem_Changed(object s, PropertyChangedEventArgs args )
        {
            var e = args as MyPropertyChangedEventArgs;
            if (e == null)
                OnPropertyChanged(new object(), new object());
            else
                OnPropertyChanged(e.OldValue, e.NewValue);
        }

        void MyItems_Changed(object d, NotifyCollectionChangedEventArgs e)
        {
            var addedItems = e.NewItems as IList;
            var deletedItems = e.OldItems as IList;

            if (addedItems != null)
            {
                foreach (var addedItem in addedItems)
                {
                    this.AddLogicalChild((TestChild) addedItem);
                    ((TestChild)addedItem).PropertyChanged += MyItem_Changed;
                }
            }
            if (deletedItems != null)
            {
                foreach (var deletedItem in deletedItems)
                {
                    this.RemoveLogicalChild((TestChild) deletedItem);
                    ((TestChild)deletedItem).PropertyChanged -= MyItem_Changed;
                }
            }
        }

        #endregion

        // Connect to Logical Tree
        protected override IEnumerator LogicalChildren
        {
            get { return MyItems.GetEnumerator(); }
        }

        public TestParent()
        {
            MyItems = new ObservableCollection<TestChild>();
            MyItems.CollectionChanged += MyItems_Changed;
        }

        static TestParent()
        {
            // Use standard label template
            DefaultStyleKeyProperty.OverrideMetadata(typeof(TestParent),
                new FrameworkPropertyMetadata(typeof(Label)));
        }
    }

    public class TestChild : FrameworkElement, INotifyPropertyChanged
    {
        #region DP string UnAttachedString

        public static readonly DependencyProperty UnAttachedStringProperty = 
            DependencyProperty.Register(
                "UnAttachedString", typeof(string), 
                typeof(TestChild),
                new PropertyMetadata(default(string), OnStringChanged));

        public string UnAttachedString
        {
            get { return (string) GetValue(UnAttachedStringProperty); }
            set { SetValue(UnAttachedStringProperty, value); }
        }

        #endregion

        #region Inherited AP string AttachedString

        public static readonly DependencyProperty AttachedStringProperty =
            TestParent.AttachedStringProperty.AddOwner(typeof(TestChild),
                new FrameworkPropertyMetadata(
                    (object) TestParent.AttachedStringProperty.DefaultMetadata.DefaultValue,
                        OnStringChanged));

        private static void OnStringChanged(DependencyObject d,
                                            DependencyPropertyChangedEventArgs e)
        {
            ((TestChild) d).OnPropertyChanged(e.OldValue, e.NewValue, e.Property.Name);
        }

        public string AttachedString
        {
            get { return (string) GetValue(AttachedStringProperty); }
            set { SetValue(AttachedStringProperty, (string) value); }
        }

        #endregion

        #region INotifyPropertyChanged implimentation
        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged(object oldValue, object newValue,
            [CallerMemberName] string propertyName = null)
        {
            var handler = PropertyChanged;
            if (handler != null)
                handler(this,
                    new MyPropertyChangedEventArgs(propertyName, oldValue, newValue));
        }

        #endregion
    }

    public class MyPropertyChangedEventArgs : PropertyChangedEventArgs
    {
        public MyPropertyChangedEventArgs(string propertyName, object oldValue, 
            object newValue) : base(propertyName)
        {
            OldValue = oldValue;
            NewValue = newValue;
        }

        public object OldValue;
        public object NewValue;
    }
}
added 94 characters in body; edited title
Source Link
Jamal
  • 35.2k
  • 13
  • 134
  • 238
Loading
expanded on a few points
Source Link
Cool Blue
  • 256
  • 2
  • 10
Loading
Source Link
Cool Blue
  • 256
  • 2
  • 10
Loading