Browse Source

Started adding property documentation.

pull/387/merge
Steven Kirk 10 years ago
parent
commit
453668590b
  1. 178
      docs/defining-properties.md
  2. 100
      docs/working-with-properties.md

178
docs/defining-properties.md

@ -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

100
docs/working-with-properties.md

@ -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…
Cancel
Save