#157 – You Can Set Standard CLR Properties from XAML

If you create a custom class, you can instantiate instances of that class from XAML by adding the object to a resource dictionary.

    <Window.Resources>
        <m:Person x:Key="perBill" FirstName="William" LastName="Shakespeare" />
    </Window.Resources>

You might wonder whether your properties have to be WPF dependency properties in order to set their values from XAML.  It turns out that the properties on the custom class do not have to be dependency properties in order to be set from XAML.  They can be standard CLR properties.

    public class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }

        public Person()
        {
        }
    }

#156 – Using the Tag Property to Store Custom Data with an Element

In WPF, the FrameworkElement class includes a Tag property that allows storing some arbitrary data with an element.

You’d normally use the Tag property to store some custom data on an element that inherits from FrameworkElement.  In the example below, we store some custom data with two different Button objects.

        <Button Tag="Some arbitrary data" Content="Test" Height="23" Width="75" Name="btnTest" Click="btnTest_Click"/>
        <Button Tag="{StaticResource greenBrush}" Content="Test 2" Height="23" Width="75" />

However, because you can attach a dependency property value to any object deriving from DependencyObject, you can also use the Tag property to attach data to objects that don’t derive from FrameworkElement or to dependency objects that are otherwise sealed.

            // ListView has a GridView as its view
            GridView gv1 = (GridView)listView1.View;

            // Attach a Tag to the GridView -- a Person object
            gv1.SetValue(FrameworkElement.TagProperty, new Person("Herodotus"));

#155 – Implementing an Attached Dependency Property

When you implement a dependency property that will be used as a XAML attached property, you use the DependencyProperty.RegisterAttached method, rather than the DependencyProperty.Register method.  The signature of the RegisterAttached method, as well as all parameters, is identical to Register.

Below is an example, where we register the Person.AgeProperty, which we intend to use as a XAML attached property.

        static PropertyMetadata ageMetadata =
            new PropertyMetadata(0, null, new CoerceValueCallback(CoerceAge));

        public static readonly DependencyProperty AgeProperty =
            DependencyProperty.RegisterAttached("Age", typeof(int), typeof(Person), ageMetadata);

        public static void SetAge(DependencyObject depObj, int value)
        {
            depObj.SetValue(AgeProperty, value);
        }

Note that because we intend to use Age as an attached property, we must also implement the SetAge method.  (Standard CLR property wrapper is also required, but not shown).

#154 – Reusing an Existing Dependency Property in Your Class

We saw earlier how to register a new dependency property in a custom class using the DependencyProperty.Register method.  You can also reuse an existing dependency property, using the DependencyProperty.AddOwner method.

When you reuse an existing dependency property, you can optionally specify new metadata that applies to the use of the dependency property in your new type.  You should also define CLR properties in the new type that wrap the GetValue/SetValue calls to read/write dependency property values.

Here’s an example, where we reuse the BirthYearProperty, originally defined in a Person class, in a new Dog class.  Notice that we also provide a new default value.

        // Dog also has a BirthYear property
        public static readonly DependencyProperty BirthYearProperty =
            Person.BirthYearProperty.AddOwner(
                typeof(Dog),
                new PropertyMetadata(2000, new PropertyChangedCallback(OnBirthYearChanged)));

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

        public static void OnBirthYearChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
        }

#153 – You Can Set the Value of any Dependency Property for any Dependency Object

A dependency object will typically use its SetValue method internally, to set dependency property values for dependency properties that the object implements.  (See How Dependency Properties Are Implemented).

A DependencyObject can, however, store the value of any dependency property–not just the properties that it implements.

The DependencyObject.SetValue method takes a dependency property reference and a property value (whose type should match the type of the dependency property).  Using SetValue, you can actually attach any property value you choose to the dependency object.

Here’s an example, where we set various property values on a Person, which is a DependencyObject.  These properties are defined in other WPF classes, but it might be useful to attach these property values to a Person object, if it can make use of them.

            Person p = new Person("Samuel", "Clemens");

            // Set some some arbitrary property values
            p.SetValue(FrameworkElement.ContextMenuProperty, myMenu);
            p.SetValue(UIElement.FocusableProperty, true);
            p.SetValue(Control.FontSizeProperty, 12.0);

#152 – Use ReadLocalValue() to Find the Local Value of a Dependency Property

You can use a DependencyObject‘s ReadLocalValue method to get the local value of a dependency property, rather than its effective value.  Recall that the local value is a value set locally, from XAML or code, which takes precedence over all other sources for the base value of a dependency property.  The local value has lower precedence than any coerced values.

Using ReadLocalValue (assuming that this refers to a DependencyObject that registers AgeProperty):

            int ageValue = (int)this.GetValue(AgeProperty);         // Effective value
            int ageLocal = (int)this.ReadLocalValue(AgeProperty);   // Local value

Getting this information for the Age/SuperOld example, we get the following:

            p.Age = 28;
            info = p.AgeInfo();   // Value=28, Local=28

            p.SuperOld = true;
            info = p.AgeInfo();   // Value=999, Local=28

            p.Age = 56;
            info = p.AgeInfo();   // Value=999, Local=56

            p.SuperOld = false;
            info = p.AgeInfo();   // Value=56, Local=56

#151 – Dependency Properties Remember Non-Coerced Values

When you set a dependency property to some value and it is coerced, the original base value that you set is remembered.  If you remove the conditions that led to the coercion, the property will take on the non-coerced value that you originally set.

Suppose we have a Person class with an Age property and a boolean SuperOld property.  Assume that Age is coerced to a value of 999 if SuperOld is true.

If you set Age to some local value and SuperOld is true, Age will get coerced to 999.  But if you later set SuperOld to false, the Age property will revert to the last local value that you tried to set.

            Person p = new Person("Methuselah");

            p.Age = 28;
            p.SuperOld = true;    // Age coerced to 999

            p.Age = 56;           // Age still 999
            p.SuperOld = false;   // Age now becomes 56

This assumes that the PropertyChanged callback for SuperOld calls CoerceValue on the Age property.  (Likely true).

#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.

#148 – Property Values Set Using Expressions Overwrite the Base Value

In the list of all possible sources for a dependency property value, we explain that a property’s base value can in turn be overwritten by the result of an expression.  If the property’s value is set using an expression, the value of the expression takes precedence over the property’s base value.

In WPF, if the value of a dependency property is set using an expression, the expression can be one of two types:

  • Data binding – set the value of the property by binding it to another property
  • Resources – set the value of the property to a resource, loaded from a resource dictionary

 

Follow

Get every new post delivered to your Inbox.

Join 129 other followers