#131 – Dependency Properties Inherit Values from Higher Up in the Logical Tree

The value of a dependency property can come from a number of different sources, but the property often inherits its value from an element further up the logical tree.

This means that when you set a property value in XAML or in code, that value often “trickles down” the element tree and is applied to other elements that have a property of the same name.

Here’s an example.  The value of FontStyle for several controls is inherited from the top-level Window element.

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:m="clr-namespace:PersonLib;assembly=PersonLib"
        Title="MainWindow" Height="350" Width="525" FontStyle="Italic">
    <StackPanel Orientation="Vertical">
        <Button Content="Run" Height="23" Width="75" />
        <Button Content="Skip" Height="23" Width="75" />
        <StackPanel Orientation="Horizontal">
            <Label Content="Inside 2nd StackPanel"/>
            <Label Content="I do my own FontStyle" FontStyle="Normal"/>
        </StackPanel>
    </StackPanel>
</Window>

Here’s what the window looks like:

The value for a dependency property will not be inherited if that value is set explicitly (locally) in an element.

#130 – WPF Supports Three Types of Triggers

WPF supports three different types of triggers:

  • Property triggers
    • Fire when the value of a dependency property changes
    • Specifies trigger property using property name
    • Actions
      • Setter elements set values for one or more dependency properties
      • One or more TriggerAction classes fire, when trigger becomes active or becomes inactive
  • Data triggers
    • Fire when the value of a CLR property changes
    • Specifies property using Binding keyword
    • Actions
      • Setter elements sets values for one or more dependency properties
      • One or more TriggerAction classes fire, when trigger becomes active or becomes inactive
  • Event triggers
    • Fire when a routed event is raised
    • Action: Class that derives from TriggerAction fires, e.g. BeginStoryboard or SoundPlayerAction
    • Often used for animations

#129 – Properties Changed by Triggers Are Automatically Reset

When you change a property value using a property trigger, the original value of the property will be restored once the conditions of the trigger are no longer true.

For example, you could create a style that applies a drop shadow to a button whenever the user hovers over it, then apply the style to several buttons, as follows:

    <Window.Resources>
        <Style x:Key="hoverStyle" TargetType="Button">
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="true">
                    <Setter Property="Button.Effect">
                        <Setter.Value>
                            <DropShadowEffect/>
                        </Setter.Value>
                    </Setter>
                </Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
        <Button Content="Run" Height="23" Width="75" Style="{StaticResource hoverStyle}"/>
        <Button Content="Skip" Height="23" Width="75" Style="{StaticResource hoverStyle}"/>
        <Button Content="Jump" Height="23" Width="75" Style="{StaticResource hoverStyle}"/>
    </StackPanel>

As you hover over a button, it gets a drop shadow.

Notice, however, that as you move on to the next button, the value of IsMouseOver for the first button becomes false and the original value of the Button.Effect property is restored–i.e. the drop shadow is automatically removed.

#128 – Using Code Snippets to Implement a Dependency Property

Visual Studio 2010 includes a code snippet that makes it easier to implement a dependency property in your own class.

To start, right-click on an empty line in your class and pick Insert Snippet.

Double-click on the NetFX30 category.

Then select Define a DependencyProperty.

The code for the code snippet will be added.  (Alternatively, just enter “propdp” in the code editor and press TAB twice).

At this point, you can dbl-click on the property name or its type to enter new values.  When you change the value, it will be changed accordingly throughout the rest of the code snippet.

#127 – Reacting to a Dependency Property Change Using Triggers

You can react to a dependency property’s value changing by using property triggers.  A property trigger allows you to set up a trigger that fires when a property has a specific value and when the trigger fires, set the value of a different property on the same object.

Because you can only fire the trigger based on discrete values, you often use property triggers that are associated with simple boolean properties.

Here’s an example where we change the Foreground color of a CheckBox control whenever the CheckBox is checked.

        <CheckBox Content="Check Me" HorizontalAlignment="Center">
            <CheckBox.Style>
                <Style TargetType="CheckBox">
                    <Style.Triggers>
                        <Trigger Property="IsChecked" Value="true">
                            <Setter Property="Foreground" Value="BlueViolet"/>
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </CheckBox.Style>
        </CheckBox>

Notice that we can achieve the desired behavior without having to write any code, specifying the behavior entirely in XAML.

#126 – Reacting to a Dependency Property Change Using Binding

One way to react to a dependency property’s value changing is by binding the property of one control to the property of another control.

The idea with binding is that there is a source object and property, where the data is coming from, and a target object and property.

Here’s an example where we bind the contents of a Label control to the value of a Slider.

        <Slider Name="slider1" Maximum="100" />
        <Label Name="lblTest" Content="{Binding ElementName=slider1, Path=Value}"/>

The Label is the target control.  We bind its Content property to the Slider control’s Value content.  When a user slides the slider, its value will change and the new value will be displayed in the Label.

Binding the property of one control to the property of another control

#125 – Responding to Changes in Dependency Properties for WPF Objects

If you author a class that implements a dependency property, you can respond to changes in the value of the property value using the PropertyChangedCallback.  You can specify this callback when registering the dependency property.

For standard WPF objects or controls, however, you can’t subscribe to this same callback to be notified when a property value changes.  It’s possible that a particular WPF class fires an event when a property value changes, but this isn’t always the case.

There are two main ways that code which uses WPF objects can react to dependency property value changes:

  • Using data binding
  • Using triggers

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

#123 – Coercing a Dependency Property

A class that implements a dependency property can optionally provide a coercion callback, which it specifies when registering the property.  A coercion callback is called when a property is about to get a new value and gives the class a chance to coerce the property value to a different value.

You specify a validation callback when registering a dependency property, using the CoerceValueCallback delegate.

            PropertyMetadata ageMetadata =
                new PropertyMetadata(
                    18,     // Default value
                    new PropertyChangedCallback(OnAgeChanged),
                    new CoerceValueCallback(OnAgeCoerceValue));    // ** allow class to coerce value

            // Register the property
            AgeProperty =
                DependencyProperty.Register(
                    "Age",                 // Property's name
                    typeof(int),           // Property's type
                    typeof(Person),        // Defining class' type
                    ageMetadata,           // Defines default value & changed/coercion callbacks  (optional)
                    new ValidateValueCallback(OnAgeValidateValue));   // validation (optional)

You might use coercion to enforce minimum and maximum values for a property.

        private static object OnAgeCoerceValue
            (DependencyObject depObj, object baseValue)
        {
            int coercedValue = (int)baseValue;

            if ((int)baseValue > 120)
                coercedValue = 120;

            if ((int)baseValue < 1)
                coercedValue = 1;

            return coercedValue;
        }

#122 – Validating a Dependency Property

A class that implements a dependency property can optionally provide a validation callback, which it specifies when registering the property.  A validation callback is called when a property is about to be set to a new value and returns true or false, indicating whether the new value is valid.

You specify a validation callback when registering a dependency property.

            AgeProperty =
                DependencyProperty.Register(
                    "Age",                 // Property's name
                    typeof(int),           // Property's type
                    typeof(Person),        // Defining class' type
                    ageMetadata,           // Defines default value & changed/coercion callbacks  (optional)
                    new ValidateValueCallback(OnAgeValidateValue));   // *** validation (optional)

The validation callback has the new value passed in.

        private static bool OnAgeValidateValue (object value)
        {
            int age = (int) value;

            // Only allow reasonable ages
            return (age > 0) && (age < 120);
        }

If the property is being set to an invalid value, an exception is thrown.

            Person p = new Person("Samuel", "Clemens");
            p.Age = 40;     // ok
            p.Age = 300;    // throws System.ArgumentException