Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create markup extensions having bindable properties?

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?

like image 855
D G Avatar asked Oct 23 '25 16:10

D G


1 Answers

If you make your markup extension implement both Element and IMarkupExtension then it will carry its own model. You can declare R, G, B, A as BindableProperties with both getters/setters. You can declare a get only Color property. Then, you make the IMarkupExtension just serve up that final Color property. The markup extension's BindingContext will follow the BindingContext of the target object's BindingContext.

[ContentProperty(nameof(R))]
[RequireService([typeof(IReferenceProvider), typeof(IProvideValueTarget)])]
public class RgbColorExtension : BindableObject, IMarkupExtension<BindingBase>
{
    public static readonly BindableProperty RProperty = BindableProperty.Create(nameof(R), typeof(float), typeof(RgbColorExtension),
        propertyChanged: (b, o, n) => ((RgbColorExtension)b).OnColorChanged());
    public float R
    {
        get { return (float)GetValue(RProperty); }
        set { SetValue(RProperty, value); }
    }

    public static readonly BindableProperty GProperty = BindableProperty.Create(nameof(G), typeof(float), typeof(RgbColorExtension),
        propertyChanged: (b, o, n) => ((RgbColorExtension)b).OnColorChanged());
    public float G
    {
        get { return (float)GetValue(GProperty); }
        set { SetValue(GProperty, value); }
    }

    public static readonly BindableProperty BProperty = BindableProperty.Create(nameof(B), typeof(float), typeof(RgbColorExtension),
        propertyChanged: (b, o, n) => ((RgbColorExtension)b).OnColorChanged());
    public float B
    {
        get { return (float)GetValue(BProperty); }
        set { SetValue(BProperty, value); }
    }

    public static readonly BindableProperty AProperty = BindableProperty.Create(nameof(A), typeof(float), typeof(RgbColorExtension),
        propertyChanged: (b, o, n) => ((RgbColorExtension)b).OnColorChanged());
    public float A
    {
        get { return (float)GetValue(AProperty); }
        set { SetValue(AProperty, value); }
    }

    public Color Color => Color.FromRgba(R, G, B, A);

    public void OnColorChanged() => OnPropertyChanged(nameof(Color));

    public object ProvideValue(IServiceProvider serviceProvider)
        => (this as IMarkupExtension<BindingBase>).ProvideValue(serviceProvider);
    BindingBase IMarkupExtension<BindingBase>.ProvideValue(IServiceProvider serviceProvider)
    {
        if (serviceProvider.GetService(typeof(IProvideValueTarget)) is IProvideValueTarget provideValueTarget && provideValueTarget.TargetObject is BindableObject targetObject)
        {
            this.SetBinding(BindableObject.BindingContextProperty, static (BindableObject t) => t.BindingContext, BindingMode.OneWay, source: targetObject);
        }
        return BindingBase.Create(static (RgbColorExtension t) => t.Color, BindingMode.OneWay, source: this);
    }
}

[EDIT, 18th Jun 2025]

With SQuan.Helpers.Maui.Mvvm [BindableProperty] source generator, you will be able to shorten the implementation to:

using SQuan.Helpers.Maui.Mvvm;

[ContentProperty(nameof(R))]
[RequireService([typeof(IReferenceProvider), typeof(IProvideValueTarget)])]
public partial class RgbColorExtension : BindableObject, IMarkupExtension<BindingBase>
{
    [BindableProperty, NotifyPropertyChangedFor(nameof(Color))] public partial float R { get; set; } = 0.0f;
    [BindableProperty, NotifyPropertyChangedFor(nameof(Color))] public partial float G { get; set; } = 0.0f;
    [BindableProperty, NotifyPropertyChangedFor(nameof(Color))] public partial float B { get; set; } = 0.0f;
    [BindableProperty, NotifyPropertyChangedFor(nameof(Color))] public partial float A { get; set; } = 0.0f;
    public Color Color => Color.FromRgba(R, G, B, A);
    public object ProvideValue(IServiceProvider serviceProvider) => (this as IMarkupExtension<BindingBase>).ProvideValue(serviceProvider);
    BindingBase IMarkupExtension<BindingBase>.ProvideValue(IServiceProvider serviceProvider)
    {
        if (serviceProvider.GetService(typeof(IProvideValueTarget)) is IProvideValueTarget provideValueTarget && provideValueTarget.TargetObject is BindableObject targetObject)
        {
            this.SetBinding(BindableObject.BindingContextProperty, static (BindableObject t) => t.BindingContext, BindingMode.OneWay, source: targetObject);
        }
        return BindingBase.Create(static (RgbColorExtension t) => t.Color, BindingMode.OneWay, source: this);
    }
}

References:

  • https://learn.microsoft.com/en-us/dotnet/maui/xaml/markup-extensions/create?view=net-maui-9.0
  • https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/bindable-properties?view=net-maui-9.0
  • GitHub sample: https://github.com/stephenquan/StackOverflow.Maui/tree/main/src/SO74319669
  • NuGet link: https://www.nuget.org/packages/SQuan.Helpers.Maui.Mvvm
like image 64
Stephen Quan Avatar answered Oct 26 '25 05:10

Stephen Quan



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!