In this post I’m going to explore Perspex’s version of the
XAML frameworks’ DependencyProperty
- PerspexProperty
.
Perspex Properties
PerspexProperty is the equivalent of WPF’s DependencyProperty. Dependency/Perspex properties give
you a number of important features over simple .NET properties with INotifyPropertyChanged
:
- Property value inheritance. If the
FontFamily
property is not set on a control it will use theFontFamily
of its parent, which if it’s not set will use the value of it’s parent and so on. In this way, settingFontFamily
on the top-levelWindow
can affect all of the controls contained in that window. - Attached Properties. Attached properties can be defined which can add arbitrary properties to
controls. The control itself doesn’t need to know what to do with an attached property - the
behaviour can be handled elsewhere. An example of an attached property is
Grid.Column
. - Default values. The value for each property applicable to a control doesn’t need to be stored unless it differs from the default value.
- Coercion. Perspex properties can also register a callback to handle coercion.
- Binding. The value of a
PerspexProperty
can be bound to the result of anIObservable
. - Binding Priorities. Bindings may have a priority, so that values that come from the styling system can be overridden by values that are set locally.
Declaring a Perspex Property
Declaring a DependencyProperty
in WPF looks something like this:
A lot of boilerplate there. With generics and default parameters we can make it look a bit nicer:
What can we see here?
- PerspexProperties are typed, so no more having to cast in the getter.
- We pass the property type and owner class as a generic type to
Register()
so we don’t have to writetypeof()
twice. - We used default parameter values in
Register()
so that defaults don’t have to be restated.
Like DependencyProperty
s in XAML, perspex properties need to be defined on a class that inherits
from the PerspexObject
class.
Adding a Perspex Property to Another Type
Just like in WPF, you can share perspex properties between unrelated controls by calling `PerspexProperty.AddOwner’:
Property Value Inheritance
Here we declare an inherited integer property called “Foo”:
By specifying the inherits: true
parameter in the call to PerspexProperty.Register
, we are
saying that in the absence of an explicitly set value for “Foo” on a control, the value from the
parent control should be used.
Note, PerspexObject
is defined at a lower level than the concept of “parent controls” so
PerspexObject
uses the protected InheritanceParent
property to determine the parent control.
This is automatically set by Visual
(which inherits from PerspexObject
) so you shouldn’t usually
have to worry about it.
Attached Properties
Attached properties are essentially the same as attached dependency properties in WPF. They are
defined by calling PerspexProperty.RegisterAttached
:
Default Values
Default values are provided in the call to PerspexProperty.Register
or RegisterAttached
. If no
default value is provided the default is taken to be default(TValue)
.
Just like in WPF, you can override the default value for a specific type:
Coercion
Coercion allows a control to react to changes in a property’s value and make sure that the value is within a valid range. For example a “Percentage” property may only allow values between 0 and 100:
The coercion method must be static, so the object on which the property change has taken place is
passed as a parameter. It’s important to note that you should not assume that the type of the object
is the same as the type that registered the property as properties can be added to other classes
using PerspexProperty.AddOwner
. If you need to access the object on which the property change has
taken place you should check the type first.
Currently coercion cannot be overridden for other classes, this is a limitation that may need to be lifted in future.
Binding
Binding in Perspex uses Reactive Extensions’ IObservable. To bind an IObservable to a property, use the Bind()
method:
Note that because PerspexProperty is typed, we can check that the observable is of the correct type.
To get the value of a property as an observable, call GetObservable()
:
Binding Priorities
A binding may also have a priority. The BindingPriority
enumeration gives a set of common
priorities:
As you can see, lower integral values are considered to be of a higher priority. We’ll explore how priorites work in another post.
Binding in Initialization Lists
One of the goals of perspex was to make defining a UI in code almost as painless as using markup. To these ends, you can use initalization lists everywhere to give a XAML-like feel to your control declarations in C#:
This works fine for perspex properties that are exposed as standard .NET properties, but how can we make this work for attached properties and bindings? Well, C# 6 introduces a new feature called index initializers which can allow us to do this:
Nice… Now lets suppose we want to bind a property:
Yep, by putting a bang in front of the property name you can bind to a property (attached or otherwise) from the object initializer.
Binding to a property on another control? Easy:
Two way binding? Just add two bangs:
If you’re writing a control template however, you don’t want to bind at the LocalValue binding
priority, you want to bind using the TemplatedParent
binding priority. To do this use the tilde
operator instead. Here’s an example from ScrollViewer
’s control template:
As before, doubling up the tilde operator creates a two-way binding.