Telerik blogs

Learn how to combine content views with bindable properties, so you can control both the appearance and behavior of a custom control.

Within the world of mobile development and development in general, there is always a need to combine two or more controls to create more complex components that we can reuse across different projects. Fortunately, .NET MAUI has features that allow us to achieve this.

In this post, you will learn how to create a reusable component that shows the progress of a task. We will achieve this by combining content views with bindable properties, which will allow us to control both the appearance and behavior of the custom control. Let’s get started!

Creating a ContentView

A ContentView is a control that allows the creation of reusable and custom controls. You can think of this control as a blank canvas on which you can place different controls, images, text, etc., with the purpose of reusing it throughout one or several applications.

Some examples of using a ContentView control could be to define the visual appearance of rows in a CollectionView or a custom view for a TitleView. Although there are several ways to create a ContentView, the simplest is to click on the context menu in the solution explorer, then Right Click | Add | New Item | .NET MAUI ContentPage (XAML) as shown below:

Selecting the ContentView template

Some things to note in the previous image are that it is also possible to create a ContentView file with just C# code. In my case, I will use the XAML template with the name Downloader.

Adding Content to the ContentView

Once the ContentView file has been created, it will be opened automatically. This file has a structure similar to a ContentPage with the difference that the container type is a ContentView:

<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="CustomControlDemo.Views.Downloader">
    ...
</ContentView>

Just like a ContentPage, a ContentView has a Content property, which we can use to add any desired content. In this example, we will use the following controls to create the custom control:

  • A Border to group the control’s content and to add a shadow
  • A Label to display a custom message about the download in general
  • A Label to indicate the percentage completed
  • A RadLinearProgressBar control to visually indicate the progress

The XAML code would look as follows:

<Grid Margin="10" MaximumHeightRequest="150">
    <Border
        Background="White"
        Stroke="White"
        StrokeShape="RoundRectangle 10">
        <Grid Margin="15" RowDefinitions="*,*,Auto">
            <Label
                x:Name="SubTitleControl"
                FontAttributes="Bold"
                FontSize="12"
                Text="Your progress"
                TextColor="#BFC9D2" />
            <Label
                x:Name="TitleControl"
                Grid.Row="1"
                FontAttributes="Bold"
                FontSize="15"
                Text="30% To Complete"
                TextColor="#0F49F7" />
            <telerik:RadLinearProgressBar
                x:Name="ProgressControl"
                Grid.Row="2"
                Margin="0,10,0,0"
                AutomationId="progressBar"
                Value="60" />
        </Grid>
        <Border.Shadow>
            <Shadow
                Brush="Gray"
                Opacity="0.8"
                Radius="40"
                Offset="20,20" />
        </Border.Shadow>
    </Border>
</Grid>

With this, we have created the content of the custom control. The next step is to use it somewhere within the .NET MAUI project, which we will do next.

Using Custom Control Within the Project

Once we have created the custom control, we will use it in our project. To do this, you first need to add the reference to the namespace location of your control. I have created a folder called Controls within the project, so the namespace to use will be as follows:

xmlns:controls="clr-namespace:CustomControlDemo.Controls"

With the reference created, all that is needed is to use the control, as in the following example:

<VerticalStackLayout Spacing="50" VerticalOptions="Center">
    <controls:Downloader />
    <Button Margin="25" Text="Start Download" />
</VerticalStackLayout>

This will show the control when the application is executed, as in the following image:

The initial Downloader control

The control undoubtedly looks great, but it is not usable at the moment because there is no way to manipulate the values of the custom control. This is where Bindable Properties can help us create an access point for custom control. Let’s see how to use them!

Creating Your First Bindable Property

A bindable property allows extending a Common Language Runtime (CLR) property by using a BindableProperty type instead of a field. In short, this means we will be able to use these properties through data binding and, if needed, we can assign default values, validate property values and add callbacks to execute actions when property values change.

To create a bindable property, we will go to the class where we want to add it. In our example, we will go to the Downloader.xaml.cs file. In this file, we will create the following BindableProperty:

public static readonly BindableProperty TitleProperty =
    BindableProperty.Create
    (
        "Title",
        typeof(string),
        typeof(Downloader),
        "0% To Complete",
        propertyChanged: (bindable, oldValue, newValue) =>
        {
            if (bindable is Downloader instance)
            {
                instance.TitleControl.Text = (string)newValue;
            }
        });

This is what each line in the previous code does:

  1. Specifies the identifier of the bindable property (TitleProperty), with the signature public static readonly BindableProperty. This signature must always be used when creating a bindable property and, by convention, it should contain the word “Property” at the end.
  2. Invokes the Create method, which initializes the instance according to the provided parameters.
  3. Specifies the name of the bindable property, which, by convention, should be the same as the identifier but without the word “Property.”
  4. Specifies the type of the bindable property. In our case, since we want to modify the text that displays the completed percentage, we will indicate that it is of type string.
  5. Specifies the type of the object that contains the bindable property; in our example, the container class is Downloader.
  6. Specifies a default value, which means that if the user of the bindable property does not assign a value, a default value is returned to avoid errors.
  7. Specifies a property changed delegate that will be invoked when the property value changes. Specifically, we obtain the reference of the custom control to access its internal controls and modify the Text property of the TitleControl control, which is one of the Labels displaying completion information.

The Create method accepts more parameters than those shown here. For more information, you can refer to the following documentation page.

The second part of creating a bindable property involves creating a property backed by the BindableProperty we previously created. We achieve this as follows:

public string Title
{
    get { return (string)GetValue(TitleProperty); }
    set { SetValue(TitleProperty, value); }
}

In the previous code, you can see that the property uses the GetValue method to return the value of TitleProperty in the getter, and the SetValue method to assign the value to TitleProperty in the setter.

Since we want the custom control to have multiple properties available (you can add as many as you want to the example), I have created a few others which I show below:

public partial class Downloader : ContentView
{
    public static readonly BindableProperty TitleProperty =
        BindableProperty.Create("Title", typeof(string), typeof(Downloader), "0% To Complete",
            propertyChanged: (bindable, oldValue, newValue) =>
            {
                if (bindable is Downloader instance)
                {
                    instance.TitleControl.Text = (string)newValue;
                }
            });

    public static readonly BindableProperty SubTitleProperty =
        BindableProperty.Create("SubTitle", typeof(string), typeof(Downloader), "Your progress",
             propertyChanged: (bindable, oldValue, newValue) =>
             {
                 if (bindable is Downloader instance)
                 {
                     instance.SubTitleControl.Text = (string)newValue;
                 }
             });
    public static readonly BindableProperty ProgressProperty =
        BindableProperty.Create("Progress", typeof(double), typeof(Downloader), 0.0,
            propertyChanged: (bindable, oldValue, newValue) =>
            {
                if (bindable is Downloader instance)
                {
                    instance.ProgressControl.Value = (double)newValue;
                }
            });
    public string Title
    {
        get { return (string)GetValue(TitleProperty); }
        set { SetValue(TitleProperty, value); }
    }

    public string SubTitle
    {
        get { return (string)GetValue(SubTitleProperty); }
        set { SetValue(SubTitleProperty, value); }
    }

    public string Progress
    {
        get { return (string)GetValue(ProgressProperty); }
        set { SetValue(ProgressProperty, value); }
    }


    public Downloader()
    {
        InitializeComponent();
        TitleControl.Text = (string)TitleProperty.DefaultValue;
        SubTitleControl.Text = (string)SubTitleProperty.DefaultValue;
        ProgressControl.Value = (double)ProgressProperty.DefaultValue;
    }
}

Manipulating Custom Control Properties

We now have both the graphical and logical parts of the custom control. Now we can go back to where we used the control and use the newly created bindable properties:

<controls:Downloader
    Title="Current download: 0%"
    Progress="25"
    SubTitle="Saving a copy of the file" />

You can see how this allows us to customize the custom control as much as we want. The true power of bindable properties is that we can bind them to a ViewModel through bindings. In my example, I have created the following ViewModel:

public partial class MainViewModel : ObservableObject
{
    [ObservableProperty]
    string title = "Current download: 0%";

    [ObservableProperty]
    double progress;

    [ObservableProperty]
    string subTitle = "Saving a copy of the file";
    partial void OnProgressChanged(double value)
    {            
        Title = $"Current download: {value.ToString("F2")}%";
    }
    [RelayCommand]
    public async Task Download()
    {
        Progress = 0.0;
        for (int i = 0; i <= 100; i++)
        {
            await Task.Delay(100);
            Progress = i;
        }
    }
}

In the previous code, I created bindable properties to manipulate the properties of the control, including a simulation of downloading a file in the Download method. With this, we can bind the properties of the ViewModel with the XAML code as follows:

<VerticalStackLayout Spacing="50" VerticalOptions="Center">
    <controls:Downloader
        Title="{Binding Title}"
        Progress="{Binding Progress}"
        SubTitle="{Binding SubTitle}" />
    <Button
        Margin="25"
        Command="{Binding DownloadCommand}"
        Text="Start Download" />
</VerticalStackLayout>

The result is seeing how the values of the custom control change when pressing the button:

The Downloader control with implemented functionality show current download at 43%

Conclusion

In this post, you have learned how to create ContentViews to build the graphical part of a custom control. Likewise, you have learned what bindable properties are and how to use them to enable properties from outside the control, allowing the use of data binding.

This knowledge will give you great power in creating reusable components not only in one project but across all your projects with .NET MAUI.


About the Author

Héctor Pérez

Héctor Pérez is a Microsoft MVP with more than 10 years of experience in software development. He is an independent consultant, working with business and government clients to achieve their goals. Additionally, he is an author of books and an instructor at El Camino Dev and Devs School.

 

Related Posts

Comments

Comments are disabled in preview mode.