In this sample you will learn how you can use a Converter
inside your Binding
in order to calculate a new value for the View
. This can be useful if you need to do some math operations or other conversions like showing different colors for different object states.
In this sample we assume that you know what a Binding
is and that you have a basic knowledge of the MVVM
concepts. Moreover you should already know how to create a new Avalonia project.
In case you want to refresh your knowledge, visit the [Basic MVVM Sample].
If your class implements [IValueConverter
], it can be used to convert a value provided by a Binding
into any other object
. The interface implements the following members:
- Convert
-
This method will be called from your
Binding
when a new value is provided by theProperty
you bind to. For details about the parameters and return value, see the [API Reference]. - ConvertBack
-
This method will be called from your
Binding
when a new value is provided by the user interface and will set the boundProperty
in yourViewModel
. For details about the parameters and return value, see the [API Reference].
|
You should not throw exceptions inside your converter. If the value is not convertible, return a [BindingNotification ] in an error state or [BindingOperations.DoNothing ] if your converter should just not do anything.
|
💡
|
If you don’t want to support Convert or ConvertBack , you can simply return [BindingOperations.DoNothing ]. This will prevent the Binding from updating.
|
If your class implements [IMultiValueConverter
], it can be used to convert an array of values provided by a MultiBinding
into any object
. The interface implements the following member:
- Convert
-
This method will be called from your
Binding
when a new value is provided by theProperty
you bind to. For details about the parameters and return value, see the [API Reference].
|
You should not throw exceptions inside your converter. If the value is not convertible, return a [BindingNotification ] in an error state or [BindingOperations.DoNothing ] if your converter should just not do anything.
|
ℹ️
|
Other than in IValueConverter the method ConvertBack is not present for IMultiValueConverter .
|
In this sample we will write a Converter
that can be used to add a constant number to another number. The number to add will be provided by the ConverterParameter
. In the ConvertBack
method we will subtract the parameter.
In your MainWindowViewModel
add a decimal
called Number1
like shown below:
// The initial value is 2.
private decimal? _Number1 = 2;
/// <summary>
/// This is our Number 1
/// </summary>
public decimal? Number1
{
get { return _Number1; }
set { this.RaiseAndSetIfChanged(ref _Number1, value); }
}
ℹ️
|
The NumericUpDown control works best with decimal or decimal values as these can store exact numbers. [more about it]
|
In the project we create a new folder called Converter
. This will be the place where we keep all our converters. Inside this folder we add a class
called MathAddConverter.cs
. This class
should implement IValueConverter
.
using Avalonia.Data.Converters;
using System;
using System.Globalization;
namespace ValueConversionSample.Converter
{
/// <summary>
/// This is a converter which will add two numbers
/// </summary>
public class MathAddConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
// For add this is simple. just return the sum of the value and the parameter.
// You may want to validate value and parameter in a real world App
return (decimal?)value + (decimal?)parameter;
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
// If we want to convert back, we need to subtract instead of add.
return (decimal?)value - (decimal?)parameter;
}
}
}
To clarify things further:
- value
-
This is the value which is provided by the
Binding
. - parameter
-
This is an optional converter parameter. We will see later how we can provide this parameter.
First we need to create a new instance of our MathAddConverter
and the ConverterParameter
as a [Resource
] which can be used later. We do this inside Window.Resources
, but it can be done also in App.axaml
or in any other Resources
-section.
ℹ️
|
Each Resource must have a unique key defined by x:Key
|
<Window x:Class="ValueConversionSample.Views.MainWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:conv="using:ValueConversionSample.Converter"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:ValueConversionSample.ViewModels"
Title="ValueConversionSample"
Width="500" Height="200"
x:CompileBindings="True" x:DataType="vm:MainWindowViewModel"
Icon="/Assets/avalonia-logo.ico"
mc:Ignorable="d">
<Window.Resources>
<!-- Add the MathAddConverter. Remember to add the needed namespace "conv" -->
<conv:MathAddConverter x:Key="MathAddConverter" />
<!-- This Resource will be used as our ConverterParameter -->
<x:Decimal x:Key="MyConverterParameter">2</x:Decimal>
</Window.Resources>
</Window>
Now we can add two [NumericUpDown
]-controls to our UI. Both will bind to Number1
while the second one will use our Converter
to calculate the sum of Number1
and the ConverterParameter
defined in Resources
.
<Grid ColumnDefinitions="Auto, *" RowDefinitions="Auto, Auto">
<TextBlock Grid.Row="0" Grid.Column="0"
Text="Number 1" />
<NumericUpDown Grid.Row="0" Grid.Column="1"
Value="{Binding Number1}" />
<TextBlock Grid.Row="1" Grid.Column="0"
Text="Sum" />
<NumericUpDown Grid.Row="1" Grid.Column="1"
Value="{Binding Number1, Converter={StaticResource MathAddConverter}, ConverterParameter={StaticResource MyConverterParameter}}" />
</Grid>
💡
|
ConverterParameter can only be a static value. You cannot bind to it and you cannot use DynamicResource . If you need more flexibility, consider to do your calculations in your ViewModel or use a MultiValueConverter .
|
If you want to bind to more than one value in your Converter
, you can implement the interface IMultiValueConverter
. In this sample we will take two numbers and calculate the result using a given operator. Because we want all three parameters to be dynamic, we will not use the ConverterParameter
here.
In addition to our Number1
from the above sample we will add another decimal
called Number2
, the Operator
as string
and a list of available Operators
the user can choose from.
💡
|
The Operator is defined as string in our case, but it can also be a char , an enum or any other object .
|
public class MainWindowViewModel : ViewModelBase
{
// The initial value is 2.
private decimal? _Number1 = 2;
/// <summary>
/// This is our Number 1
/// </summary>
public decimal? Number1
{
get { return _Number1; }
set { this.RaiseAndSetIfChanged(ref _Number1, value); }
}
// The initial value is 3.
private decimal? _Number2 = 3;
/// <summary>
/// This is our Number 2
/// </summary>
public decimal? Number2
{
get { return _Number2; }
set { this.RaiseAndSetIfChanged(ref _Number2, value); }
}
// The initial value is "+" (Add).
private string _Operator = "+";
/// <summary>
/// Gets or sets the operator to use.
/// </summary>
public string Operator
{
get { return _Operator; }
set { this.RaiseAndSetIfChanged(ref _Operator, value); }
}
/// <summary>
/// Gets a collection of available operators
/// </summary>
public string[] AvailableMathOperators { get; } = new string[]
{
"+", "-", "*", "/"
};
}
In the folder Converter
add a new class called MathMultiConverter.cs
, which implements IMultiValueConverter
like shown below:
using Avalonia.Data;
using Avalonia.Data.Converters;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
namespace ValueConversionSample.Converter
{
/// <summary>
/// This converter can calculate any number of values.
/// </summary>
public class MathMultiConverter : IMultiValueConverter
{
public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
{
// We need to validate if the provided values are valid. We need at least 3 values.
// The first value is the operator and the other two values should be a decimal.
if (values.Count != 3)
{
// We can write a message into the Trace if we want to inform the developer.
Trace.WriteLine("Exactly three values expected");
// return "BindingOperations.DoNothing" instead of throwing an Exception.
// If you want, you can also return a BindingNotification with an Exception
return BindingOperations.DoNothing;
}
// The first item of values is the operation.
// The operation to use is stored as a string.
string operation = values[0] as string ?? "+";
// Create a variable result and assign the first value we have to if
decimal value1 = values[1] as decimal? ?? 0;
decimal value2 = values[2] as decimal? ?? 0;
// depending on the operator calculate the result.
switch (operation)
{
case "+":
return value1 + value2;
case "-":
return value1 - value2;
case "*":
return value1 * value2;
case "/":
// We cannot divide by zero. If value2 is '0', return an error.
if (value2 == 0)
{
return new BindingNotification(new DivideByZeroException("Don't do this!"), BindingErrorType.Error);
}
return value1 / value2;
}
// If we reach this line, something was wrong. So we return an error notification
return new BindingNotification(new InvalidOperationException("Something went wrong"), BindingErrorType.Error);
}
}
}
❗
|
The order of the values provided may be important like shown in our sample.
|
💡
|
The ?? in C# can be used to define a default value, if the value provided is null . See [Microsoft Docs]
|
Again we need to add a new MathMultiConverter
into our Resources
section:
<Window x:Class="ValueConversionSample.Views.MainWindow"
xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:conv="using:ValueConversionSample.Converter" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="using:ValueConversionSample.ViewModels"
Title="ValueConversionSample"
Width="500" Height="200"
x:CompileBindings="True" x:DataType="vm:MainWindowViewModel"
Icon="/Assets/avalonia-logo.ico"
mc:Ignorable="d">
<Window.Resources>
<!-- Add the MathMultiConverter -->
<conv:MathMultiConverter x:Key="MathMultiConverter" />
</Window.Resources>
</Window>
Our view will consist of one [ComboBox
] and two NumericUpDown
-controls. In the ComboBox
the user can select one of the available operators.
The result will be shown in another NumericUpDown
-control, which is read-only. As we cannot convert back, a user input is not allowed here. As you can see we use a MultiBinding
with three nested Bindings
:
<Grid ColumnDefinitions="Auto, *" RowDefinitions="Auto, Auto, Auto, Auto">
<TextBlock Grid.Row="0" Grid.Column="0"
Text="Operator" />
<ComboBox Grid.Row="0" Grid.Column="1"
ItemsSource="{Binding AvailableMathOperators}"
SelectedItem="{Binding Operator}" />
<TextBlock Grid.Row="1" Grid.Column="0"
Text="Number 1" />
<NumericUpDown Grid.Row="1" Grid.Column="1"
Value="{Binding Number1}" />
<TextBlock Grid.Row="2" Grid.Column="0"
Text="Number 2" />
<NumericUpDown Grid.Row="2" Grid.Column="1"
Value="{Binding Number2}" />
<TextBlock Grid.Row="3" Grid.Column="0"
Text="Result" />
<NumericUpDown Grid.Row="3" Grid.Column="1"
IsReadOnly="True">
<NumericUpDown.Value>
<MultiBinding Converter="{StaticResource MathMultiConverter}" Mode="OneWay">
<Binding Path="Operator" />
<Binding Path="Number1" />
<Binding Path="Number2" />
</MultiBinding>
</NumericUpDown.Value>
</NumericUpDown>
</Grid>
|
Input-controls have Properties that binds TwoWay by default, like TextBox.Text or NumericUpDown.Value . If you require a OneWay-Binding , you must set the [BindingMode ] to OneWay on your own. Otherwise your App will fail when trying to update the Binding .
|
Avalonia has some really nice built-in Converters
for you. Read more about in the [Docs].