Kaynağa Gözat

Started adding property documentation.

Steven Kirk 10 yıl önce
ebeveyn
işleme
453668590b
2 değiştirilmiş dosya ile 278 ekleme ve 0 silme
  1. 178 0
      docs/defining-properties.md
  2. 100 0
      docs/working-with-properties.md

+ 178 - 0
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 - 0
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.
+    }
+```