How to create markup extensions having bindable properties?

368 Views Asked by At

I want to have a markup extension called RgbColorExtension where R property is bindable to let me adjust its value with a slider.

The following attempt does not work. Changing the slider does not effect the R property.

<?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="Sandbox.MainPage"
             xmlns:local="clr-namespace:Sandbox"
             >
    <VerticalStackLayout>
        <Slider x:Name="slider" Maximum="1"/>
        <Label Text="{Binding Value,Source={x:Reference slider}}"/>
        <BoxView HeightRequest="200" WidthRequest="200">
            <BoxView.Color>
                <local:RgbColor R="{Binding Value,Source={x:Reference slider}}" G="0" B="0" A="0.5"/>
            </BoxView.Color>
        </BoxView>
        <BoxView 
            HeightRequest="200" 
            WidthRequest="200"
            Color="{local:RgbColor R={Binding Value,Source={x:Reference slider}},G=0.5,B=0.5,A=.5}"
            />
    </VerticalStackLayout>

</ContentPage>

namespace Sandbox;

public class RgbColorExtension : BindableObject, IMarkupExtension<Color>
{

    public static readonly BindableProperty RProperty =
        BindableProperty.Create(nameof(R), typeof(float), typeof(RgbColorExtension), 0.5f);

    public float R
    {
        get => (float)GetValue(RProperty);
        set => SetValue(RProperty, value);
    }


    public float G { get; set; }
    public float B { get; set; }
    public float A { get; set; }


    public Color ProvideValue(IServiceProvider serviceProvider)
    {
        return Color.FromRgba(R, G, B, A);
    }

    object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
    {
        return (this as IMarkupExtension<Color>).ProvideValue(serviceProvider);
    }
}

What am I missing here?

1

There are 1 best solutions below

0
On BEST ANSWER

Since you have 4 parameters, you can have your IMarkupExtension implement (1) BindableObject (which you've already done so), (2) MultiBinding, with (3) an IMultiValueConverter. The 4 float values can participate in the MultiBinding and the IMultiValueConverter converts those 4 float values into a Color value.

Here's a working implementation:

[ContentProperty("R")]
public class RgbColorExtension : BindableObject, IMarkupExtension<BindingBase>, IMultiValueConverter
{
    public static readonly BindableProperty RProperty
        = BindableProperty.Create(nameof(R), typeof(float), typeof(RgbColorExtension));

    public static readonly BindableProperty GProperty
        = BindableProperty.Create(nameof(G), typeof(float), typeof(RgbColorExtension));

    public static readonly BindableProperty BProperty
        = BindableProperty.Create(nameof(B), typeof(float), typeof(RgbColorExtension));

    public static readonly BindableProperty AProperty
        = BindableProperty.Create(nameof(A), typeof(float), typeof(RgbColorExtension));

    public float R
    {
        get { return (float)GetValue(RProperty); }
        set { SetValue(RProperty, value); }
    }

    public float G
    {
        get { return (float)GetValue(GProperty); }
        set { SetValue(GProperty, value); }
    }

    public float B
    {
        get { return (float)GetValue(BProperty); }
        set { SetValue(BProperty, value); }
    }

    public float A
    {
        get { return (float)GetValue(AProperty); }
        set { SetValue(AProperty, value); }
    }

    public object ProvideValue(IServiceProvider serviceProvider)
    {
        return (this as IMarkupExtension<BindingBase>).ProvideValue(serviceProvider);
    }

    BindingBase IMarkupExtension<BindingBase>.ProvideValue(IServiceProvider serviceProvider)
    {
        return new MultiBinding()
        {
            Converter = this,
            Mode = BindingMode.OneWay,
            Bindings = new Collection<BindingBase>
            {
                new Binding(nameof(R), BindingMode.OneWay, null, null, null, this),
                new Binding(nameof(G), BindingMode.OneWay, null, null, null, this),
                new Binding(nameof(B), BindingMode.OneWay, null, null, null, this),
                new Binding(nameof(A), BindingMode.OneWay, null, null, null, this)
            }
        };
    }

    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        float R = (float)values[0];
        float G = (float)values[1];
        float B = (float)values[2];
        float A = (float)values[3];
        return Color.FromRgba(R, G, B, A);
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}