# How to bind to functions from XAML in .NET MAUI

# Introduction

In .NET MAUI, we sometimes face the situation that we would like to evaluate a specific value, e.g. a property from a child object, against some logic from the ViewModel. I personally sometimes wish it were possible to bind directly to a method and pass a value to it so that I can decide how to display the value in the UI.

Normally, I would use a *trigger* when it comes to simply checking whether a property has a certain value and then update the UI accordingly. But, what if the evaluation isn’t as simple as checking for a specific value? What if we want to use a range of values or apply some more complex logic?

One thing that I would like to avoid when using the MVVM pattern is moving business logic to data objects or even into the UI layer, because that’s usually not where it belongs. I want my business logic to be separate from data and UI layers and I would like it to be unit testable, so having evaluation functions for objects or arguments is desired.

In this blog post I’ll demonstrate how we can use a trick in order to bind to functions from XAML by leveraging the capabilities of *data triggers*, *multi-bindings*, *multi-value converters* and *function delegates*.

As always, I’ve added the code used here to my [MAUI Samples repository](https://github.com/ewerspej/maui-samples), so that you can follow along.

# The Goal

The idea is to check whether the `Count` property of type `int` of the already existing [`BindingItem`](https://github.com/ewerspej/maui-samples/blob/main/MauiSamples/MauiSamples/Models/BindingItem.cs) class is a prime number, and if it is, display the row the object resides in in a different color, in our case light green, without modifying the data class:

```csharp
public class BindingItem
{
    public string Name { get; set; }
    public int Count { get; set; }
}
```

Currently, adding items simply displays a new row with the `Count` value for the new item:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1739961843055/aa225d06-a72c-466c-bfb0-1aa2b00c7b28.png align="center")

However, we would like to highlight all rows that contain a prime number, like so:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1739961869013/cfb73ecd-e119-42dd-aac2-a2d2a13bc933.png align="center")

Things like this can usually be done using triggers. Let’s have a look.

# Data Triggers

A [data trigger](https://learn.microsoft.com/dotnet/maui/fundamentals/triggers#data-triggers) uses a binding expression and a value to compare the property against and then applies a setter to update a specified set of visual properties of a control, like the background color:

```xml
<Label>
  <Label.Triggers>
    <DataTrigger
      TargetType="Label"
      Binding="{Binding Count}"
      Value="3">
      <Setter Property="BackgroundColor" Value="LightGreen" />
    </DataTrigger>
  </Label.Triggers>
</Label>
```

The flaw of this approach is that we would need to know the value that we want to evaluate against for each item beforehand, but we don’t. In order to check whether an integer is actually a prime number requires applying some logic, which we usually cannot directly do using a data trigger, and, as mentioned above, this logic doesn’t belong into the data layer.

However, we can still make use of a data trigger to accomplish what we want, if we combine this with a converter that evaluates to `true` or `false` based on an evaluation function for the given property:

```xml
<ContentPage.Resources>
  <converters:PrimeNumberToBoolConverter x:Key="IsPrimeConverter" />
</ContentPage.Resources>

<Label>
  <Label.Triggers>
    <DataTrigger
      TargetType="Label"
      Binding="{Binding Count, Converter={StaticResource IsPrimeConverter}}"
      Value="True">
      <Setter Property="BackgroundColor" Value="LightGreen" />
    </DataTrigger>
  </Label.Triggers>
</Label>
```

We could implement a simple `IValueConverter` that contains the logic for checking for prime numbers, but this would potentially mean moving logic into a converter, which is not desirable in most scenarios, because we may want to apply some business logic from the ViewModel, as well. So, this logic doesn’t belong into the UI layer, either.

💡 Yet, we can create a generalized converter that allows us to bind to a function delegate, which can then execute any kind of business logic from the ViewModel. This is exciting, let’s have a look.

# FuncValueToBoolConverter

The heart of our desired functionality is the *FuncValueToBoolConverter* which is an [IMultiValueConverter](https://learn.microsoft.com/dotnet/api/microsoft.maui.controls.imultivalueconverter) implementation.

In our case it only takes two values: The first one is a function delegate of type `Func<object, bool>`, and the second value is an object which serves as the argument to be be passed to the function delegate:

```csharp
public sealed class FuncValueToBoolConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values[0] is Func<object, bool> func)
        {
            return func(values[1]);
        }

        return false;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        return [];
    }
}
```

This converter is a general-purpose *multi-value converter*. Its purpose is to take a function delegate that can evaluate an object based on some business logic and either returns `true` or `false`.

In our case, we will use a method from the [BindingsViewModel](https://github.com/ewerspej/maui-samples/blob/main/MauiSamples/MauiSamples/ViewModels/BindingsViewModel.cs) class, which we don’t have yet.

# Prime number function

Let’s add a method which checks whether a number is prime to the ViewModel:

```csharp
public partial class BindingsViewModel : ObservableObject
{ 
    private static bool IsPrime(object number)
    {
        if (number is not int intNumber || intNumber < 2)
        {
            return false;
        }
    
        for (var i = 2; i <= Math.Sqrt(intNumber); i++)
        {
            if (intNumber % i == 0)
            {
                return false;
            }
        }
    
        return true;
    }
}
```

> **Note**: The `IsPrime()` method takes in an argument of type `object`, because that way, the *FuncValueToBoolConverter* can be reused in different contexts. We could also create a *FuncIntToBoolConverter* or similar in order to avoid boxing the integer.

⁉️ But wait, we cannot bind to methods, and, apart from that, this static method is `private`. Fortunately, C# allows us to expose this method as a `public` function delegate auto-property *(which means that we can use it in a binding expression)*:

```csharp
public partial class BindingsViewModel : ObservableObject
{
    // expose the IsPrime() function as a delegate,
    // note that this property must not be static
    public Func<object, bool> IsPrimeFunc => IsPrime;

    private static bool IsPrime(object number)
    {
        // see above
    }
}
```

Time to put it all together and make the magic happen. 🪄

# Putting it together

In our page, we first add the [FuncValueToBoolConverter](https://github.com/ewerspej/maui-samples/blob/main/MauiSamples/MauiSamples/Converters/FuncValueToBoolConverter.cs) as a static resource:

```xml
  <ContentPage.Resources>
    <converters:FuncValueToBoolConverter x:Key="FuncValueToBoolConverter" />
  </ContentPage.Resources>
```

Then, we can apply the converter together with a data trigger inside the *DataTemplate* of a *CollectionView*:

```xml
<CollectionView ItemsSource="{Binding Items}">
  <CollectionView.ItemTemplate>
    <DataTemplate x:DataType="models:BindingItem">
      <Grid
        Padding="8"
        ColumnDefinitions="*,*">
        <!-- skipping irrelevant parts for brevity -->
        <Grid.Triggers>

          <DataTrigger TargetType="Grid" Value="True">
            <DataTrigger.Binding>
              <MultiBinding Converter="{StaticResource FuncValueToBoolConverter}">
                <Binding
                  Path="IsPrimeFunc"
                  Source="{RelativeSource AncestorType={x:Type viewModels:BindingsViewModel}}" />
                <Binding Path="Count" />
              </MultiBinding>
            </DataTrigger.Binding>
            <DataTrigger.Setters>
              <Setter Property="BackgroundColor" Value="LightGreen" />
            </DataTrigger.Setters>
          </DataTrigger>

        </Grid.Triggers>
      </Grid>
    </DataTemplate>
  </CollectionView.ItemTemplate>
</CollectionView>
```

Note the two separate bindings in the multi-binding. The first one must be pointing to the `IsPrimeFunc` property of the ViewModel *(using a* [*relative binding*](https://learn.microsoft.com/dotnet/maui/fundamentals/data-binding/relative-bindings)*)* and the second one binds to the `Count` property of the current item.

Running the app now, we will be greeted with green rows for every prime number when adding new items to the collection:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1739961869013/cfb73ecd-e119-42dd-aac2-a2d2a13bc933.png align="center")

🤩 Isn’t this awesome? We managed to bind to a function to apply complex logic within a data binding context. I’m very happy with the result.

> You might be asking yourself whether this will also work with objects that have observable properties that frequently can change. The answer is: **Yes, absolutely.** Multi-bindings are evaluated everytime one of the properties of the child bindings changes, as long as it’s an observable property.

# Conclusions and next steps

I’ve demonstrated how we can take advantage of MAUI’s built-in capabilities in order to bind to a ViewModel method. We’ve done this by exposing it as a function delegate property and then using it in the context of a multi-binding expression together with a multi-value converter that executes the method for us. This let’s us evaluate values of data objects without having to add logic to the data class and without having to add critical business logic to converters.

If you enjoyed this blog [post](https://hashnode.com/@ewerspej), then follow me on [**LinkedIn**](https://www.linkedin.com/in/jewerspeters), subscribe to this [**blog**](https://hashnode.com/@ewerspej) and star the [**GitHub repository**](https://github.com/ewerspej/maui-samples) for this post so you don't miss out on any future posts and developments. And remember that sharing is caring.
