Перейти к основному содержимому
Версия: 0.10.x

Binding from Code

Binding from code in Avalonia 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).

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:

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:

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:

var text = textBlock.GetObservable(TextBlock.TextProperty).Skip(1);

Binding to an observable

You can bind a property to an observable using the AvaloniaObject.Bind method:

// 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
var subscription = textBlock.Bind(TextBlock.TextProperty, source);

// Set textBlock.Text to "hello"
source.OnNext("hello");
// Set textBlock.Text to "world!"
source.OnNext("world!");

// Terminate the binding
subscription.Dispose();

Notice that the Bind method returns an IDisposable which can be used to terminate the binding. If you never call this, then then binding will automatically terminate when the observable finishes via OnCompleted or OnError.

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:

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:

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:

textBlock2[!TextBlock.TextProperty] = textBlock1[!TextBlock.TextProperty];

The only downside of this syntax is that no IDisposable is returned. If you need to manually terminate the binding then you should use the Bind method.

Transforming binding values

Because we're working with observables, we can easily transform the values we're binding!

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:

var textBlock = new TextBlock();
var viewModelProperty = textBlock.GetObservable(TextBlock.DataContext)
.OfType<MyViewModel>()
.Select(x => x?.Name);
textBlock.Bind(TextBlock.TextProperty, viewModelProperty);

However, it might be preferable to use a XAML binding in this case:

var textBlock = new TextBlock
{
[!TextBlock.TextProperty] = new Binding("Name")
};

Or, if you need an IDisposable to terminate the binding:

var textBlock = new TextBlock();
var subscription = textBlock.Bind(TextBlock.TextProperty, new Binding("Name"));

subscription.Dispose();

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 which isn't tied to an instance of an object.

To do this you can subscribe to AvaloniaProperty.Changed which is an observable which fires every time the property is changed on any instance.

In WPF this is done by passing a static PropertyChangedCallback to the DependencyProperty registration method, but this only allows the control author to register a property changed callback.

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:

static MyControl()
{
FooProperty.Changed.AddClassHandler<MyControl>(x => x.FooChanged);
}

private void FooChanged(AvaloniaPropertyChangedEventArgs e)
{
// The 'e' parameter describes what's changed.
}

Binding to INotifyPropertyChanged objects

Binding to objects that implements INotifyPropertyChanged is also available.

var textBlock = new TextBlock();

var binding = new Binding
{
Source = someObjectImplementingINotifyPropertyChanged,
Path = nameof(someObjectImplementingINotifyPropertyChanged.MyProperty)
};

textBlock.Bind(TextBlock.TextProperty, binding);