#150 – An Example of Using PropertyChanged and CoerceValue Callbacks

The PropertyChangedCallback and CoerceValueCallback can be used to enforce relationships between properties on an object.  Here’s an example, showing the relationship between BirthYear, MarriageYear and DeathYear properties on a Person object.

The BirthYear property:

        public int BirthYear
        {
            get { return (int)GetValue(BirthYearProperty); }
            set { SetValue(BirthYearProperty, value); }
        }

        public static readonly DependencyProperty BirthYearProperty =
            DependencyProperty.Register("BirthYear", typeof(int), typeof(Person),
                new PropertyMetadata(
                    1900,       // Default
                    new PropertyChangedCallback(OnBirthYearChanged)));

        public static void OnBirthYearChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Person p = (Person)d;
            p.CoerceValue(DeathYearProperty);
            p.CoerceValue(MarriageYearProperty);
        }

The DeathYear property:

        public int DeathYear
        {
            get { return (int)GetValue(DeathYearProperty); }
            set { SetValue(DeathYearProperty, value); }
        }

        // Using a DependencyProperty as the backing store for DeathYear.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DeathYearProperty =
            DependencyProperty.Register("DeathYear", typeof(int), typeof(Person),
                new PropertyMetadata(
                    1900,       // Default
                    new PropertyChangedCallback(OnDeathYearChanged),
                    new CoerceValueCallback(CoerceDeathYear)));

        public static void OnDeathYearChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Person p = (Person)d;
            p.CoerceValue(MarriageYearProperty);
        }

        public static object CoerceDeathYear(DependencyObject d, object value)
        {
            Person p = (Person)d;
            int deathYear = (int)value;

            if (deathYear < p.BirthYear)
                deathYear = p.BirthYear;

            return deathYear;
        }

The MarriageYear property:

        public int MarriageYear
        {
            get { return (int)GetValue(MarriageYearProperty); }
            set { SetValue(MarriageYearProperty, value); }
        }

        // Using a DependencyProperty as the backing store for DeathYear.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MarriageYearProperty =
            DependencyProperty.Register("MarriageYear", typeof(int), typeof(Person),
                new PropertyMetadata(
                    1900,       // Default
                    null,
                    new CoerceValueCallback(CoerceMarriageYear)));

        public static object CoerceMarriageYear(DependencyObject d, object value)
        {
            Person p = (Person)d;
            int marriageYear = (int)value;

            if (marriageYear < p.BirthYear)
                marriageYear = p.BirthYear;
            if (marriageYear > p.DeathYear)
                marriageYear = p.DeathYear;

            return marriageYear;
        }

#149 – Use PropertyChanged and Coercion Callbacks to Force Dependencies Between Properties

When implementing dependency properties, you typically use the CoerceValueCallback and PropertyChangedCallbacks to enforce relationships between properties.

For example, assume we have a Person class with BirthYear, DeathYear and MarriageYear properties.  We want to ensure that MarriageYear is never earlier than BirthYear or later than DeathYear.  We want these checks done any time that we change any of the three properties.

The basic plan is:

  • Allow setting BirthYear to anything
  • When setting DeathYear, don’t let it be earlier than BirthYear
  • When setting MarriageYear, don’t let it be earlier than birth or later than death

When registering the dependency properties, we therefore do the following:

  • BirthYear
    • In PropertyChangedCallback, force coercion of DeathYear and MarriageYear
  • DeathYear
    • In PropertyChangedCallback, force coercion of MarriageYear
    • In CoerceValueCallback, if value earlier than birth, set to BirthYear
  • MarriageYear
    • In CoerceValueCallback, if marriage earlier than birth, set to birth year; if later than death, set to death year

Here’s an example.

#124 – One Example of WPF’s Use of Dependency Property Callbacks

There are three callbacks related to dependency properties that a class can respond to:

  • PropertyChangedCallback – react to a new value
  • ValidateValueCallback – determine whether a value is valid
  • CoerceValueCallback – coerce a requested value to some different value

The DataGrid.FrozenColumnCount property is an example of a dependency property in a WPF class that implements all three callbacks.

FrozenColumnCount is used to specified the leftmost n columns in a data grid that should not scroll horizontally.  These columns remain fixed while the other columns scroll.

DataGrid uses the dependency property callbacks for this property as follows:

  • PropertyChangedCallback – notify constituent controls in the DataGrid of the new value so that they can render properly.  (E.g. the control that renders the column headers).
  • ValidateValueCallback – validation fails if a negative value is used for FrozenColumnCount
  • CoerceValueCallback – if value greater than the number of columns is specified, coerce FrozenColumnCount to be equal to the number of columns.