다음을 통해 공유


Custom Dependency Objects and Dependency Properties

Microsoft Silverlight will reach end of support after October 2021. Learn more.

This topic describes the reasons that application developers and component authors for Silverlight might want to create custom dependency properties. This topic describes the implementation steps for a dependency property, as well as some implementation options that can improve performance, usability, or versatility of the property.

This topic contains the following sections.

  • Prerequisites
  • What Is a Dependency Property?
  • Differences Between the Silverlight and WPF Property Systems
  • The Implementation Pattern of a Dependency Property
  • When Should You Implement a Dependency Property?
  • Checklist for Defining a Dependency Property
  • Property Metadata for a Custom Dependency Property
  • More Information About Custom Dependency Properties
  • Related Topics

Prerequisites

This topic assumes that you understand dependency properties from the perspective of a consumer of existing dependency properties on Silverlight classes, and have read Dependency Properties Overview. In order to follow the examples in this topic, you should also understand XAML and know how to write basic applications for Silverlight.

What Is a Dependency Property?

Dependency properties are properties that are registered with the Silverlight property system by calling the DependencyProperty.Register method, and that are identified by a DependencyProperty identifier field. You can enable what would otherwise be a CLR property to support styling, data binding, animations, and default values by implementing it as a dependency property. Dependency properties can be used only by DependencyObject types, but DependencyObject is quite high in the Silverlight class hierarchy, so the majority of classes available in Silverlight that are intended for UI and presentation can support dependency properties. For more information about dependency properties and some of the terminology and conventions used for describing them in this documentation, see Dependency Properties Overview.

Differences Between the Silverlight and WPF Property Systems

Windows Presentation Foundation (WPF) is the technology that first defined the dependency property concept. Silverlight is in many ways a subset of WPF. This subset principle also applies to the respective property systems. The following is a list of property system capabilities that exist in WPF but do not exist in Silverlight 5:

  • Coerce-value callbacks and DependencyObject.CoerceValue. However, you can typically implement most of your needs in this area by using property-changed callbacks, which are supported in Silverlight.

  • Custom read-only dependency properties. Silverlight does not implement DependencyPropertyKey or a SetValue pattern that uses the key. Without this key technique, there is no API available that can prevent external code from setting the dependency property through SetValue and violating your read-only intention for the property.

  • Per-type property metadata, as established with OverrideMetadata. The property metadata used for a dependency property in Silverlight is always the metadata as it is initially registered for its owner type. This consideration applies to default values and to property-changed callbacks, which are the two supported information sets in Silverlight property metadata.

  • Property metadata subclasses that are defined by the framework, such as UIPropertyMetadata or FrameworkPropertyMetadata. Only PropertyMetadata exists in Silverlight, although conceivably you could derive from it. The WPF FrameworkPropertyMetadata provides a "flags" mechanism for information passing conventions, but Silverlight 5 does not support this.

  • The AddOwner mechanism whereby an existing dependency property can be reregistered to a different owner type.

  • The full set of settable properties on PropertyMetadata. Silverlight supports a GetMetadata method and a readable DefaultValue from PropertyMetadata as of Silverlight 3, but does not permit changing the metadata or retrieving the PropertyChangedCallback from metadata. Also absent in Silverlight are retrieval and analysis APIs for the property system such as the DependencyObjectType class. 

  • The DependencyPropertyHelper utility and DependencyObject.GetLocalValueEnumerator.

  • The Freezable and FreezableCollection classes. These are not strictly dependency property considerations, but these concepts/classes participate in the overall WPF property system and apply to certain scenarios such as custom animation types.

The Implementation Pattern of a Dependency Property

Examples of Silverlight dependency properties are: Control.Background, FrameworkElement.Width, and TextBox.Text, among many others. Each dependency property exposed by a class has a corresponding public static readonly field of type DependencyProperty exposed on that same class. That field is the identifier for the dependency property. The identifier field is named using a convention: the name of the dependency property with the string Property appended to it. For example, the corresponding DependencyProperty identifier field for the Control.Background property is Control.BackgroundProperty. The identifier stores the information about the dependency property as it was registered, and the identifier can then be used later for other operations involving the dependency property, such as calling SetValue.

As mentioned in Dependency Properties Overview, all dependency properties in Silverlight (except most attached properties) are also CLR properties because of the wrapper implementation. Therefore, from code, you can get or set dependency properties by calling the CLR accessors that serve as the wrappers in the same manner that you would use other CLR properties. As a consumer of established dependency properties, you do not typically use the DependencyObject methods GetValue and SetValue, which are the connection point to the underlying property system. Rather, the existing implementation of the Silverlight dependency properties will have already called GetValue and SetValue within the get and set accessors of the property, using the identifier field appropriately. If you implement a custom dependency property yourself, then you define the accessors for the CLR type system in a similar way.

In addition to providing ease of access to callers, the property wrappers are also useful for reporting the basic information about dependency properties to reflection or to static analysis. The property definition of the property wrapper is also where you place XAML-related CLR attributes such as ContentPropertyAttribute or TypeConverterAttribute; this is where that attribution is expected to be when custom dependency properties are consumed by the Silverlight XAML parser.

When Should You Implement a Dependency Property?

When you implement a read/write property on a class, so long as your class derives from DependencyObject, you have the option to back your property with a DependencyProperty identifier and thus to make it a dependency property. Making your Silverlight property a dependency property is not always necessary or appropriate and will depend on your needs. Sometimes, the typical technique of backing your property with a private CLR field is adequate. However, you should implement your property as a dependency property whenever you want the property to support one or more of the following Silverlight property system capabilities:

  • You want the property to be settable in a style. For more information, see Customizing the Appearance of an Existing Control by Using a ControlTemplate.

  • You want the property to be a property target that supports data binding. For more information about data binding dependency properties, see Data Binding.

  • You want the property to support an animated value, using the Silverlight animation system. For more information, see Animation Overview.

  • You want the Silverlight property system to report when the previous value of the property has been changed by actions taken by the property system itself, the environment, or the user, or by reading and using styles. Your property can specify a callback method that will be invoked each time the property system determines that your property value was definitively changed.

Checklist for Defining a Dependency Property

Defining a dependency property consists of four distinct concepts. These concepts are not necessarily strict procedural steps, because some of these are combined as single lines of code in the implementation:

  • (Optional) Create property metadata for the dependency property. You need property metadata only if you want property-changed behavior or a metadata-based default value that can be restored by calling ClearValue.

  • Register the property name with the property system, specifying an owner type and the type of the property value. Also specify the property metadata, if used, or null if you do not require property metadata.

  • Define a DependencyProperty identifier as a public static readonly field on the owner type.

  • Define a CLR wrapper property whose name matches the name of the dependency property field, minus the Property suffix, and also matches the name parameter from the Register(String, Type, Type, PropertyMetadata) call. Implement the CLR get and set accessors to connect the wrapper that provides the CLR exposure with the dependency property that backs it. (If you are defining an attached property, you generally omit the wrapper and instead write a different style of accessor that a XAML processor can use. For more information, see Attached Properties Overview.)

  • (Optional) Place any XAML-related CLR attributes such as ContentPropertyAttribute or TypeConverterAttribute on the property definition (the outer definition, not the get-set implementations).

Registering the Property with the Property System

In order for your property to be a dependency property, you must register the property into a property store maintained by the Silverlight property system, and you must give it a unique identifier to be used as the qualifier for later property system operations. These operations might be internal operations, or your own code calling property system APIs. To register the property, you call the DependencyProperty.Register method within the body of your class (inside the class, but outside any member definitions). The identifier field is also provided by the DependencyProperty.Register method call, as the return value. The DependencyProperty.Register call is typically done outside of other member definitions because you use the return value to assign and create a public static readonly field of type DependencyProperty as part of your class. This field becomes the identifier for your dependency property.

Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty = DependencyProperty.Register("AquariumGraphic", GetType(Uri), GetType(AquariumObject), New PropertyMetadata(Nothing, New PropertyChangedCallback(AddressOf OnUriChanged)))
public static readonly DependencyProperty AquariumGraphicProperty = DependencyProperty.Register(
  "AquariumGraphic",
  typeof(Uri),
  typeof(AquariumObject),
  new PropertyMetadata(null,
      new PropertyChangedCallback(OnUriChanged)
  )
);

Dependency Property Name Conventions

There are established naming conventions regarding dependency properties that you should follow in all but exceptional circumstances.

The dependency property itself has a basic name (AquariumGraphic in the preceding example) that is given as the first parameter of DependencyProperty.Register. The name must be unique within each registering type, and the uniqueness scope includes any inherited members. Dependency properties inherited through base types are considered to be already part of the registering type; names of inherited properties cannot be registered again.

Caution noteCaution:

Although the name you provide here can be any identifier that is accepted in CLR programming for a CLR field entity, you should anticipate that your dependency property might also need to be settable in XAML. In order to be settable in XAML, the name you choose must be a valid XAML name. For more information, see XamlName Grammar.

When you create the identifier field, combine the name of the property as you registered it with the suffix Property (AquariumGraphicProperty, for example). This field is your identifier for the dependency property, and it is used as an input for the SetValue and GetValue calls you make in your own CLR wrappers, by the property system, and potentially by XAML processors.

NoteNote:

Defining the dependency property in the class body is the typical implementation, but it is also possible to define a dependency property in the class static constructor. This approach might make sense if you need more than one line of code to initialize the dependency property.

Implementing the Wrapper

Your wrapper implementation should call GetValue in the get implementation, and SetValue in the set implementation (the original registration call and field are shown in the code example for clarity).

In all but exceptional circumstances, your wrapper implementations should perform only the GetValue and SetValue operations, respectively. The reason for this is discussed in XAML Loading and Dependency Properties, later in this topic.


Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty = DependencyProperty.Register("AquariumGraphic", GetType(Uri), GetType(AquariumObject), New PropertyMetadata(Nothing, New PropertyChangedCallback(AddressOf OnUriChanged)))

Public Property AquariumGraphic() As Uri 
    Get 
        Return DirectCast(GetValue(AquariumGraphicProperty), Uri) 
    End Get 
    Set(ByVal value As Uri) 
        SetValue(AquariumGraphicProperty, value) 
    End Set 
End Property 

public static readonly DependencyProperty AquariumGraphicProperty = DependencyProperty.Register(
  "AquariumGraphic",
  typeof(Uri),
  typeof(AquariumObject),
  new PropertyMetadata(null,
      new PropertyChangedCallback(OnUriChanged)
  )
);
public Uri AquariumGraphic
{
    get { return (Uri)GetValue(AquariumGraphicProperty); }
    set { SetValue(AquariumGraphicProperty, value); }
}

Again, by convention, the name of the wrapper property should be the same as the name chosen and given as first parameter of the DependencyProperty.Register call that registered the property. If your property does not follow the convention, this does not necessarily disable all possible uses, but you will encounter several notable issues:

  • Certain aspects of styles and templates might not work.

  • Most tools and designers must rely on the naming conventions to provide designer environment assistance at a per-property level.

  • Potentially, a XAML processor bypasses the wrappers entirely, and relies on the naming convention when processing attribute values. See the next section.

XAML Loading and Dependency Properties

The Silverlight 5 implementation of the XAML processor is inherently aware of dependency properties. The Silverlight 5 XAML processor uses property system methods for dependency properties when loading XAML content and processing attributes that are dependency properties, if they are set using a style Setter. This operation bypasses the CLR wrappers. When you implement custom dependency properties, you must account for this behavior and should avoid placing any other code in your property wrapper other than the property system methods GetValue and SetValue.

Similarly, other aspects of the XAML processor that obtain property values from XAML processing also potentially use GetValue rather than using the wrapper. Therefore, you should also avoid any additional implementation in the get definition beyond the GetValue call.

Property Metadata for a Custom Dependency Property

When property metadata is assigned to a dependency property, the same metadata is applied to all cases of the property on all object instances. In property metadata, you can specify two behaviors of the property:

  • A default value that the property system assigns to all cases of the property.

  • A static callback method that is invoked within the property system whenever the property value is changed.

Default Value

If it is not specified, the default value for a dependency property is null for a reference type, or the default of the type for a value type. The main reason that you might want to establish a default value is that this value is restored if you ever call ClearValue on the property. Establishing a default value on a per-property basis might be more convenient than the pattern of establishing default values in constructors, particularly for value types. However, for reference types, you should make sure that establishing a default value does not create an unintentional singleton pattern. You can use class constructors to set initial values for a reference type dependency property if you want a non-null value, but be aware that this would be considered a local value for purposes of dependency property precedence. It might be more appropriate to use a template for this purpose, if your class is templatable. Another way to avoid a singleton pattern but still provide a useful default is to expose a static property on the reference type that provides a suitable default for the values of that class.

Important noteImportant Note:

Do not register a dependency property with the specific default value of UnsetValue. This will be confusing for property consumers and will have unintended consequences within the property system.

Property-Changed Callback

A property-changed callback is relevant if your dependency property has interactions with other dependency properties, or if it is used to set an internal property or state of your object. You do not need to compare OldValue and NewValue to determine whether there was a change; if the callback is invoked, then the property system has already determined that there is an effective property value change. Because the method is static, the d parameter of the callback is important because it informs you which instance of the class has reported a change.

A typical implementation uses the NewValue of the event-data class and processes that value in some manner, usually by performing some other change on the object passed as d. Additional possible scenarios are to reject an attempted NewValue set, and to either restore OldValue or set the value to some known constraint that makes sense based on the attempted NewValue.

Example PropertyChangedCallback Implementation

The following example implements the OnUriChanged callback as registered in the Register example shown earlier. In this case, the implementation uses the changed Uri value in order to rebuild one of its internal properties, which constructs a BitmapImage based on a Uri.

    Private Shared Sub OnUriChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs) 
        Dim sh As Shape = DirectCast(d, Shape) 
        Dim ib As New ImageBrush() 
        ib.ImageSource = New BitmapImage(TryCast(e.NewValue, Uri)) 
        sh.Fill = ib 
    End Sub 
End Class 
private static void OnUriChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    Shape sh = (Shape)d;
    ImageBrush ib = new ImageBrush();
    ib.ImageSource = new BitmapImage(e.NewValue as Uri);
    sh.Fill = ib;
}

More Information About Custom Dependency Properties

Collection-Type Dependency Properties

Collection-type dependency properties have some additional implementation issues to consider. For details, see Collection-Type Dependency Properties.

Dependency Property Security Considerations

Dependency properties should be declared as public properties. Dependency property identifier fields should be declared as publicstaticreadonly fields. Even if you attempt to declare other access levels (such as protected), a dependency property can always be accessed through the identifier in combination with the property system APIs. For more information, see Dependency Property Security.

Dependency Properties and Class Constructors

There is a general principle in managed code programming (often enforced by code analysis tools such as FxCop) that class constructors should not call virtual methods. This is because constructors can be called as base initialization of a derived class constructor, and entering the virtual method through the constructor might occur at an incomplete initialization state of the object instance being constructed. When you derive from any class that already derives from DependencyObject, you should be aware that the property system itself calls and exposes virtual methods internally as part of the Silverlight property system services. To avoid potential problems with run-time initialization, you should not set dependency property values within constructors of classes, unless you follow a very specific constructor pattern. For details, see Safe Constructor Patterns for Dependency Objects.