-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #766 from AvaloniaUI/binding-tweaks-and-docs
Binding tweaks and docs
- Loading branch information
Showing
11 changed files
with
283 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" } | ||
}; | ||
``` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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.* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.