Skip to content

Advanced Features

Enable reorder controls on a section by setting UseDragSort="True". Each cell gets up/down arrow buttons to move it within the section.

<tv:TableView ItemDroppedCommand="{Binding ItemDroppedCommand}">
<tv:TableRoot>
<tv:TableSection Title="Drag to Reorder" UseDragSort="True">
<tv:LabelCell Title="First" ValueText="1" />
<tv:LabelCell Title="Second" ValueText="2" />
<tv:LabelCell Title="Third" ValueText="3" />
</tv:TableSection>
</tv:TableRoot>
</tv:TableView>

The ItemDroppedCommand receives an ItemDroppedEventArgs with:

PropertyTypeDescription
SectionTvTableSectionThe section containing the cell
CellCellBaseThe cell that was moved
FromIndexintOriginal position
ToIndexintNew position

You can also use the event-based approach:

tableView.ItemDropped += (sender, args) =>
{
Console.WriteLine($"Moved from {args.FromIndex} to {args.ToIndex}");
};

Trigger scrolling from your view model using bindable properties:

<tv:TableView ScrollToTop="{Binding ShouldScrollTop}"
ScrollToBottom="{Binding ShouldScrollBottom}" />

Set the bound property to true to trigger the scroll. The control resets it to false after scrolling.

Use the async methods for programmatic scroll control:

// Scroll to top with animation
await tableView.ScrollToTopAsync(animated: true);
// Scroll to bottom with animation
await tableView.ScrollToBottomAsync(animated: true);

Fires when the structure of the TableView changes (sections added/removed, cells added/removed):

tableView.ModelChanged += (sender, args) =>
{
Console.WriteLine("TableView structure changed");
};

Fires when a property changes on any cell within the TableView:

tableView.CellPropertyChanged += (sender, args) =>
{
Console.WriteLine($"Cell property changed: {args.PropertyName}");
};

Here is a comprehensive example showing multiple cell types, sections, styling, and MVVM bindings:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:tv="http://shiny.net/maui/tableview"
x:Class="MyApp.SettingsPage"
Title="Settings">
<tv:TableView CellTitleFontSize="16"
CellValueTextColor="#007AFF"
CellSelectedColor="#E8E8ED"
CellAccentColor="#34C759"
HeaderFontSize="13"
HeaderTextColor="#6D6D72"
FooterTextColor="#8E8E93">
<tv:TableRoot>
<!-- Account Section -->
<tv:TableSection Title="ACCOUNT">
<tv:LabelCell Title="Name"
ValueText="{Binding UserName}"
IconSource="person.png" />
<tv:LabelCell Title="Email"
ValueText="{Binding UserEmail}"
IconSource="email.png" />
<tv:CommandCell Title="Edit Profile"
Command="{Binding EditProfileCommand}"
ShowArrow="True"
IconSource="edit.png" />
</tv:TableSection>
<!-- Preferences Section -->
<tv:TableSection Title="PREFERENCES"
FooterText="Notifications may use cellular data">
<tv:SwitchCell Title="Push Notifications"
On="{Binding PushEnabled, Mode=TwoWay}"
IconSource="bell.png" />
<tv:SwitchCell Title="Email Alerts"
On="{Binding EmailAlerts, Mode=TwoWay}"
IconSource="mail.png" />
<tv:TextPickerCell Title="Language"
ItemsSource="{Binding Languages}"
SelectedItem="{Binding SelectedLanguage, Mode=TwoWay}"
IconSource="globe.png" />
</tv:TableSection>
<!-- Appearance Section -->
<tv:TableSection Title="APPEARANCE"
tv:RadioCell.SelectedValue="{Binding Theme, Mode=TwoWay}">
<tv:RadioCell Title="Light" Value="Light" />
<tv:RadioCell Title="Dark" Value="Dark" />
<tv:RadioCell Title="System" Value="System" />
</tv:TableSection>
<!-- About Section -->
<tv:TableSection Title="ABOUT">
<tv:LabelCell Title="Version" ValueText="1.0.0" />
<tv:CommandCell Title="Privacy Policy"
Command="{Binding PrivacyCommand}"
ShowArrow="True" />
<tv:CommandCell Title="Terms of Service"
Command="{Binding TermsCommand}"
ShowArrow="True" />
</tv:TableSection>
<!-- Actions -->
<tv:TableSection>
<tv:ButtonCell Title="Sign Out"
Command="{Binding SignOutCommand}"
ButtonTextColor="Red" />
</tv:TableSection>
</tv:TableRoot>
</tv:TableView>
</ContentPage>
public class SettingsViewModel : INotifyPropertyChanged
{
private bool _pushEnabled = true;
private bool _emailAlerts;
private string _theme = "System";
private object? _selectedLanguage;
public string UserName => "Jane Doe";
public string UserEmail => "jane@example.com";
public bool PushEnabled
{
get => _pushEnabled;
set => SetProperty(ref _pushEnabled, value);
}
public bool EmailAlerts
{
get => _emailAlerts;
set => SetProperty(ref _emailAlerts, value);
}
public string Theme
{
get => _theme;
set => SetProperty(ref _theme, value);
}
public List<string> Languages { get; } = ["English", "Spanish", "French", "German"];
public object? SelectedLanguage
{
get => _selectedLanguage;
set => SetProperty(ref _selectedLanguage, value);
}
public ICommand EditProfileCommand { get; }
public ICommand PrivacyCommand { get; }
public ICommand TermsCommand { get; }
public ICommand SignOutCommand { get; }
public SettingsViewModel()
{
EditProfileCommand = new Command(async () =>
await Shell.Current.GoToAsync("editprofile"));
PrivacyCommand = new Command(async () =>
await Launcher.OpenAsync("https://example.com/privacy"));
TermsCommand = new Command(async () =>
await Launcher.OpenAsync("https://example.com/terms"));
SignOutCommand = new Command(async () =>
{
bool confirm = await Application.Current!.Windows[0].Page!
.DisplayAlert("Sign Out", "Are you sure?", "Sign Out", "Cancel");
if (confirm) { /* sign out logic */ }
});
}
public event PropertyChangedEventHandler? PropertyChanged;
private bool SetProperty<T>(ref T field, T value,
[System.Runtime.CompilerServices.CallerMemberName] string? name = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
return true;
}
}