Skip to content

Latest commit

 

History

History
 
 

ValueConversionSample

Value conversion sample

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.

Difficulty

🐥 Easy 🐥

Buzz-Words

Converter, Binding, MultiBinding, IValueConverter, IMultiValueConverter, MVVM

Before we start

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].

IValueConverter

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 the Property 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 bound Property in your ViewModel. 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.

IMultiValueConverter

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 the Property 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.

The Solution 1 : Using IValueConverter

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.

Step 1: Setup your ViewModel

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]

Step 2: Create the MathAddConverter

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.

Step 3: Setup the View

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.

Step 4: See it in action

In your IDE select [Run] or [Debug] to see the result in action. Try to change the value in both input boxes and see how they update each other.

IValueConverter sample

Solution 2 : Using IMultiValueConverter

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.

Step 1: Setup your ViewModel

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[]
    {
        "+", "-", "*", "/"
    };
}

Step 2: Create the MathMultiConverter

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]

Step 3: Setup the View

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.

Step 4: See it in action

In your IDE select [Run] or [Debug] to see the result in action. Try to change the value in both input boxes and the ComboBox and see how they update the result.

IMultiValueConverter sample

Avalonia has some really nice built-in Converters for you. Read more about in the [Docs].