2

I am trying to customize the appearance of a RadioButton in .NET MAUI, with the goal of dynamically changing the text color based on its selection state (Checked/Unchecked) and using the button's background as a visual indicator for selection. However, I encountered a challenge: I cannot apply the text color directly to the container element (like a Grid or Border) because they do not have a TextColor property.

How can I achieve this effect while ensuring the RadioButton has no checkmark or other icon?

Any help would be greatly appreciated!

<Style TargetType="RadioButton" x:Key="WizardRadioButton">
    <Setter Property="FontFamily" Value="Inter-Bold"/>
    <Setter Property="FontSize" Value="12"/>
    <Setter Property="Margin" Value="0,6,0,6"/>
    <Setter Property="MinimumHeightRequest" Value="44"/>
    <Setter Property="MinimumWidthRequest" Value="44"/>
    <Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}"/>
    <Setter Property="BackgroundColor" Value="Transparent"/>
    <Setter Property="ControlTemplate">
        <Setter.Value>
            <ControlTemplate>
                <Border StrokeThickness="0"
                    Stroke="Transparent"
                    Padding="10,5"
                    BackgroundColor="Transparent"
                        StrokeShape="RoundRectangle 8">
                    <ContentPresenter VerticalOptions="Center"
                                      HorizontalOptions="Center"/>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CheckedStates">
                            <VisualState x:Name="Checked">
                                <VisualState.Setters>
                                    <Setter Property="BackgroundColor" Value="#1CB41C"/>
                                    
                                </VisualState.Setters>
                            </VisualState>
                            <VisualState x:Name="Unchecked">
                                <VisualState.Setters>
                                    <Setter Property="BackgroundColor" Value="White"/>
                                    
                                </VisualState.Setters>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
2
  • What platform are you targeting on? Commented Feb 19 at 7:43
  • Data binding or implementing CheckedChanged event could also do the job. More info, you can refer to RadioButton Commented Apr 2 at 2:42

2 Answers 2

1

The ContentPresenter seems to be a Label. So, what you can define a ResourceDictionary in your ContentPresenter to bind Label.TextColor to the Border.Stroke.Color:

<Style x:Key="WizardRadioButton" TargetType="RadioButton">
    <Setter Property="FontFamily" Value="Inter-Bold" />
    <Setter Property="FontSize" Value="12" />
    <Setter Property="Margin" Value="0,6,0,6" />
    <Setter Property="MinimumHeightRequest" Value="44" />
    <Setter Property="MinimumWidthRequest" Value="44" />
    <Setter Property="BackgroundColor" Value="Transparent" />
    <Setter Property="ControlTemplate">
        <Setter.Value>
            <ControlTemplate>
                <Border
                    Padding="10,5"
                    BackgroundColor="Transparent"
                    Stroke="Transparent"
                    StrokeShape="RoundRectangle 8"
                    StrokeThickness="0">
                    <ContentPresenter HorizontalOptions="Center" VerticalOptions="Center">
                        <ContentPresenter.Resources>
                            <ResourceDictionary>
                                <Style TargetType="Label">
                                    <Setter Property="TextColor" Value="{Binding Stroke.Color, x:DataType=Border, Source={RelativeSource AncestorType={x:Type Border}}}" />
                                </Style>
                            </ResourceDictionary>
                        </ContentPresenter.Resources>
                    </ContentPresenter>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CheckedStates">
                            <VisualState x:Name="Checked">
                                <VisualState.Setters>
                                    <Setter Property="BackgroundColor" Value="#1CB41C" />
                                    <Setter Property="Stroke" Value="White" />
                                </VisualState.Setters>
                            </VisualState>
                            <VisualState x:Name="Unchecked">
                                <VisualState.Setters>
                                    <Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
                                    <Setter Property="Stroke" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
                                </VisualState.Setters>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
Sign up to request clarification or add additional context in comments.

Comments

0

Stephen’s answer ▲ does a great job using a Style, as the OP requested. To build on that, this answer drills a little deeper into an alternative approach: a custom control for better reusability, maintainability, and extensibility across projects. Since the OP wants a control that "has no checkmark or other icon" (essentially a Button), what if we create OneHotButton and give it a property that binds in XAML markup—e.g., GroupName, whose members behave one-hot, just like a RadioButton group? While we're at it, we can add bindable properties for SelectedTextColor, SelectedBackgroundColor, UnselectedTextColor, and UnselectedBackgroundColor, making it easier to customize the appearance without modifying multiple styles.

<local:OneHotButton Text="Apple" SelectedTextColor="Salmon"  GroupName="OptionsGroup"  IsChecked="true"/>

and

<Style TargetType="local:OneHotButton" >
    <Setter Property="SelectedBackgroundColor" Value="CornflowerBlue"/>
</Style>

Example One Hot Implementation

[DebuggerDisplay("{Text}")]
public partial class OneHotButton : Button
{
    public OneHotButton()
    {
        Clicked += OnButtonClicked;
        UpdateColors();
    }
    private static readonly Dictionary<string, List<OneHotButton>> buttonGroups = new();

    public static readonly BindableProperty GroupNameProperty =
        BindableProperty.Create(
            nameof(GroupName),
            typeof(string),
            typeof(OneHotButton),
            default(string),
            propertyChanged: (bindable, oldValue, newValue) =>
            {
                if (bindable is OneHotButton button)
                {
                    if (oldValue is string oldGroup)
                    {
                        buttonGroups[oldGroup].Remove(button);
                        if (buttonGroups[oldGroup].Count == 0)
                            buttonGroups.Remove(oldGroup);

                    }
                    if (newValue is string newGroup && !string.IsNullOrWhiteSpace(newGroup))
                    {
                        if (!buttonGroups.ContainsKey(newGroup))
                            buttonGroups[newGroup] = new List<OneHotButton>();
                        buttonGroups[newGroup].Add(button);
                    }
                }
            });

    public static readonly BindableProperty IsCheckedProperty =
        BindableProperty.Create(
            nameof(IsChecked),
            typeof(bool),
            typeof(OneHotButton),
            false,
            propertyChanged: (bindable, oldValue, newValue) =>
            {
                if (bindable is OneHotButton button &&
                    Equals(newValue, true) &&
                    !string.IsNullOrWhiteSpace(button.GroupName) &&
                    buttonGroups.TryGetValue(button.GroupName, out var buttonList))
                {
                    button.UpdateColors();
                    buttonList
                    .Where(_ => !ReferenceEquals(_, button))
                    .ToList()
                    .ForEach(_ =>
                    {
                        _.IsChecked = false;
                        _.UpdateColors();
                    });
                }
            });
    ...
}

Example Bindable Color

This snippet shows how to expose a property to be used in XAML markup.

public Color SelectedTextColor
{
    get => (Color)GetValue(SelectedTextColorProperty);
    set => SetValue(SelectedTextColorProperty, value);
}

public static readonly BindableProperty SelectedBackgroundColorProperty =
    BindableProperty.Create(
        propertyName: nameof(OneHotButton.SelectedBackgroundColor),
        returnType: typeof(Color),
        declaringType: typeof(OneHotButton),
        defaultValue: Colors.CornflowerBlue,
        defaultBindingMode: BindingMode.OneWay,
        propertyChanged: (bindable, oldValue, newValue) =>
        {
            if (bindable is OneHotButton @this)
            {
                @this.UpdateColors();
            }
        });

private void UpdateColors()
{
    if(IsChecked)
    {
        TextColor = SelectedTextColor;
        BackgroundColor = SelectedBackgroundColor;
    }
    else
    {
        TextColor = UnselectedTextColor;
        BackgroundColor = UnselectedBackgroundColor;
    }
}
... Other bindable colors

XAML Example

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:style_radio_group"
             x:Class="style_radio_group.MainPage">

    <ContentPage.Resources>
        <Style TargetType="local:OneHotButton" >
            <Setter Property="SelectedBackgroundColor" Value="CornflowerBlue"/>
            <Setter Property="Margin" Value="25,0"/>
        </Style>
    </ContentPage.Resources>
    <ScrollView>
        <VerticalStackLayout Padding="30,0" Spacing="25">
            <Image 
                Source="dotnet_bot.png"
                HeightRequest="185"
                Aspect="AspectFit"
                SemanticProperties.Description="dot net bot in a race car number eight" />
            <Border Padding="0,0,0,10" StrokeShape="RoundRectangle 10">
                <VerticalStackLayout Spacing="10">
                    <Label
                        Text="Choose One Fruit"
                        Padding="5"
                        BackgroundColor="LightGray"
                        FontSize="16"
                        FontAttributes="Bold"
                        HorizontalOptions="Fill"/>
                    <local:OneHotButton 
                        Text="Apple"
                        SelectedTextColor="Salmon"     
                        GroupName="OptionsGroup"
                        IsChecked="true"/>
                    <local:OneHotButton    
                        Text="Banana"
                        SelectedTextColor="Yellow"
                        GroupName="OptionsGroup"/>
                    <local:OneHotButton   
                        Text="Grape"
                        SelectedTextColor="LightGreen"
                        GroupName="OptionsGroup"/>
                </VerticalStackLayout>
            </Border>
        </VerticalStackLayout>
    </ScrollView>
</ContentPage>

net9.0 on android

1 Comment

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.