Get Started with DevExpress Tabs for .NET MAUI. How to Specify Tabs in View Model
- 6 minutes to read
This topic explains how to use the TabView component with tab items generated from a data source to create a tab bar that allows users to filter lists:
Create a New .NET MAUI Application and Add a Tab View
Create a new .NET MAUI solution.
Refer to Microsoft .NET Multi-platform App UI documentation for more information on how to get started with .NET MAUI.
Remove default content from the main page (MainPage.xaml) and event handlers from the code-behind file (MainPage.xaml.cs). Clear also the application’s resource dictionary (App.xaml).
Install the DevExpress.Maui.Controls package from your DevExpress NuGet feed.
In the MauiProgram.cs file, call UseDevExpress and UseDevExpressControls methods to register handlers for the TabView class:
using Microsoft.Maui;
using Microsoft.Maui.Hosting;
using Microsoft.Maui.Controls.Hosting;
using DevExpress.Maui.Controls;
namespace TabView_GenerateItems {
public static class MauiProgram {
public static MauiApp CreateMauiApp() {
var builder = MauiApp.CreateBuilder();
builder
.UseDevExpress()
.UseDevExpressControls()
.UseMauiApp<App>()
.ConfigureFonts(fonts => {
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
return builder.Build();
}
}
}
In the MainPage.xaml file, use the dx prefix to declare the DevExpress.Maui namespace and add a TabView object to the main page:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:dx="http://schemas.devexpress.com/maui"
x:Class="TabView_GenerateItems.MainPage">
<dx:TabView/>
</ContentPage>
Note
DevExpress Navigation Controls for .NET MAUI support iOS and Android. The project should contain only these platforms.
Create Models and View Models
Add a CarModel class that specifies a data object in the application:
namespace TabView_GenerateItems {
public class CarModel {
public string BrandName { get; }
public string ModelName { get; }
public string FullName => $"{BrandName} {ModelName}";
public CarModel(string brand, string model) {
this.BrandName = brand;
this.ModelName = model;
}
}
}
Create a CarBrandViewModel class that defines content for the tab view: car make and corresponding models. This view model will be used to display brands as tabs in the header and matching models in the tab item’s content area:
using System;
using System.Linq;
using System.ComponentModel;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace TabView_GenerateItems {
public class CarBrandViewModel : INotifyPropertyChanged {
private bool isSelected = false;
public string BrandName { get; }
public IReadOnlyList<CarModel> CarModels { get; }
// This property is used to change the appearance of a tab depending on its state.
public bool IsSelected {
get { return isSelected; }
set {
if (value == isSelected) return;
isSelected = value;
RaisePropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public CarBrandViewModel(string brandName, IEnumerable<CarModel> carModels) {
if (String.IsNullOrEmpty(brandName)) {
this.BrandName = String.Empty;
}
else {
this.BrandName = brandName;
}
if (carModels == null) {
this.CarModels = new List<CarModel>();
}
else {
this.CarModels = carModels.ToList();
}
}
private void RaisePropertyChanged([CallerMemberName] string caller = "") {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler.Invoke(this, new PropertyChangedEventArgs(caller));
}
}
}
}
Create a MainViewModel class that defines content for the MainPage (models grouped by make/brand):
using System.Linq;
using System.ComponentModel;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace TabView_GenerateItems {
public class MainViewModel : INotifyPropertyChanged {
private const int UnselectedIndex = -1;
private static readonly IReadOnlyList<CarModel> allCarModels = new List<CarModel> {
new CarModel("Mercedes-Benz", "SL500 Roadster"),
new CarModel("Mercedes-Benz", "CLK55 AMG Cabriolet"),
new CarModel("Mercedes-Benz", "C230 Kompressor Sport Coupe"),
new CarModel("BMW", "530i"),
new CarModel("Rolls-Royce", "Corniche"),
new CarModel("Jaguar", "S-Type 3.0"),
new CarModel("Cadillac", "Seville"),
new CarModel("Cadillac", "DeVille"),
new CarModel("Lexus", "LS430"),
new CarModel("Lexus", "GS430"),
new CarModel("Ford", "Ranger FX-4"),
new CarModel("Dodge", "RAM 1500"),
new CarModel("GMC", "Siera Quadrasteer"),
new CarModel("Nissan", "Crew Cab SE"),
new CarModel("Toyota", "Tacoma S-Runner"),
};
public IReadOnlyList<CarBrandViewModel> CarModelsByBrand { get; }
int selectedIndex = UnselectedIndex;
public int SelectedIndex {
get => selectedIndex;
set {
if (selectedIndex == value) return;
if (selectedIndex != UnselectedIndex) {
CarModelsByBrand[selectedIndex].IsSelected = false;
}
selectedIndex = value;
if (selectedIndex != UnselectedIndex) {
CarModelsByBrand[selectedIndex].IsSelected = true;
}
RaisePropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public MainViewModel() {
List<CarBrandViewModel> carBrandViewModels = new List<CarBrandViewModel>();
carBrandViewModels.Add(new CarBrandViewModel("All", allCarModels));
IEnumerable<IGrouping<string, CarModel>> groupedCarModels =
allCarModels.GroupBy(v => v.BrandName);
foreach (IGrouping<string, CarModel> carModelGroup in groupedCarModels) {
carBrandViewModels.Add(new CarBrandViewModel(carModelGroup.Key, carModelGroup));
}
CarModelsByBrand = carBrandViewModels;
}
private void RaisePropertyChanged([CallerMemberName] string caller = "") {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler.Invoke(this, new PropertyChangedEventArgs(caller));
}
}
}
}
Bind the Tab View to View Models
In the MainPage.xaml file:
- Assign a MainViewModel instance to the ContentPage.BindingContext property.
- Bind the TabView’s ItemsSource property to the CarModelsByBrand property of the view model, and the SelectedItemIndex property to the view model’s SelectedIndex property.
- Use the ItemHeaderTemplate property to define the tab item header’s appearance, and the ItemTemplate property to specify a view displayed in the tab item’s content area.
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:dx="http://schemas.devexpress.com/maui"
xmlns:local="clr-namespace:TabView_GenerateItems"
x:Class="TabView_GenerateItems.MainPage">
<ContentPage.BindingContext>
<local:MainViewModel/>
</ContentPage.BindingContext>
<dx:TabView ItemsSource="{Binding CarModelsByBrand}"
SelectedItemIndex="{Binding SelectedIndex, Mode=TwoWay}">
<dx:TabView.ItemHeaderTemplate>
<Grid>
<Label Text="{Binding BrandName}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"/>
</Grid>
</dx:TabView.ItemHeaderTemplate>
<dx:TabView.ItemTemplate>
<DataTemplate>
<Grid>
<ListView ItemsSource="{Binding CarModels}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Padding="5" Text="{Binding FullName}"/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</DataTemplate>
</dx:TabView.ItemTemplate>
</dx:TabView>
</ContentPage>
Customize the Tab View
Configure the appearance of the TabView’s header panel and header items.
Specify the minimum and maximum sizes of items, the spacing between them, and item padding:
<dx:TabView ItemsSource="{Binding CarModelsByBrand}"
SelectedItemIndex="{Binding SelectedIndex, Mode=TwoWay}"
ItemHeaderMinWidth="90"
ItemHeaderMaxWidth="360"
HeaderPanelItemSpacing="8">
<dx:TabView.ItemHeaderTemplate>
<DataTemplate>
<Grid>
<Label Text="{Binding BrandName}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
Padding="5,0"/>
</Grid>
</DataTemplate>
</dx:TabView.ItemHeaderTemplate>
<!-- Other Tab View settings.-->
</dx:TabView>
Specify the header panel’s background, and assign a color to a header item depending on whether the tab is selected:
using System;
using System.Globalization;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
namespace TabView_GenerateItems {
public partial class MainPage : ContentPage {
public MainPage() {
InitializeComponent();
}
}
class IsSelectedToColorConverter : IValueConverter {
public Color DefaultColor { get; set; }
public Color SelectedColor { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
if (!(value is bool boolValue)) return DefaultColor;
return (boolValue) ? SelectedColor : DefaultColor;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
}
<ContentPage.Resources>
<ResourceDictionary>
<local:IsSelectedToColorConverter x:Key="isSelectedToColorConverter"
DefaultColor="Transparent"
SelectedColor="#40FFFFFF"/>
</ResourceDictionary>
</ContentPage.Resources>
<dx:TabView ItemsSource="{Binding CarModelsByBrand}"
SelectedItemIndex="{Binding SelectedIndex, Mode=TwoWay}"
ItemHeaderMinWidth="90"
ItemHeaderMaxWidth="360"
HeaderPanelItemSpacing="8"
HeaderPanelBackgroundColor="#1e88e5">
<dx:TabView.ItemHeaderTemplate>
<DataTemplate>
<Grid>
<BoxView BackgroundColor="{Binding IsSelected,
Converter={StaticResource isSelectedToColorConverter}}"/>
<Label HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
Text="{Binding BrandName}"
Padding="5,0"/>
</Grid>
</DataTemplate>
</dx:TabView.ItemHeaderTemplate>
<!-- Other Tab View settings.-->
</dx:TabView>
Configure also the header panel’s shadow, hide the selected item indicator, and specify the frame corner radius, margin and text color for header items:
<dx:TabView ItemsSource="{Binding CarModelsByBrand}"
SelectedItemIndex="{Binding SelectedIndex, Mode=TwoWay}"
ItemHeaderMinWidth="90"
ItemHeaderMaxWidth="360"
HeaderPanelItemSpacing="8"
HeaderPanelBackgroundColor="#1e88e5"
HeaderPanelShadowHeight="3"
HeaderPanelShadowRadius="3"
IsSelectedItemIndicatorVisible="False">
<dx:TabView.ItemHeaderTemplate>
<DataTemplate>
<Grid>
<BoxView BackgroundColor="{Binding IsSelected,
Converter={StaticResource isSelectedToColorConverter}}"
Margin="0,8,0,8"
CornerRadius="25"/>
<Label HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
Text="{Binding BrandName}"
Padding="5,0"
TextColor="White"/>
</Grid>
</DataTemplate>
</dx:TabView.ItemHeaderTemplate>
<!-- Other Tab View settings.-->
</dx:TabView>