2 changed files with 278 additions and 0 deletions
@ -0,0 +1,178 @@ |
|||
# Defining Properties |
|||
|
|||
If you are creating a control, you will want to define properties on your |
|||
control. The process in Perspex is broadly similar to other XAML languages |
|||
with a few differences - the main one being that Perpsex's equivalent of |
|||
`DependencyProperty` is called `StyledProperty`. |
|||
|
|||
## Registering Styled Properties |
|||
|
|||
A styled property is analogous to a `DependencyProperty` in other XAML |
|||
frameworks. |
|||
|
|||
You register a styled property by calling `PerspexProperty.Register` and |
|||
storing the result in a `static readonly` field. You then create a standard C# |
|||
property to access it. |
|||
|
|||
Here's how the `Border` control defines its `Background` property: |
|||
|
|||
```c# |
|||
public static readonly StyledProperty<Brush> BackgroundProperty = |
|||
PerspexProperty.Register<Border, Brush>(nameof(Background)); |
|||
|
|||
public Brush Background |
|||
{ |
|||
get { return GetValue(BackgroundProperty); } |
|||
set { SetValue(BackgroundProperty, value); } |
|||
} |
|||
``` |
|||
|
|||
The `PerspexProperty.Register` method also accepts a number of other parameters: |
|||
|
|||
- `defaultValue`: This gives the property a default value. Be sure to only pass |
|||
value types and immutable types here as passing a reference type will cause the |
|||
same object to be used on all instances on which the property is registered. |
|||
- `inherits`: Specified that the property's default value should come from |
|||
the parent control. |
|||
- `defaultBindingMode`: The default binding mode for the property. Can be set to |
|||
`OneWay`, `TwoWay`, `OneTime` or `OneWayToSource`. |
|||
- `validate`: A validation/coercion function of type |
|||
`Func<TOwner, TValue, TValue>`. The function accepts the instance of the class |
|||
on which the property is being set and the value and returns the coerced value |
|||
or throws an exception for an invalid value. |
|||
|
|||
## Using a StyledProperty from Another Class |
|||
|
|||
Sometimes the property you want to add to your control already exists on another |
|||
control, `Background` being a good example. To register a property defined on |
|||
another control, you call `StyledProperty.AddOwner`: |
|||
|
|||
|
|||
```c# |
|||
public static readonly StyledProperty<Brush> BackgroundProperty = |
|||
Border.BackgroundProperty.AddOwner<Panel>(); |
|||
|
|||
public Brush Background |
|||
{ |
|||
get { return GetValue(BackgroundProperty); } |
|||
set { SetValue(BackgroundProperty, value); } |
|||
} |
|||
``` |
|||
|
|||
*Note: Unlike WPF, a property must be registered on a class otherwise it cannot |
|||
be set on an object of that class.* |
|||
|
|||
## Attached Properties |
|||
|
|||
Attached properties are defined almost identically to styled properties except |
|||
that they are registered using the `RegisterAttached` method and their accessors |
|||
are defined as static methods. |
|||
|
|||
Here's how `Grid` defines its `Grid.Column` attached property: |
|||
|
|||
```c# |
|||
public static readonly AttachedProperty<int> ColumnProperty = |
|||
PerspexProperty.RegisterAttached<Grid, Control, int>("Column"); |
|||
|
|||
public static int GetColumn(Control element) |
|||
{ |
|||
return element.GetValue(ColumnProperty); |
|||
} |
|||
|
|||
public static void SetColumn(Control element, int value) |
|||
{ |
|||
element.SetValue(ColumnProperty, value); |
|||
} |
|||
``` |
|||
|
|||
## Readonly PerspexProperties |
|||
|
|||
To create a readonly property you use the `PerspexProperty.RegisterDirect` |
|||
method. Here is how `Visual` registers the readonly `Bounds` property: |
|||
|
|||
```c# |
|||
public static readonly DirectProperty<Visual, Rect> BoundsProperty = |
|||
PerspexProperty.RegisterDirect<Visual, Rect>( |
|||
nameof(Bounds), |
|||
o => o.Bounds); |
|||
|
|||
private Rect _bounds; |
|||
|
|||
public Rect Bounds |
|||
{ |
|||
get { return _bounds; } |
|||
private set { SetAndRaise(BoundsProperty, ref _bounds, value); } |
|||
} |
|||
``` |
|||
|
|||
As can be seen, readonly properties are stored as a field on the object. When |
|||
registering the property, a getter is passed which is used to access the |
|||
property value through `GetValue` and then `SetAndRaise` is used to notify |
|||
listeners to changes to the property. |
|||
|
|||
## Direct PerspexProperties |
|||
|
|||
As its name suggests, `RegisterDirect` isn't just used for registering readonly |
|||
properties. You can also pass a *setter* to `RegisterDirect` to expose a |
|||
standard C# property as a Perspex property. |
|||
|
|||
A `StyledProperty` which is registered using `PerspexProperty.Register` |
|||
maintains a prioritized list of values and bindings that allow styles to work. |
|||
However, this is overkill for many properties, such as `ItemsControl.Items` - |
|||
this will never be styled and the overhead involved with styled properties is |
|||
unnecessary. |
|||
|
|||
Here is how `ItemsControl.Items` is registered: |
|||
|
|||
```c# |
|||
public static readonly DirectProperty<ItemsControl, IEnumerable> ItemsProperty = |
|||
PerspexProperty.RegisterDirect<ItemsControl, IEnumerable>( |
|||
nameof(Items), |
|||
o => o.Items, |
|||
(o, v) => o.Items = v); |
|||
|
|||
private IEnumerable _items = new PerspexList<object>(); |
|||
|
|||
public IEnumerable Items |
|||
{ |
|||
get { return _items; } |
|||
set { SetAndRaise(ItemsProperty, ref _items, value); } |
|||
} |
|||
``` |
|||
|
|||
|
|||
Direct properties are a lightweight version of styled properties that support |
|||
the following: |
|||
|
|||
- PerspexObject.GetValue |
|||
- PerspexObject.SetValue for non-readonly properties |
|||
- PropertyChanged |
|||
- Binding (only with LocalValue priority) |
|||
- GetObservable |
|||
- AddOwner |
|||
- Metadata |
|||
|
|||
They don't support the following: |
|||
|
|||
- Validation/Coercion (although this could be done in the property setter) |
|||
- Overriding default values. |
|||
- Inherited values |
|||
|
|||
## When to use a Direct vs a Styled Property |
|||
|
|||
Direct properties have advantages and disadvantages: |
|||
|
|||
Pros: |
|||
- No additional object is allocated per-instance for the property |
|||
- Property getter is a standard C# property getter |
|||
- Property setter is is a standard C# property setter that raises an event. |
|||
|
|||
Cons: |
|||
- Cannot inherit value from parent control |
|||
- Cannot take advantage of Perspex's styling system |
|||
- Property value is a field and as such is allocated whether the property is |
|||
set on the object or not |
|||
|
|||
So use direct properties when you have the following requirements: |
|||
- Property will not need to be styled |
|||
- Property will usually or always have a value |
|||
@ -0,0 +1,100 @@ |
|||
# Working with Properties |
|||
|
|||
Perspex controls expose their properties as standard CLR properties, so for |
|||
reading and writing values there's no surprises: |
|||
|
|||
```c# |
|||
// Create a TextBlock and set its Text property. |
|||
var textBlock = new TextBlock(); |
|||
textBlock.Text = "Hello World!"; |
|||
``` |
|||
|
|||
|
|||
However there's a lot more you can do with properties such as subscribing to |
|||
changes on the property, and binding. |
|||
|
|||
# Subscribing to Changes to a Property |
|||
|
|||
You can subscribe to changes on a property by calling the `GetObservable` |
|||
method. This returns an `IObservable<T>` which can be used to listen for changes |
|||
to the property: |
|||
|
|||
```c# |
|||
var textBlock = new TextBlock(); |
|||
var text = textBlock.GetObservable(TextBlock.TextProperty); |
|||
``` |
|||
|
|||
Each property that can be subscribed to has a static readonly field called |
|||
`[PropertyName]Property` which is passed to `GetObservable` in order to |
|||
subscribe to the property's changes. |
|||
|
|||
`IObservable` (part of Reactive Extensions, or rx for short) is out of scope |
|||
for this guide, but here's an example which uses the returned observable to |
|||
print a message with the changing property values to the console: |
|||
|
|||
```c# |
|||
var textBlock = new TextBlock(); |
|||
var text = textBlock.GetObservable(TextBlock.TextProperty); |
|||
text.Subscribe(value => Console.WriteLine(value + " Changed")); |
|||
``` |
|||
|
|||
When the returned observable is subscribed, it will return the current value |
|||
of the property immediately and then push a new value each time the property |
|||
changes. If you don't want the current value, you can use the rx `Skip` |
|||
operator: |
|||
|
|||
```c# |
|||
var text = textBlock.GetObservable(TextBlock.TextProperty).Skip(1); |
|||
``` |
|||
|
|||
# Binding a property |
|||
|
|||
Observables don't just go one way! You can also use them to bind properties. |
|||
For example here we create two `TextBlock`s and bind the second's `Text` |
|||
property to the first: |
|||
|
|||
```c# |
|||
var textBlock1 = new TextBlock(); |
|||
var textBlock2 = new TextBlock(); |
|||
|
|||
// Get an observable for the first text block's Text property. |
|||
var source = textBlock1.GetObservable(TextBlock.TextProperty); |
|||
|
|||
// And bind it to the second. |
|||
textBlock2.Bind(TextBlock.TextProperty, source); |
|||
|
|||
// Changes to the first TextBlock's Text property will now be propagated |
|||
// to the second. |
|||
textBlock1.Text = "Goodbye Cruel World"; |
|||
|
|||
// Prints "Goodbye Cruel World" |
|||
Console.WriteLine(textBlock2.Text); |
|||
``` |
|||
|
|||
# Subscribing to a Property on Any Object |
|||
|
|||
The `GetObservable` method returns an observable that tracks changes to a |
|||
property on a single instance. However, if you're writing a control you may |
|||
want to implement an `OnPropertyChanged` method. In WPF this is done by passing |
|||
a static `PropertyChangedCallback` to the `DependencyProperty` registration |
|||
method, but in Perspex it's slightly different (and hopefully easier!) |
|||
|
|||
The field which defines the property is derived from `PerspexProperty` and this |
|||
has a `Changed` observable which is fired every time the property changes on |
|||
*any* object. In addition there is an `AddClassHandler` extension method which |
|||
can automatically route the event to a method on your control. |
|||
|
|||
For example if you want to listen to changes to your control's `Foo` property |
|||
you'd do it like this: |
|||
|
|||
```c# |
|||
static MyControl() |
|||
{ |
|||
FooProperty.Changed.AddClassHandler<MyControl>(x => x.FooChanged); |
|||
} |
|||
|
|||
private void FooChanged(PerspexPropertyChangedEventArgs e) |
|||
{ |
|||
// The 'e' parameter describes what's changed. |
|||
} |
|||
``` |
|||
Loading…
Reference in new issue