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?
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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With