DEV Community

Cover image for Making a TabBar or SegmentedControl in .NET MAUI
David Ortinau
David Ortinau

Posted on

28 2

Making a TabBar or SegmentedControl in .NET MAUI

This is the experience I just created for a little demo app I'm building with .NET MAUI. Component vendors like Syncfusion and Progress ship SegmentedControls that do this and probably more, but I wanted to see what it would be like to use "in the box" controls. I'm really happy with the result and how easy it was to code. This probably took me 10 minutes.

What I used:

The first two "controls" are really the heart of the solution. The rest are just needed for layout/design.

The Code

1 - I created my container with a set of data to be repeated for each tab.



<HorizontalStackLayout>
    <BindableLayout.ItemTemplate>
    <!-- Views Here -->
    </BindableLayout.ItemTemplate>
    <BindableLayout.ItemsSource>
        <x:Array Type="{x:Type x:String}">
            <x:String>Hot Dishes</x:String>
            <x:String>Cold Dishes</x:String>
            <x:String>Soups</x:String>
            <x:String>Appetizers</x:String>
            <x:String>Desserts</x:String>
        </x:Array>
    </BindableLayout.ItemsSource>
</HorizontalStackLayout>


Enter fullscreen mode Exit fullscreen mode

2 - I created the item template to display a RadioButton.



<BindableLayout.ItemTemplate>
    <DataTemplate>
        <RadioButton Content="{Binding .}" />
    </DataTemplate>
</BindableLayout.ItemTemplate>


Enter fullscreen mode Exit fullscreen mode

To make this a grouped list of radios, I added a name to the parent layout.



<HorizontalStackLayout 
    RadioButtonGroup.GroupName="MenuCategories">


Enter fullscreen mode Exit fullscreen mode

Image description

3 - I styled the RadioButton using a ControlTemplate.



<RadioButton Content="{Binding .}">
    <RadioButton.ControlTemplate>
        <ControlTemplate>
            <Grid RowDefinitions="30,4">
                <Label Text="{TemplateBinding Content}" />
                <BoxView Grid.Row="1" Color="Transparent"/>
            </Grid>
        </ControlTemplate>
    </RadioButton.ControlTemplate>
</RadioButton>


Enter fullscreen mode Exit fullscreen mode

Because we are inside of a control template, rather than using Binding I use TemplateBinding. The Content could be anything, but I supplied a String so it seems safe to bind that directly to the label text.

4 - To get the different look for selected vs unselected, I:

  • added a VisualStateManager (VSM) to the control template layout
  • gave my lable and box names so I could target them from the VSM
  • styled the checked and unchecked states (odd naming ¯_(ツ)_/¯)

Image description



<ControlTemplate>
<Grid RowDefinitions="30,4">
<VisualStateManager.VisualStateGroups>
<VisualStateGroupList>
<VisualStateGroup x:Name="CheckedStates">
<VisualState x:Name="Checked">
<VisualState.Setters>
<Setter
TargetName="TextLabel"
Property="Label.TextColor"
Value="{StaticResource Primary}"/>
<Setter
TargetName="Indicator"
Property="BoxView.Color"
Value="{StaticResource Primary}"/>
</VisualState.Setters>
</VisualState>

                &lt;VisualState x:Name="Unchecked"&gt;
                    &lt;VisualState.Setters&gt;
                        &lt;Setter
                            TargetName="TextLabel"
                            Property="Label.TextColor"
                            Value="White"/&gt;
                        &lt;Setter
                            TargetName="Indicator"
                            Property="BoxView.Color"
                            Value="Transparent"/&gt;
                    &lt;/VisualState.Setters&gt;
                &lt;/VisualState&gt;
            &lt;/VisualStateGroup&gt;
        &lt;/VisualStateGroupList&gt;
    &lt;/VisualStateManager.VisualStateGroups&gt;
    &lt;Label Text="{TemplateBinding Content}" x:Name="TextLabel" /&gt;
    &lt;BoxView x:Name="Indicator" Grid.Row="1" Color="Transparent"/&gt;
&lt;/Grid&gt;
Enter fullscreen mode Exit fullscreen mode

</ControlTemplate>

Enter fullscreen mode Exit fullscreen mode




Conclusion

That's it. There are a lot of useful concepts in this example that made implementing it super easy. I hope learned at least one new thing to use in .NET MAUI.

Jetbrains image

Don’t Become a Data Breach Headline

57% of organizations have suffered from a security incident related to DevOps toolchain exposures. Is your CI/CD protected? Check out these nine practical tips to keep your CI/CD secure—without adding friction.

Learn more

Top comments (2)

Collapse
 
gpproton profile image
radioActive DROID • Edited

I've been trying to implement something like this in C# with no luck, any suggestion will be appreciated.

public class SegmentView : Border {
    readonly string[] items = { "Pending", "Confirmed", "Completed" };

    class SegmentButton : Button {
        public SegmentButton() => this.Margins(0, 0, 8, 0);
    }

    public SegmentView() {
        Padding = 3;
        Margin = 8;
        StrokeShape = new RoundRectangle {
            CornerRadius = new CornerRadius(10)
        };
        SetDynamicResource(BackgroundColorProperty, "Tertiary");
        var layout = new HorizontalStackLayout()
            .ItemsSource(items)
            .ItemTemplate(new DataTemplate(() => new RadioButton {
                ControlTemplate = new ControlTemplate(() => new SegmentButton()
                    .Bind(Button.TextProperty, "Content", source: RelativeBindingSource.TemplatedParent))
            }.Bind(RadioButton.ContentProperty)));
        RadioButtonGroup.SetGroupName(layout, "MenuGroup");
        var vsGroups = new VisualStateGroup() {
            Name = "CheckedStates",
        };
        vsGroups.States.Add(new VisualState {
            Name = "Checked",
            Setters = {
                new Setter { TargetName = nameof(SegmentButton), Property = Button.BackgroundColorProperty, Value = Colors.Indigo }
            }
        });
        vsGroups.States.Add(new VisualState {
            Name = "Unchecked",
            Setters = {
                new Setter { TargetName = nameof(SegmentButton), Property = Button.BackgroundColorProperty, Value = Colors.Transparent },
                new Setter { TargetName = nameof(SegmentButton), Property = Button.TextColorProperty, Value = Colors.Indigo }
            }
        });
        VisualStateManager.SetVisualStateGroups(layout,
                new VisualStateGroupList {

                }
            );
        Content = layout;
    }
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
sikandaramla profile image
Sikandar Amla

Simple, clean, elegant

ACI image

ACI.dev: The Only MCP Server Your AI Agents Need

ACI.dev’s open-source tool-use platform and Unified MCP Server turns 600+ functions into two simple MCP tools on one server—search and execute. Comes with multi-tenant auth and natural-language permission scopes. 100% open-source under Apache 2.0.

Star our GitHub!

👋 Kindness is contagious

Dive into this thoughtful article, cherished within the supportive DEV Community. Coders of every background are encouraged to share and grow our collective expertise.

A genuine "thank you" can brighten someone’s day—drop your appreciation in the comments below!

On DEV, sharing knowledge smooths our journey and strengthens our community bonds. Found value here? A quick thank you to the author makes a big difference.

Okay