#1,174 – Custom Panel, part VI (Attached Properties)

You can define an attached dependency property in a custom panel.  The attached property can be used by child elements of the panel and in a way that affects how the child elements are laid out.  In the example below, we define a boolean SecondColumn property.  If set, this property indicates that a child element should appear in a second column.

    public class TwoColPanel : Panel
    {
        private static FrameworkPropertyMetadata secColMetadata =
            new FrameworkPropertyMetadata(false,
                FrameworkPropertyMetadataOptions.AffectsParentArrange);

        public static readonly DependencyProperty SecondColumnProperty =
            DependencyProperty.RegisterAttached("SecondColumn", typeof(bool),
                typeof(TwoColPanel), secColMetadata);

        public static void SetSecondColumn(DependencyObject depObj, bool value)
        {
            depObj.SetValue(SecondColumnProperty, value);
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            foreach (UIElement elem in InternalChildren)
                elem.Measure(availableSize);

            return availableSize;
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            double topCol1 = 0.0;
            double topCol2 = 0.0;

            for (int i = 0; i < InternalChildren.Count; i++)
            {
                bool col2 = (bool)InternalChildren[i].GetValue(SecondColumnProperty);

                double left = col2 ? (finalSize.Width / 2.0) : 0.0;
                double top = col2 ? topCol2 : topCol1;

                Rect r = new Rect(new Point(left, top),
                                  InternalChildren[i].DesiredSize);

                InternalChildren[i].Arrange(r);

                if (col2)
                    topCol2 += InternalChildren[i].DesiredSize.Height;
                else
                    topCol1 += InternalChildren[i].DesiredSize.Height;
            }

            return finalSize;
        }
    }

We use the attached property as follows:

    <loc:TwoColPanel Margin="5">
        <Label Content="I'm child #1" loc:TwoColPanel.SecondColumn="True"
               Background="Thistle" />
        <Label Content="I'm child #2" loc:TwoColPanel.SecondColumn="False"
               Background="Lavender" />
        <Label Content="Third kid"
               Background="Honeydew" />
    </loc:TwoColPanel>

1174-001

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

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

#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

#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