MAUI Creating Custom Bindable Properties


When creating custom controls that use databinding in n .NET MAUI it’s useful to create custom bindable properties. Custom controls can participate in XAML based data binding like any other UI control using Bindable properties.

Custom Bindable properties overview

A bindable property allows you to:

  • Bind UI to data that expose.
  • Let the UI know if a change has taken place on a property.
  • They can participate in property value inheritance, and be made more dynamic.

With the BindableProperty class we create a bindable property which lets us define the property with extra properties such as defaults values and property changed callbacks and validations.

So I’m going to make a custom control called RatingView. The binding to the current rating value via a bindable property called Rating will be done through this control.

So first let’s create a RatingView that will contain star ratings and use a custom bindable property ‘Rating’ that will be implemented dynamically.

Creating the Custom Control

Next, we lay out the control in the new ContentView, RatingView.

RatingView.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiAppExample.RatingView">

    <StackLayout Orientation="Horizontal" Spacing="5">
        <Image x:Name="Star1" Source="star_empty.png" WidthRequest="40" HeightRequest="40" />
        <Image x:Name="Star2" Source="star_empty.png" WidthRequest="40" HeightRequest="40" />
        <Image x:Name="Star3" Source="star_empty.png" WidthRequest="40" HeightRequest="40" />
        <Image x:Name="Star4" Source="star_empty.png" WidthRequest="40" HeightRequest="40" />
        <Image x:Name="Star5" Source="star_empty.png" WidthRequest="40" HeightRequest="40" />
    </StackLayout>
</ContentView>

RatingView.xaml.cs

using Microsoft.Maui.Controls;

namespace MauiAppExample
{
    public partial class RatingView : ContentView
    {
        // Define a bindable property named Rating
        public static readonly BindableProperty RatingProperty = BindableProperty.Create(
            propertyName: nameof(Rating),
            returnType: typeof(int),
            declaringType: typeof(RatingView),
            defaultValue: 0,
            defaultBindingMode: BindingMode.TwoWay,
            propertyChanged: OnRatingPropertyChanged);

        public int Rating
        {
            get => (int)GetValue(RatingProperty);
            set => SetValue(RatingProperty, value);
        }

        public RatingView()
        {
            InitializeComponent();
            UpdateStars();
        }

        // Callback for when the Rating property changes
        private static void OnRatingPropertyChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var control = (RatingView)bindable;
            control.UpdateStars();
        }

        // Method to update the visual state of stars based on the Rating
        private void UpdateStars()
        {
            Star1.Source = Rating >= 1 ? "star_filled.png" : "star_empty.png";
            Star2.Source = Rating >= 2 ? "star_filled.png" : "star_empty.png";
            Star3.Source = Rating >= 3 ? "star_filled.png" : "star_empty.png";
            Star4.Source = Rating >= 4 ? "star_filled.png" : "star_empty.png";
            Star5.Source = Rating >= 5 ? "star_filled.png" : "star_empty.png";
        }
    }
}

Define Bindable Property (RatingProperty):

  • BindableProperty.Create:
    • propertyName: The property (Rating) name.
    • returnType: A property of type int.
    • declaringType: This property belongs to the Class (RatingView).
    • defaultValue: Contains (by default) the value 0 for the property.
    • defaultBindingMode: Sets how changes are propagated (TwoWay for read and write).
    • propertyChanged: This is a callback method (OnRatingPropertyChanged) triggered every time the property value changes.

Rating Property:

  • GetValue and SetValue are used by the property to talk to BindableProperty.
  • It gives Rating property to use in both XAML or in data binding.

Property Changed Callback (OnRatingPropertyChanged):

  • UpdateStars is called whenever Rating changes and the UI needs to be updated.

Update Stars Method:

  • The source images for each star gets updated with their Rating value (star_filled.png or star_empty.png).

Using the Custom Control with Data Binding

So, let’s use the RatingView in a page and bind it to a ViewModel property.

MainViewModel.cs

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace MauiAppExample
{
    public class MainViewModel : INotifyPropertyChanged
    {
        private int _userRating;

        public int UserRating
        {
            get => _userRating;
            set
            {
                if (_userRating != value)
                {
                    _userRating = value;
                    OnPropertyChanged();
                }
            }
        }

        public MainViewModel()
        {
            UserRating = 3; // Default rating
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
  • UserRating Property: This property implements the Rating property of RatingView control.
  • While this will notify the UI when the rating changes, to do this using INotifyPropertyChanged.

XAML for Main Page (MainPage.xaml):

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiAppExample.MainPage"
             xmlns:local="clr-namespace:MauiAppExample">

    <ContentPage.BindingContext>
        <local:MainViewModel />
    </ContentPage.BindingContext>

    <StackLayout Padding="20" Spacing="30" VerticalOptions="Center">
        <Label Text="Please rate the service:" FontSize="Large" HorizontalOptions="Center" />

        <!-- Using the custom RatingView control -->
        <local:RatingView Rating="{Binding UserRating, Mode=TwoWay}" />

        <Label Text="{Binding UserRating, StringFormat='Your rating: {0} stars'}"
               FontSize="Medium"
               HorizontalOptions="Center" />
    </StackLayout>
</ContentPage>

BindingContext:

  • MainViewModel is added as BindingContext of MainPage.

RatingView Binding:

  • UserRating is two way bound to the Rating property of the ViewModel. Any change to the UI will be sent to the ViewModel and the ViewModel will send a change to the UI.

Label Binding:

  • UserRating is bound property that displays the current rating using a label, formatted with StringFormat.

Some benefits of Custom Bindable Properties.

  • Reusable Custom Controls: The ability to create bindable properties bonds your UI behavior in a neat, reusable package, which makes it possible to create custom controls that are reusable across multiple pages.
  • Data Binding Support: Custom bindable properties use the XAML data binding system, allowing powerful data driven UIs.
  • Ease of Maintenance: This makes it easier to maintain and extend custom controls as your application grows because you have properties exposed as bindable properties.