9

I have a class called MyComponent and it has a DependencyProperty caled BackgroundProperty.

public class MyComponent
{
    public MyBackground Background
    {
        get { return (MyBackground)GetValue(BackgroundProperty); }
        set { SetValue(BackgroundProperty, value); }
    }
    public static readonly DependencyProperty BackgroundProperty =
        DependencyProperty.Register("Background", typeof(MyBackground),
            typeof(MyComponent), new FrameworkPropertyMetadata(default(MyBackground), new PropertyChangedCallback(OnPropertyChanged)));
}

MyBackground is a class that derives from DependencyObject and it has some DependencyProperties.

public class MyBackground : DependencyObject
{
    public Color BaseColor
    {
        set { SetValue(BaseColorProperty, value); }
        get { return (Color)GetValue(BaseColorProperty); }
    }
    public static readonly DependencyProperty BaseColorProperty =
        DependencyProperty.Register("BaseColor", typeof(Color),
            typeof(MyBackground ), new UIPropertyMetadata(Colors.White));

    [...]
}

Now, what I want is when a property from MyBackground is changed, MyComponent to be notified that MyBackground has changed and the PropertyChangedCallback named OnPropertyChanged to be called.

morsanu
  • 975
  • 20
  • 35
  • I am kinda confused as to why do you need that. Usually it's the other way around, where DP are used for bindings and when those change, you want to notify the DP. Why would you need it the other way around? – Omri Btian Oct 14 '13 at 13:57
  • What do you mean this is backwards @Omribitan? This is standard WPF. If I modify a dependency property's value, all the things bound to that property know about it instantly. This is kind of what dependency properties are for -- and WPF's data binding is built on this concept. – BrainSlugs83 Nov 06 '13 at 07:36
  • @BrainSlugs83 Think of a control's Visibility bounded to a property on the ViewModel's class, let's call it `IsVisibile`. the `Visibility` is the DP and the `IsVisibile` is a simple property. What usually happens is when `IsVisible` changes you want to notify the UI (Mostly by using `INotifyPropertyChanged`) to let the `DP` know it's value changed, not the other way around ... – Omri Btian Nov 06 '13 at 07:41

4 Answers4

3

Bear with me for a second because it appears that you are trying to go against the grain of WPF. Since it seems you are writing code related to display logic, the typical method for getting related DependencyObjects to interact with one another is through bindings.

If, for example, MyComponent is a control of some sort and it uses the Background property in its ControlTemplate, you would use a TemplateBinding that references the Background property and any important sub-properties.

Since 1) you probably already know that and 2) you either aren't using templates or don't have them available, you can set up a binding in code in order to react to changes in to the Background property. If you provide more detail about what your OnPropertyChanged method does I can provide some sample code.

kcnygaard
  • 794
  • 7
  • 18
3

One way to do what you describe would be to derive from Freezable instead of DependencyObject. When a property of a Freezable changes the PropertyChangedCallback for any DO referencing that Freezable will be invoked so the callback for the Background property of your MyComponent. In that case the e.OldValue and e.NewValue will be the same reference. Internally WPF has some flag on the event args that indicates that it is a subobject change.

This is what the framework does for things like brushes so that an element can be invalidated if say the Color property of a SolidColorBrush is changed. If an object will never be changed (or you want to make it thread safe) then one can freezing the object (i.e. making it immutable).

BTW I would probably avoid using Background as the name of the property. Most developers will assume that is of type Brush as that is what the framework uses for that named property on several of its elements (e.g. control, border).

AndrewS
  • 6,054
  • 24
  • 31
2

Sounds like you want to use a DependencyPropertyDescriptor and AddValueChanged.

Here's an article on it: http://www.codeproject.com/Articles/34741/Change-Notification-for-Dependency-Properties.aspx

..and possibly a better implementation: http://agsmith.wordpress.com/2008/04/07/propertydescriptor-addvaluechanged-alternative/

scobi
  • 14,252
  • 13
  • 80
  • 114
1

Here's a small static class of extension methods I wrote for WPF -- it allows you to register an EventHandler or an Action callback for the changing of any DependencyProperty on any DependencyObject. No changes to the dependency object are necessary.

It also prevents recursion (i.e. if you change that same property during the callback, etc..)

It takes advantage of the DependencyPropertyDescriptor that @ScottBilas linked to.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows;

namespace BrainSlugs83.Writes.Too.Much.Code
{
    public static class WpfExtensions
    {
        public static void OnPropertyChanged<T>(this T obj, DependencyProperty prop, Action<T> callback) where T : DependencyObject
        {
            if (callback != null)
            {
                obj.OnPropertyChanged(prop, new EventHandler((o, e) =>
                {
                    callback((T)o);
                }));
            }
        }

        public static void OnPropertyChanged<T>(this T obj, DependencyProperty prop, EventHandler handler) where T : DependencyObject
        {
            var descriptor = DependencyPropertyDescriptor.FromProperty(prop, typeof(T));
            descriptor.AddValueChanged(obj, new EventHandler((o, e) =>
            {
                if (handler != null)
                {
                    if (o == null) { handler(o, e); }
                    else
                    {
                        lock (PreventRecursions)
                        {
                            if (IsRecursing(obj, prop)) { return; }
                            SetIsRecursing(obj, prop, true);
                        }

                        try
                        {
                            handler(o, e);
                        }
                        finally
                        {
                            SetIsRecursing(obj, prop, false);
                        }
                    }
                }
            }));
        }

        #region OnPropertyChanged Recursion Prevention

        private static readonly Dictionary<object, List<DependencyProperty>> PreventRecursions = new Dictionary<object, List<DependencyProperty>>();

        private static bool IsRecursing(object obj, DependencyProperty prop)
        {
            lock (PreventRecursions)
            {
                List<DependencyProperty> propList = null;
                if (PreventRecursions.ContainsKey(obj))
                {
                    propList = PreventRecursions[obj];
                }

                return propList == null ? false : propList.Contains(prop);
            }
        }

        private static void SetIsRecursing(object obj, DependencyProperty prop, bool value)
        {
            lock (PreventRecursions)
            {
                List<DependencyProperty> propList = null;
                if (PreventRecursions.ContainsKey(obj))
                {
                    propList = PreventRecursions[obj];
                }

                if (propList == null)
                {
                    if (!value) { return; }

                    propList = PreventRecursions[obj] = new List<DependencyProperty>();
                }

                if (value)
                {
                    if (!propList.Contains(prop))
                    {
                        propList.Add(prop);
                    }
                }
                else
                {
                    while (propList.Contains(prop))
                    {
                        propList.Remove(prop);
                    }

                    if (!propList.Any())
                    {
                        propList = PreventRecursions[obj] = null;
                    }
                }
            }
        }

        #endregion

        public static bool IsInDesignMode(this DependencyObject obj)
        {
            try
            {
                return DesignerProperties.GetIsInDesignMode(obj);
            }
            catch { /* do nothing */ }

            return false;
        }
    }
}
BrainSlugs83
  • 6,214
  • 7
  • 50
  • 56