committed by
GitHub
11 changed files with 283 additions and 32 deletions
@ -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<string>(); |
|||
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 |
|||
/// <summary> |
|||
/// The priority of a binding. |
|||
/// </summary> |
|||
public enum BindingPriority |
|||
{ |
|||
/// <summary> |
|||
/// A value that comes from an animation. |
|||
/// </summary> |
|||
Animation = -1, |
|||
|
|||
/// <summary> |
|||
/// A local value: this is the default. |
|||
/// </summary> |
|||
LocalValue = 0, |
|||
|
|||
/// <summary> |
|||
/// A triggered style binding. |
|||
/// </summary> |
|||
/// <remarks> |
|||
/// A style trigger is a selector such as .class which overrides a |
|||
/// <see cref="TemplatedParent"/> 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. |
|||
/// </remarks> |
|||
StyleTrigger, |
|||
|
|||
/// <summary> |
|||
/// A binding to a property on the templated parent. |
|||
/// </summary> |
|||
TemplatedParent, |
|||
|
|||
/// <summary> |
|||
/// A style binding. |
|||
/// </summary> |
|||
Style, |
|||
|
|||
/// <summary> |
|||
/// The binding is uninitialized. |
|||
/// </summary> |
|||
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<string>(); |
|||
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<string>(); |
|||
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 only observables you could bind to a property on `DataContext` like this: |
|||
|
|||
```csharp |
|||
var textBlock = new TextBlock(); |
|||
var viewModelProperty = textBlock.GetObservable(TextBlock.DataContext) |
|||
.OfType<MyViewModel>() |
|||
.Select(x => x?.Name); |
|||
textBlock.Bind(TextBlock, viewModelProperty); |
|||
``` |
|||
|
|||
However, it might be preferable to 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" } |
|||
}; |
|||
``` |
|||
|
|||
@ -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 |
|||
<!-- Binds to the tb.DataContext.Name property --> |
|||
<TextBlock Name="tb" Text="{Binding Name}"/> |
|||
<!-- Which is the same as ('Path' is optional) --> |
|||
<TextBlock Name="tb" Text="{Binding Path=Name}"/> |
|||
``` |
|||
|
|||
An empty binding binds to DataContext itself |
|||
|
|||
```xml |
|||
<!-- Binds to the tb.DataContext property --> |
|||
<TextBlock Name="tb" Text="{Binding}"/> |
|||
<!-- Which is the same as --> |
|||
<TextBlock Name="tb" Text="{Binding .}"/> |
|||
``` |
|||
|
|||
This usage is identical to WPF/UWP etc. |
|||
|
|||
## Two way bindings and more |
|||
|
|||
You can also specify a binding `Mode`: |
|||
|
|||
```xml |
|||
<!-- Bind two-way to the property (although this is actually the default binding mode for |
|||
TextBox.Text) so strictly speaking it's unnecessary here) --> |
|||
<TextBox Name="tb" Text="{Binding Name, Mode=TwoWay}"/> |
|||
``` |
|||
|
|||
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 |
|||
<TextBlock Name="tb" Text="{TemplateBinding Caption}"/> |
|||
<!-- Which is short for --> |
|||
<TextBlock Name="tb" Text="{Binding Caption, RelativeSource={RelativeSource TemplatedParent}}"/> |
|||
``` |
|||
|
|||
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 |
|||
<!-- Binds to the Tag property of a control named "other" --> |
|||
<TextBlock Text="{Binding Tag, ElementName=other}"/> |
|||
``` |
|||
|
|||
However Avalonia also introduces a shorthand syntax for this: |
|||
|
|||
```xml |
|||
<TextBlock Text="{Binding #other.Tag}"/> |
|||
``` |
|||
|
|||
## Negating bindings |
|||
|
|||
You can also negate the value of a binding using the `!` operator: |
|||
|
|||
```xml |
|||
<TextBox IsEnabled="{Binding !HasErrors}"/> |
|||
``` |
|||
|
|||
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 |
|||
<!-- If DataContext.Name is an IObservable<string> then this will bind to the length of each |
|||
string produced by the observable as each value is produced --> |
|||
<TextBlock Text="{Binding Name^.Length}"/> |
|||
``` |
|||
|
|||
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.* |
|||
Loading…
Reference in new issue