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, 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
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:
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:
However, we would like to highlight all rows that contain a prime number, like so:
Things like this can usually be done using triggers. Let’s have a look.
Data Triggers
A data trigger 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:
<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:
<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 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:
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 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:
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 typeobject
, 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):
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 as a static resource:
<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:
<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) 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:
🤩 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, then follow me on LinkedIn, subscribe to this blog and star the GitHub repository for this post so you don't miss out on any future posts and developments. And remember that sharing is caring.
Subscribe to my newsletter
Read articles from Julian Ewers-Peters directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Julian Ewers-Peters
Julian Ewers-Peters
I am a passionate mobile app and software developer with a focus on C# .NET, Xamarin.Forms and .NET MAUI.