#160 – Be Careful When Setting Dependency Property Values from a DependencyObject Constructor

You should normally avoid calling any virtual methods in a C# class constructor.  Your constructor might get called during construction of a derived class, before the derived class has finished initializing everything that it needs to initialize.  If the derived class also overrides the virtual method that you’re calling, that method could end up getting called before the derived class has finished initializing everything (in its constructor).

If you use FxCop to check your code, this will be caught by the DoNotCallOverridableMethodsInConstructors rule.

This rule applies to WPF.  If you set the value of a dependency property in a constructor, virtual methods or callbacks in the property system may end up getting called.

The best way to avoid the problem is to follow the following rule:

  • Initialize all objects that might be used by property system callbacks in your default (parameterless) constructor

See also: Safe Constructor Patterns for DependencyObjects

#159 – Creating a Read-Only Dependency Property

Creating a custom dependency property that is read-only is slightly different from creating a standard dependency property.

  • Use DependencyProperty.RegisterReadOnly, rather than Register
  • Create an internal static copy of a DependencyPropertyKey in addition to a public static DependencyProperty
  • Make the CLR property wrapper read-only

For example, assume that we want to add a read-only IQ property to our Person class.

We register the property as read-only and retrieve a DependencyPropertyKey.

        internal static readonly DependencyPropertyKey IQPropertyKey =
            DependencyProperty.RegisterReadOnly("IQ", typeof(int), typeof(Person), new PropertyMetadata(100));

Although the key is private, we make the property public.

        public static readonly DependencyProperty IQProperty =
            IQPropertyKey.DependencyProperty;

We can still set the value internally, by using the key.

public Person(string first, string last, int iq)
{
    FirstName = first;
    LastName = last;
    SetValue(IQPropertyKey, 100);
}

Finally, we provide a CLR property wrapper.

        public int IQ
        {
            get { return (int)GetValue(IQProperty); }
        }

#158 – When to Create a Custom Dependency Property

When you’re implementing a new class and creating properties of that class, you need to decide for each property whether to make it a full-fledged WPF dependency property.

If a property is a standard CLR property, rather than a dependency property, you can still set property values from XAML.

But you might want to create a custom dependency property if you want your class to do any of the following:

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

Follow

Get every new post delivered to your Inbox.

Join 351 other followers