xamlmobilemaui

How do I utilize a value derived from parameters in MAUI?


I am creating a component that displays information about how many more trips a user needs to complete in order to receive a discount.

<!--DiscountProgressCard.xaml-->

<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MyProject.DiscountProgressCard"
             x:Name="this">

    <Border BindingContext="{x:Reference this}">  <!--Border styling cut for brevity -->
        <VerticalStackLayout>

            <ProgressBar ProgressColor="Green" Progress="{Binding Progress}" />

            <Label>
                <Label.Text>
                    <MultiBinding StringFormat="{}{0} out of {1} trips completed.">
                        <Binding Path="CompletedTrips" />
                        <Binding Path="TripsNecessaryForDiscount" />
                    </MultiBinding>
                </Label.Text>
            </Label>

        </VerticalStackLayout>
    </Border>
</ContentView>

The values CompletedTrips and TripsNecessaryForDiscount are bindable properties. Progress is meant to be derived from these properties, and utilized solely within this component.

public partial class DiscountProgressCard : ContentView
{
    public DiscountProgressCard()
    {
        InitializeComponent();
    }

    //Parameter - The number of trips the user has already completed.
    public int CompletedTrips
    {
        get { return (int)GetValue(CompletedTripsProperty); }
        set { SetValue(CompletedTripsProperty, value); }
    }

    public static readonly BindableProperty CompletedTripsProperty = BindableProperty.Create(
        nameof(CompletedTrips), 
        typeof(int), 
        typeof(DiscountProgressCard),
        0);

    //Parameter - The number of trips the user must complete to receive a discount.
    public int TripsNecessaryForDiscount
    {
        get { return (int)GetValue(TripsNecessaryForDiscountProperty); }
        set { SetValue(TripsNecessaryForDiscountProperty, value); }
    }

    public static readonly BindableProperty TripsNecessaryForDiscountProperty = BindableProperty.Create(
        nameof(TripsNecessaryForDiscount),
        typeof(int), 
        typeof(DiscountProgressCard),
        999);

    //Progress should be derived from CompletedTrips and TripsNecessaryForDiscount.
    protected decimal Progress { 
        get { return Decimal.Divide(CompletedTrips, TripsNecessaryForDiscount); }
    }

When the component is rendered, and values are provided to the parameters CompletedTrips and TripsNecessaryForDiscount, those parameters are displayed correctly. However, the getter of Progress is only run once, using the default values of CompletedTrips and TripsNecessaryForDiscount. As a result, the progress bar is always empty.

<!--MyPage.xaml-->

<ContentPage>

<!--...cut for brevity...-->

    <components:DiscountProgressCard CompletedTrips=200" TripsNecessaryForDiscount="400" />

</ContentPage>

A card with a label and progress bar. The label reads, "200 out of 400 trips completed." However, the progress bar remains empty, when it should be half filled.

Why is Progress not being recalculated with the provided parameter values? And how do I go about fixing it?


Solution

  • Progress is not updated, because there is no notification that something has changed. You'll need to raise a notification that Progress should be updated whenever the CompletedTrips updates:

    public static readonly BindableProperty CompletedTripsProperty = BindableProperty.Create(
        nameof(CompletedTrips), 
        typeof(int), 
        typeof(DiscountProgressCard),
        0,
        propertyChanged: OnProgressChanged);
    
    private static void OnProgressChanged(BindableObject bindable, object oldValue, object newValue)
    {
        ((DiscountProgressCard)bindable).OnPropertyChanged(nameof(Progress));
    }
    

    Apart from that, the Progress property should probably be public.