diff --git a/docs/spec/binding-from-code.md b/docs/spec/binding-from-code.md new file mode 100644 index 0000000000..aa7893762b --- /dev/null +++ b/docs/spec/binding-from-code.md @@ -0,0 +1,156 @@ +# Binding from Code + +Avalonia binding from code works somewhat differently to WPF/UWP. At the low level, Avalonia's +binding system is based on Reactive Extensions' `IObservable` which is then built upon by XAML +bindings (which can also be instantiated in code). + +## Binding to an observable + +You can bind a property to an observable using the `AvaloniaObject.Bind` method: + +```csharp +// We use an Rx Subject here so we can push new values using OnNext +var source = new Subject(); +var textBlock = new TextBlock(); + +// Bind TextBlock.Text to source +textBlock.Bind(TextBlock.TextProperty, source); + +// Set textBlock.Text to "hello" +source.OnNext("hello"); +// Set textBlock.Text to "world!" +source.OnNext("world!"); +``` + +## Binding priorities + +You can also pass a priority to a binding. *Note: Priorities only apply to styled properties: they +are ignored for direct properties.* + +The priority is passed using the `BindingPriority` enum, which looks like this: + +```csharp +/// +/// The priority of a binding. +/// +public enum BindingPriority +{ + /// + /// A value that comes from an animation. + /// + Animation = -1, + + /// + /// A local value: this is the default. + /// + LocalValue = 0, + + /// + /// A triggered style binding. + /// + /// + /// A style trigger is a selector such as .class which overrides a + /// binding. In this way, a basic control can have + /// for example a Background from the templated parent which changes when the + /// control has the :pointerover class. + /// + StyleTrigger, + + /// + /// A binding to a property on the templated parent. + /// + TemplatedParent, + + /// + /// A style binding. + /// + Style, + + /// + /// The binding is uninitialized. + /// + Unset = int.MaxValue, +} +``` + +Bindings with a priority with a smaller number take precedence over bindings with a higher value +priority, and bindings added more recently take precedence over other bindings with the same +priority. Whenever the binding produces `AvaloniaProperty.UnsetValue` then the next binding in the +priority order is selected. + +## Setting a binding in an object initializer + +It is often useful to set up bindings in object initializers. You can do this using the indexer: + +```csharp +var source = new Subject(); +var textBlock = new TextBlock +{ + Foreground = Brushes.Red, + MaxWidth = 200, + [!TextBlock.TextProperty] = source.ToBinding(), +}; +``` + +Using this method you can also easily bind a property on one control to a property on another: + +```csharp +var textBlock1 = new TextBlock(); +var textBlock2 = new TextBlock +{ + Foreground = Brushes.Red, + MaxWidth = 200, + [!TextBlock.TextProperty] = textBlock1[!TextBlock.TextProperty], +}; +``` + +Of course the indexer can be used outside object initializers too: + +```csharp +textBlock2[!TextBlock.TextProperty] = textBlock1[!TextBlock.TextProperty]; +``` + +# Transforming binding values + +Because we're working with observables, we can easily transform the values we're binding! + +```csharp +var source = new Subject(); +var textBlock = new TextBlock +{ + Foreground = Brushes.Red, + MaxWidth = 200, + [!TextBlock.TextProperty] = source.Select(x => "Hello " + x).ToBinding(), +}; +``` + +# Using XAML bindings from code + +Sometimes when you want the additional features that XAML bindings provide, it's easier to use XAML bindings from code,. For example, using observables you could bind to a property on `DataContext` like this: + +```csharp +var textBlock = new TextBlock(); +var viewModelProperty = textBlock.GetObservable(TextBlock.DataContext) + .OfType() + .Select(x => x?.Name); +textBlock.Bind(TextBlock, viewModelProperty); +``` + +However, it might be preferable to just use a XAML binding in this case: + +```csharp +var textBlock = new TextBlock +{ + [!TextBlock.TextProperty] = new Binding("Name") +}; +``` + +By using XAML binding objects, you get access to binding to named controls and [all the other features that XAML bindings bring](binding-from.xaml.md): + +```csharp +var textBlock = new TextBlock +{ + [!TextBlock.TextProperty] = new Binding("Text") { ElementName = "other" } +}; +``` + diff --git a/docs/spec/binding-from-xaml.md b/docs/spec/binding-from-xaml.md new file mode 100644 index 0000000000..143e3627c8 --- /dev/null +++ b/docs/spec/binding-from-xaml.md @@ -0,0 +1,99 @@ +# Binding from XAML + +Binding from XAML works on the whole the same as in other XAML frameworks: you use the `{Binding}` +markup extension. Avalonia does have some extra syntacic niceties however. Here's an overview of +what you can currently do in Avalonia: + +## Binding to a property on the DataContext + +By default a binding binds to a property on the `DataContext`, e.g.: + +```xml + + + + +``` + +An empty binding binds to DataContext itself + +```xml + + + + +``` + +This usage is identical to WPF/UWP etc. + +## Two way bindings and more + +You can also specify a binding `Mode`: + +```xml + + +``` + +This usage is identical to WPF/UWP etc. + +## Binding to a property on the templated parent + +When you're creating a control template and you want to bind to the templated parent you can use: + +```xml + + + +``` + +This usage is identical to WPF/UWP etc. + +## Binding to a named control + +If you want to bind to a property on another (named) control, you can use `ElementName` as in +WPF/UWP: + +```xml + + +``` + +However Avalonia also introduces a shorthand syntax for this: + +```xml + +``` + +## Negating bindings + +You can also negate the value of a binding using the `!` operator: + +```xml + +``` + +Here, the `TextBox` will only be enabled when the view model signals that it has no errors. Behind +the scenes, Avalonia tries to convert the incoming value to a boolean, and if it can be converted +it negates the value. If the incoming value cannot be converted to a boolean then no value will be +pushed to the binding target. + +This syntax is specific to Avalonia. + +## Binding to tasks and observables + +You can subscribe to the result of a task or an observable by using the `^` stream binding operator. + +```xml + + +``` + +This syntax is specific to Avalonia. + +*Note: the stream operator is actually extensible, see +[here](https://github.com/AvaloniaUI/Avalonia/blob/master/src/Markup/Avalonia.Markup/Data/Plugins/IStreamPlugin.cs) +for the interface to implement and [here](https://github.com/AvaloniaUI/Avalonia/blob/master/src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs#L47) +for the registration.* diff --git a/docs/spec/toc.yml b/docs/spec/toc.yml index f225084c4e..24a84ed06b 100644 --- a/docs/spec/toc.yml +++ b/docs/spec/toc.yml @@ -8,3 +8,7 @@ href: working-with-properties.md - name: Logging href: logging.md +- name: Binding from XAML + href: binding-from-xaml +- name: Binding from Code + href: binding-from-code diff --git a/docs/spec/working-with-properties.md b/docs/spec/working-with-properties.md index 74dd60a9b2..a8a383b733 100644 --- a/docs/spec/working-with-properties.md +++ b/docs/spec/working-with-properties.md @@ -71,6 +71,8 @@ property to the first: Console.WriteLine(textBlock2.Text); ``` +To read more about creating bindings from code, see [Binding from Code](binding-from-code.md). + # Subscribing to a Property on Any Object The `GetObservable` method returns an observable that tracks changes to a