-
Notifications
You must be signed in to change notification settings - Fork 47
UPF Dependency Properties
Dependency properties are a concept in the Ultraviolet Presentation Foundation which supplement C#'s built-in properties. A dependency property's value depends (hence its name) on various external factors, rather than being provided by a backing field on the type that defines the property.
To create a new dependency property, it must be registered with the dependency property system using the static Register()
method on the DependencyProperty
class. This method returns a new DependencyProperty
instance which serves to uniquely identify the dependency property.
public class MyElement : FrameworkElement
{
public static readonly DependencyProperty FooProperty = DependencyProperty.Register("Foo", typeof(Int32), typeof(MyElement),
new PropertyMetadata<Int32>(123, PropertyMetadataOptions.AffectsMeasure, HandleFooChanged));
}
There's a lot to unpack here:
- The first argument is the name of the dependency property. In C# 6 and beyond, it is probably a good idea to use
nameof(Foo)
here, whereFoo
is a standard property which exposes the dependency property (see below). - The
typeof(Int32)
argument specifies the dependency property's type. - The
typeof(MyElement)
argument specifies the dependency property's owner type. This is the type which is declaring the property, in this case a custom element type calledMyElement
. - The
PropertyMetadata<T>
object is used to specify the dependency property metadata, which consists of the following elements:- The property's default value, in this case
123
; if set tonull
, thendefault(T)
will be used, whereT
is the dependency property's type. - The property's metadata options. In this case, we're specifying that changing the value of
Foo
on an element will invalidate that element's measurement state. - The property's change handler, which is called when the property value is changed for a particular element.
- The property's value coercion callback, not shown in this example. This is a delegate which can be used to coerce the dependency property's value into a particular state; for example, the
RangeBase
element uses this technique to force itsValue
property to remain within the range specified by itsMinimum
andMaximum
properties.
- The property's default value, in this case
Once you have an instance of the DependencyProperty
class, you can use that identifier to expose the dependency property on a particular object by declaring a traditional property:
public Int32 Foo
{
get { return GetValue<Int32>(FooProperty); }
set { SetValue<Int32>(FooProperty, value); }
}
Dependency properties can only be set on dependency objects; these are anything derived from the DependencyObject
abstract class, which includes all of UPF's interface elements. This class defines the API for interacting with an object's dependency property values, including the GetValue<T>()
and SetValue<T>()
methods.
Dependency properties can be referenced in UVML using the name specified in the call to Register()
.
<MyElement Foo="999"/>
When styling a dependency property via UVSS, it is important to note that it must be referenced by its styling name. Unless a styling name is explicitly defined through the call to Register()
, it is automatically generated from the property's real name; a property named "FooBarBaz"
will have an automatically generated styling name of foo-bar-baz
.
A dependency property can have multiple owner types. To add an owner type to an existing dependency property, use the AddOwner()
method:
public class AnotherElement : FrameworkElement
{
public static readonly DependencyProperty FooProperty = MyElement.FooProperty.AddOwner(typeof(AnotherElement),
new PropertyMetadata<Int32>(456, PropertyMetadataOptions.AffectsArrage, HandleFooChanged));
}
This will add the AnotherElement
type to the list of owner types for FooProperty
.
Each owner type can specify its own property metadata; here, we've overridden the default value and specified a new change handler. New property metadata is merged with inherited metadata where it makes sense to do so, and otherwise overridden. Note that metadata is "inherited" first from any types which are ancestors of the new owner type, and finally from the dependency property's "true owner," i.e. the type which originally declared it.
- If a default value is specified, it overrides any existing default value; otherwise it is inherited.
- The property's options are merged with inherited options.
- The property's change handler, if specified, is combined with inherited handlers.
- The property's coercion callback overrides any existing coercion callbacks.
An attached property is a special type of dependency property which can be set on a type other than one of its owner types. As an example, the Grid
layout container defines Row
and Column
properties which are intended to be set not on the grid itself, but on the grid's children, which can be any arbitrary type of interface element.
To define an attached property, use the RegisterAttached()
method on DependencyProperty
.
public class MyElement : FrameworkElement
{
public static readonly DependencyProperty BarProperty = DependencyProperty.RegisterAttached("Bar", typeof(Int32), typeof(MyElement),
new PropertyMetadata<Int32>(789, PropertyMetadata.AffectsMeasure));
}
The parameters of RegisterAttached()
are identical to those used by Register()
.
Attached properties are usually exposed through static methods on the declaring type, rather than properties on the target type. These methods take the target object as their first parameters.
public class MyElement : FrameworkElement
{
public static void SetBar(DependencyObject element, Int32 value)
{
element.SetValue<Int32>(BarProperty, value);
}
public static Int32 GetBar(DependencyObject element)
{
return element.GetValue<Int32>(BarProperty);
}
}
In UVML, an attached property is referenced by prepending the name of the owner type to the property name.
<Button MyElement.Bar="999"/>
This works similarly in UVSS, except that the property's styling name should be used instead:
button
{
myelement.bar { 999 }
}
A dependency property can be declared read-only using the RegisterReadOnly()
method. This is useful for creating dependency properties which should not be modifiable by the "outside world," such as those which directly represent the internal state of an element.
public class MyElement : FrameworkElement
{
private static readonly DependencyPropertyKey QuxPropertyKey = DependencyProperty.RegisterReadOnly("Qux", typeof(Int32), typeof(MyElement),
new PropertyMetadata<Int32>(0));
public static readonly DependencyProperty QuxProperty = QuxPropertyKey.DependencyProperty;
public static Int32 Qux
{
get { return GetValue<Int32>(QuxProperty); }
private set { SetValue<Int32>(QuxPropertyKey, value); }
}
}
Note that this returns an instance of the DependencyPropertyKey
class, rather than the DependencyProperty
class, and that this object is declared private
. The declaring class can still use this key value to set the dependency property value using SetValue<T>()
, but attempting to set the value in any other way—such as through UVML or UVSS—will result in an exception being thrown.
As noted in the introduction, dependency properties can draw their values from a number of different sources. When multiple value sources are applicable, they have the following priority, from highest to lowest:
- Animations
- Data bindings
- Local values (i.e. those set directly using the
SetValue<T>()
method) - Triggers
- Styles
- Default values
The Ultraviolet Presentation Foundation uses a digest cycle to track changes to dependency properties which are animated or data bound. Every frame, each such dependency property is digested—its current value is compared to its value in the previous frame, and if that value has changed, the appropriate change handlers are invoked.
As an optimization, dependency properties which have a simple binding to another dependency property do not participate in the digest cycle. A simple binding is defined as any binding which directly references a dependency property without needing to dereference any other objects. Thus, the binding expression {{Foo}}
is a simple binding, but {{A.B.C.Foo}}
is not.
- Contributing
- Dependencies
- Basic Concepts
- First Look- Platform
- First Look- Graphics
- First Look- Audio
- First Look- Input
- First Look- Content
- First Look- UI
- sRGB Color
- Customizing SpriteBatch
- Creating Fonts
- Creating Effects
- Creating Glyph Shaders
- FreeType2 Fonts
- Rendering 3D Models