Open sandboxFocusImprove this doc

WPF Dependency Property

A dependency property in WPF is a kind of property that can be set from the WPF markup language and bound to a property of another object (such as a View-Model object) using a Binding element. Unlike C# properties, dependency properties must be programmatically registered using DependencyProperty.Register. To expose a dependency property as a C# property, one typically writes boilerplate code as demonstrated in the following example:

class MyClass
{
    public static readonly DependencyProperty IsEnabledProperty =
        DependencyProperty.Register( nameof(IsEnabled), typeof(bool), typeof(MyClass));

    public bool IsEnabled
    {
        get { return (bool)GetValue(IsEnabledProperty); }
        set { SetValue(IsEnabledProperty, value); }
    }
}

Instead of writing this boilerplate, you can simply add the [DependencyProperty] aspect to a C# automatic property to convert it into a WPF dependency property:

class MyClass
{
    [DependencyProperty]
    public bool IsEnabled { get; set; }
}

The [DependencyProperty] aspect implements the following features and benefits:

  • Zero boilerplate.
  • Integration with Metalama.Patterns.Contracts to validate dependency properties using aspects like [NotNull] or [Url]. See Metalama.Patterns.Contracts for details.
  • Support for custom pre- and post-assignment callbacks.
  • Detection of mutable or read-only dependency properties based on property accessor accessibility.
  • Handling of default values.

Creating a dependency property

To create a dependency property using the [DependencyProperty] aspect, follow these steps:

  1. Add a reference to the Metalama.Patterns.Wpf package to your project.
  2. Open a class derived from DependencyObject, such as a window or a user control.
  3. Add an automatic property to this class.
  4. Add the [DependencyProperty] custom attribute to this automatic property.
  5. Optionally, add any contract from the Metalama.Patterns.Contracts details to the automatic property. See Metalama.Patterns.Contracts for details about contracts.

Example: a simple dependency property

The following example demonstrates the code generation pattern for a standard property.

Source Code
1using Metalama.Patterns.Wpf;
2using System.Windows.Controls;
3

4namespace Doc.DependencyProperties.Simple;
5
6internal class MyControl : UserControl
7{
8    [DependencyProperty]
9    public double BorderWidth { get; set; }
10}
Transformed Code
1using Metalama.Patterns.Wpf;
2using System.Windows;
3using System.Windows.Controls;
4
5namespace Doc.DependencyProperties.Simple;
6
7internal class MyControl : UserControl
8{
9    [DependencyProperty]
10    public double BorderWidth
11    {
12        get
13        {
14            return (double)GetValue(BorderWidthProperty);
15        }
16
17        set
18        {
19            this.SetValue(BorderWidthProperty, value);
20        }
21    }
22
23    public static readonly DependencyProperty BorderWidthProperty;
24
25    static MyControl()
26    {
27        BorderWidthProperty = DependencyProperty.Register("BorderWidth", typeof(double), typeof(MyControl));
28    }
29}

Example: a read-only dependency property

In the following example, the automatic property has a private setter. The [DependencyProperty] aspect generates a read-only dependency property.

Source Code
1using Metalama.Patterns.Wpf;
2using System.Windows.Controls;
3

4namespace Doc.DependencyProperties.ReadOnly;
5
6internal class MyControl : UserControl
7{
8    [DependencyProperty]
9    public double BorderWidth { get; private set; }
10}
Transformed Code
1using Metalama.Patterns.Wpf;
2using System.Windows;
3using System.Windows.Controls;
4
5namespace Doc.DependencyProperties.ReadOnly;
6
7internal class MyControl : UserControl
8{
9    [DependencyProperty]
10    public double BorderWidth
11    {
12        get
13        {
14            return (double)GetValue(BorderWidthProperty);
15        }
16
17        private set
18        {
19            this.SetValue(BorderWidthPropertyKey, value);
20        }
21    }
22
23    public static readonly DependencyProperty BorderWidthProperty;
24    private static readonly DependencyPropertyKey BorderWidthPropertyKey;
25
26    static MyControl()
27    {
28        BorderWidthPropertyKey = DependencyProperty.RegisterReadOnly("BorderWidth", typeof(double), typeof(MyControl), null);
29        BorderWidthProperty = BorderWidthPropertyKey.DependencyProperty;
30    }
31}

Adding validation through a contract

The most straightforward way to add validation to a contract is to add an aspect of the Metalama.Patterns.Contracts package.

Example: a dependency property with contracts

In the following example, a [Positive] contract is added to the automatic property. You can see how the [DependencyProperty] aspect generates code to enforce this precondition.

Source Code
1using Metalama.Patterns.Contracts;
2using Metalama.Patterns.Wpf;
3using System.Windows.Controls;
4


5namespace Doc.DependencyProperties.Contract;
6
7internal class MyControl : UserControl
8{
9    [DependencyProperty]
10    [NonNegative]
11    public double BorderWidth { get; set; }
12}
Transformed Code
1using Metalama.Patterns.Contracts;
2using Metalama.Patterns.Wpf;
3using System;
4using System.Windows;
5using System.Windows.Controls;
6
7namespace Doc.DependencyProperties.Contract;
8
9internal class MyControl : UserControl
10{
11    [DependencyProperty]
12    [NonNegative]
13    public double BorderWidth
14    {
15        get
16        {
17            return (double)GetValue(BorderWidthProperty);
18        }
19
20        set
21        {
22            this.SetValue(BorderWidthProperty, value);
23        }
24    }
25
26    public static readonly DependencyProperty BorderWidthProperty;
27
28    static MyControl()
29    {
30        BorderWidthProperty = DependencyProperty.Register("BorderWidth", typeof(double), typeof(MyControl), new PropertyMetadata()
31        {
32            CoerceValueCallback = (d, value) =>
33            {
34                value = ApplyBorderWidthContracts((double)value);
35                return value;
36            }
37        });
38    }
39
40    private static double ApplyBorderWidthContracts(double value)
41    {
42        if (value < 0)
43        {
44            throw new ArgumentOutOfRangeException("value", value, "The 'value' parameter must be greater than or equal to 0.");
45        }
46
47        return value;
48    }
49}

Adding validation through a callback method

The second way to add validation to a dependency property is by adding a callback method to your code. For a property named Foo, the validation method must be named ValidateFoo and have one of the following signatures:

  • static void ValidateFoo(TPropertyType value)
  • static void ValidateFoo(DependencyProperty property, TPropertyType value)
  • static void ValidateFoo(TDeclaringType instance, TPropertyType value)
  • static void ValidateFoo(DependencyProperty property, TDeclaringType instance, TPropertyType value)
  • void ValidateFoo(TPropertyType value)
  • void ValidateFoo(DependencyProperty property, TPropertyType value)

where TDeclaringType is the declaring type of the target property, DependencyObject, or object, and where TPropertyType is any type assignable from the actual type of the target property. TPropertyType can also be a generic type parameter, in which case the method must have exactly one generic parameter.

If you prefer specifying the validation method explicitly instead of relying on a naming convention, you can do it using the DependencyPropertyAttribute.ValidateMethod property.

These methods must throw an exception in case of invalid value.

Example: validation callback

The following example implements a profanity filter on a dependency filter. If the value contains the word foo, it will throw an exception.

Source Code
1using Metalama.Patterns.Wpf;
2using System;
3using System.Windows.Controls;
4

5namespace Doc.DependencyProperties.Validate;
6
7internal class MyControl : UserControl
8{
9    [DependencyProperty]
10    public string? Title { get; set; }
11
12    private void ValidateTitle( string value )



















13    {
14        if ( value.Contains( "foo", StringComparison.OrdinalIgnoreCase ) )
15        {




16            throw new ArgumentOutOfRangeException( nameof(value) );
17        }
18    }

19}
Transformed Code
1using Metalama.Patterns.Wpf;
2using System;
3using System.Windows;
4using System.Windows.Controls;
5
6namespace Doc.DependencyProperties.Validate;
7
8internal class MyControl : UserControl
9{
10    [DependencyProperty]
11    public string? Title
12    {
13        get
14        {
15            return (string?)GetValue(TitleProperty);
16        }
17
18        set
19        {
20            this.SetValue(TitleProperty, value);
21        }
22    }
23
24    private void ValidateTitle(string value)
25    {
26        if (value.Contains("foo", StringComparison.OrdinalIgnoreCase))
27        {
28            throw new ArgumentOutOfRangeException(nameof(value));
29        }
30    }
31
32    public static readonly DependencyProperty TitleProperty;
33
34    static MyControl()
35    {
36        TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(MyControl), new PropertyMetadata()
37        {
38            CoerceValueCallback = (d, value_1) =>
39            {
40                ((MyControl)d).ValidateTitle((string?)value_1);
41                return value_1;
42            }
43        });
44    }
45}

Handling of default values

When an automatic property is initialized with a value, not from the constructor but from the property declaration itself, this expression is used as the default value of the dependency property. The concept of default value of a property in WPF means that if you attempt to set a property to its default value in a wpf file, the assignment will be grayed out as redundant.

Note

When an automatic property is initialized to a value, this value is also assigned to the property from the instance constructor of the object to mimic the behavior of a C# automatic property. Note that there is a slight difference: in standard automatic properties, the initial value is assigned before the base constructor is executed. However, with a dependency property, the value is assigned after the base constructor is invoked.

The following example demonstrates the code generation pattern when an automatic property is initialized to a value.

Source Code
1using Metalama.Patterns.Wpf;
2using System.Windows.Controls;
3

4namespace Doc.DependencyProperties.DefaultValue;
5
6internal class MyControl : UserControl
7{
8    [DependencyProperty]
9    public double BorderWidth { get; set; } = 5;
10}
Transformed Code
1using Metalama.Patterns.Wpf;
2using System.Windows;
3using System.Windows.Controls;
4
5namespace Doc.DependencyProperties.DefaultValue;
6
7internal class MyControl : UserControl
8{
9    [DependencyProperty]
10    public double BorderWidth
11    {
12        get
13        {
14            return (double)GetValue(BorderWidthProperty);
15        }
16
17        set
18        {
19            this.SetValue(BorderWidthProperty, value);
20        }
21    }
22
23    public static readonly DependencyProperty BorderWidthProperty;
24
25    static MyControl()
26    {
27        BorderWidthProperty = DependencyProperty.Register("BorderWidth", typeof(double), typeof(MyControl), new PropertyMetadata((double)5));
28    }
29
30    public MyControl()
31    {
32        BorderWidth = 5;
33    }
34}

If you don't want the property initial value to be interpreted as the default value of the dependency property, you can disable this behavior by setting the InitializerProvidesDefaultValue property to false. This property is available from the DependencyPropertyAttribute class from the ConfigureDependencyProperty fabric extension method.

Adding a PropertyChanged callback

Whereas the validate method executes before the assignment, you can also add code that executes after the assignment of a dependency property to its new value. For a property named Foo, add a method named OnFooChanged of one of these signatures:

  • static void OnFooChanged()
  • static void OnFooChanged(DependencyProperty property)
  • static void OnFooChanged(TDeclaringType instance)
  • static void OnFooChanged(DependencyProperty property, TDeclaringType instance)
  • void OnFooChanged()
  • void OnFooChanged(DependencyProperty property)
  • void OnFooChanged(TPropertyType value)
  • void OnFooChanged(DependencyProperty oldValue, DependencyProperty newValue)
  • void OnFooChanged<T>(T value)
  • void OnFooChanged<T>(T oldValue, T newValue)

As with the validate method, you can explicitly identify the property-changed method instead of relying on a naming convention thanks to the DependencyPropertyAttribute.PropertyChangedMethod property.

Example: post-assignment callback

In the following example, the OnBorderWidthChanged method is executed after the value of the BorderWidth property has changed.

Source Code
1using Metalama.Patterns.Wpf;
2using System.Windows.Controls;
3

4namespace Doc.DependencyProperties.OnPropertyChanged;
5
6internal class MyControl : UserControl
7{
8    [DependencyProperty]
9    public double BorderWidth { get; set; }
10
11    [DependencyProperty]





12    public double AvailableWidth { get; private set; }






13
14    private void OnBorderWidthChanged()











15    {
16        this.AvailableWidth = this.Width - this.BorderWidth * 2;
17    }
18}
Transformed Code
1using Metalama.Patterns.Wpf;
2using System.Windows;
3using System.Windows.Controls;
4
5namespace Doc.DependencyProperties.OnPropertyChanged;
6
7internal class MyControl : UserControl
8{
9    [DependencyProperty]
10    public double BorderWidth
11    {
12        get
13        {
14            return (double)GetValue(BorderWidthProperty);
15        }
16
17        set
18        {
19            this.SetValue(BorderWidthProperty, value);
20        }
21    }
22
23    [DependencyProperty]
24    public double AvailableWidth
25    {
26        get
27        {
28            return (double)GetValue(AvailableWidthProperty);
29        }
30
31        private set
32        {
33            this.SetValue(AvailableWidthPropertyKey, value);
34        }
35    }
36
37    private void OnBorderWidthChanged()
38    {
39        this.AvailableWidth = this.Width - this.BorderWidth * 2;
40    }
41
42    public static readonly DependencyProperty AvailableWidthProperty;
43    private static readonly DependencyPropertyKey AvailableWidthPropertyKey;
44    public static readonly DependencyProperty BorderWidthProperty;
45
46    static MyControl()
47    {
48        BorderWidthProperty = DependencyProperty.Register("BorderWidth", typeof(double), typeof(MyControl), new PropertyMetadata((d, e) => ((MyControl)d).OnBorderWidthChanged()));
49        AvailableWidthPropertyKey = DependencyProperty.RegisterReadOnly("AvailableWidth", typeof(double), typeof(MyControl), null);
50        AvailableWidthProperty = AvailableWidthPropertyKey.DependencyProperty;
51    }
52}

Customizing naming conventions

All examples above relied on the default naming convention, which is based on the following assumptions:

  • Given a property named Foo:
    • The name of the field containing the DependencyProperty object is FooProperty.
    • The name of the validation method is ValidateFoo.
    • The name of the post-assignment callback is OnFooChanged.

This naming convention can be modified by calling the ConfigureDependencyProperty fabric extension method, then builder.AddNamingConvention, and supplying an instance of the DependencyPropertyNamingConvention class.

If specified, the DependencyPropertyNamingConvention.PropertyNamePattern is a regular expression that matches the name of the WPF dependency property from the name of the C# property. If this property is unspecified, the default matching algorithm is used, i.e., the name of the dependency property equals the name of the C# property. The OnPropertyChangedPattern and ValidatePattern properties are regular expressions that match the validate and property-changed methods. The RegistrationFieldName property represents the name of the field containing the DependencyProperty object. In these expressions, the {PropertyName} substring is replaced by the name of the dependency property returned by PropertyNamePattern.

Naming conventions are evaluated by priority order. The default priority is the one in which the convention has been added. It can be overwritten by supplying a value to the priority parameter.

The default naming convention is evaluated last and cannot be modified.

Example: Czech naming convention

Here is an illustration of a coding convention for the Czech language.

1using Metalama.Framework.Fabrics;
2using Metalama.Patterns.Wpf.Configuration;
3
4namespace Doc.DependencyProperties.NamingConvention;
5
6internal class Fabric : ProjectFabric
7{
8    public override void AmendProject( IProjectAmender amender )
9    {
10        amender.ConfigureDependencyProperty(
11            builder =>
12                builder.AddNamingConvention(
13                    new DependencyPropertyNamingConvention( "czech" )
14                    {
15                        ValidatePattern = "Kontrolovat{PropertyName}"
16                    } ) );
17    }
18}
Source Code
1using System;
2using System.Windows;
3using Metalama.Patterns.Wpf;
4
5namespace Doc.DependencyProperties.NamingConvention;
6
7public class MojeOkno : Window
8{
9    [DependencyProperty]
10    public double ŠířkaRámečku { get; set; }
11
12    // No, víme, skloňovat neumíme.











13    private void KontrolovatŠířkaRámečku( double value )
14    {
15        if ( value < 0 )
16        {
17            throw new ArgumentOutOfRangeException();
18        }
19    }
20}
Transformed Code
1using System;
2using System.Windows;
3using Metalama.Patterns.Wpf;
4
5namespace Doc.DependencyProperties.NamingConvention;
6
7public class MojeOkno : Window
8{
9    [DependencyProperty]
10    public double ŠířkaRámečku
11    {
12        get
13        {
14            return (double)GetValue(ŠířkaRámečkuProperty);
15        }
16
17        set
18        {
19            this.SetValue(ŠířkaRámečkuProperty, value);
20        }
21    }
22
23    // No, víme, skloňovat neumíme.
24    private void KontrolovatŠířkaRámečku(double value)
25    {
26        if (value < 0)
27        {
28            throw new ArgumentOutOfRangeException();
29        }
30    }
31
32    public static readonly DependencyProperty ŠířkaRámečkuProperty;
33
34    static MojeOkno()
35    {
36        ŠířkaRámečkuProperty = DependencyProperty.Register("ŠířkaRámečku", typeof(double), typeof(MojeOkno), new PropertyMetadata()
37        {
38            CoerceValueCallback = (d, value_1) =>
39            {
40                ((MojeOkno)d).KontrolovatŠířkaRámečku((double)value_1);
41                return value_1;
42            }
43        });
44    }
45}