DEV Community

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

Posted on

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>

                    <VisualState x:Name="Unchecked">
                        <VisualState.Setters>
                            <Setter
                                TargetName="TextLabel"
                                Property="Label.TextColor"
                                Value="White"/>
                            <Setter
                                TargetName="Indicator"
                                Property="BoxView.Color"
                                Value="Transparent"/>
                        </VisualState.Setters>
                    </VisualState>
                </VisualStateGroup>
            </VisualStateGroupList>
        </VisualStateManager.VisualStateGroups>
        <Label Text="{TemplateBinding Content}" x:Name="TextLabel" />
        <BoxView x:Name="Indicator" Grid.Row="1" Color="Transparent"/>
    </Grid>
</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.

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