TL;DR: Discover how to add a context menu to the .NET MAUI ListView for quick actions like copy, delete, and mark as favorite. Using the .NET MAUI Popup control, we’ll display the menu on long-press or right-tap gestures. This blog covers defining menu actions, handling user interactions, and seamlessly integrating the popup with ListView. Elevate your app’s usability with an intuitive and responsive context menu!
In mobile and desktop applications, users expect interactive and responsive interfaces. A well-designed context menu in your list view can help users engage more intuitively with content by adding functionality like quick actions for deleting or marking items as favorites and providing undo options.
In this blog, we’ll create a context menu in the Syncfusion .NET MAUI ListView control and learn to display it on long-press or right-tap actions. This implementation is ideal for email clients, to-do lists, or other apps where users frequently interact with a list of items.
The .NET MAUI ListView control presents lists of data virtually in a vertical or horizontal orientation with different layouts. It supports essential features, such as selection, template selectors, horizontal and vertical orientation, load more, autofitting items, and more. The control also supports sorting, grouping, and filtering with optimization for working with large amounts of data.
Implementing a context menu in your .NET MAUI app is straightforward with the ListView control, which adds a layer of interactivity that makes your UI feel responsive and user-friendly.
This blog includes the following sections:
Let’s get started!
First, create a context menu using the .NET MAUI Popup control. This context menu will appear when an item is long-pressed or right-tapped.
Note: Before proceeding, refer to the getting started with .NET MAUI Popup control documentation.
Create a class to define the actions that will populate in the context menu. Each action will have a name and an associated icon. Refer to the following code example.
public class ContextMenuAction { public ContextMenuAction() { } public string ActionName { get; set; } // Holds the action name public string ActionIcon { get; set; } // Holds the picture for the action }
In the ViewModel, create an ObservableCollection to store the context menu actions. This collection will be populated with a few quick actions.
public class ViewModel : INotifyPropertyChanged { public ObservableCollection<ContextMenuAction> ContextMenuActions { get; set; } public ViewModel() { GenerateContextMenuActions(); } private void GenerateContextMenuActions() { ContextMenuActions = new ObservableCollection<ContextMenuAction>(); ContextMenuActions.Add(new ContextMenuAction() { ActionName = "Copy", ActionIcon = "\uE737" }); ContextMenuActions.Add(new ContextMenuAction() { ActionName = "Delete" , ActionIcon= "\uE73C" }); ContextMenuActions.Add(new ContextMenuAction() { ActionName = "Archive", ActionIcon = "\uE777" }); ContextMenuActions.Add(new ContextMenuAction() { ActionName = "Mark as Read/Unread", ActionIcon= "\uE75C" }); ContextMenuActions.Add(new ContextMenuAction() { ActionName = "Mark as Favorite", ActionIcon= "\uE73A" }); } }
Now that the context menu is defined and populated, the next step is to manage user interactions when they click on the quick actions. Here, we’ll implement the logic to handle the various actions within the ViewModel.
public class ViewModel : INotifyPropertyChanged { private async void OnContextMenuClicked(object eventArgs) { var eventData = eventArgs as Syncfusion.Maui.ListView.ItemTappedEventArgs; if (eventData == null) return; var action = (eventData.DataItem as ContextMenuAction).ActionName; switch(action) { case "Copy": OnExcuteCopyCommand(); break; case "Delete": OnDelete(listViewItem); break; case "Archive": OnArchive(listViewItem); break; case "Mark as Read/Unread": listViewItem.IsOpened = !listViewItem.IsOpened; break; case "Mark as Favorite": OnSetFavorites(listViewItem); break; } ContextMenuPopup.IsOpen = false; } private void OnExcuteCopyCommand() { string text = "From" + ":" + listViewItem.ProfileName + "\n" + "Subject" + ":" + listViewItem.Subject + "\n" + "Received" + ":" + listViewItem.Date; text = Regex.Replace(text, "<.*?>| ", string.Empty); Clipboard.SetTextAsync(text); } private async void OnArchive(object item) { PopUpText = "Archived"; listViewItem = (ListViewInboxInfo)item; listViewItemIndex = inboxInfo!.IndexOf(listViewItem); inboxInfo!.Remove(listViewItem); archivedMessages!.Add(listViewItem); this.IsDeleted = true; await Task.Delay(3000); this.IsDeleted = false; } private void OnUndoAction() { this.IsDeleted = false; if (listViewItem != null) { inboxInfo!.Insert(listViewItemIndex, listViewItem); } listViewItemIndex = 0; listViewItem = null; } private void OnSetFavorites(object item) { var listViewItem = item as ListViewInboxInfo; if ((bool)listViewItem!.IsFavorite) { listViewItem.IsFavorite = false; } else { listViewItem.IsFavorite = true; } } private async void OnDelete(object item) { PopUpText = "Deleted"; listViewItemIndex = inboxInfo!.IndexOf(listViewItem); inboxInfo!.Remove(listViewItem); this.IsDeleted = true; await Task.Delay(3000); this.IsDeleted = false; } }
Then, define the popup in the ContentPage.Resources section for easy reuse. Refer to the following code example.
<popup:SfPopup x:Name="contextMenuPopup" x:Key="contextMenu" ShowHeader="False" WidthRequest="250" HeightRequest="225"> <popup:SfPopup.ContentTemplate> <DataTemplate> <syncfusion:SfListView Margin="10" ItemSize="40" ItemsSource="{Binding ContextMenuActions}" TapCommand="{Binding ContextMenuCommand}"> <syncfusion:SfListView.ItemTemplate> <DataTemplate> <Label Grid.Column="0" Margin="5" Text="{Binding ActionIcon}" FontSize="18" FontAttributes="Bold" FontFamily="MauiSampleFontIcon" VerticalOptions="Center" VerticalTextAlignment="Center"/> <Label x:Name="label" Grid.Column="1" VerticalTextAlignment="Center" Text="{Binding ActionName}" FontSize="16"/> </DataTemplate> </syncfusion:SfListView.ItemTemplate> </syncfusion:SfListView> </DataTemplate> </popup:SfPopup.ContentTemplate> </popup:SfPopup>
In the above code example, we’ve shown the action items in the .NET MAUI ListView and enabled the actions by tapping them.
Let’s create a responsive inbox item list view showcasing essential features such as data binding, item selection, and interaction commands.
Create a class to define the inbox information that will be displayed in the .NET MAUI ListView.
public class ListViewInboxInfo : INotifyPropertyChanged { #region Fields private string? profileName; private string? subject; private string? desc; private string? date; private string? image; private bool? isAttached; private bool isOpened; private bool isFavorite = true; #endregion #region Constructor public ListViewInboxInfo() { } #endregion #region Properties public string? ProfileName { get { return profileName; } set { profileName = value; OnPropertyChanged("Title"); } } public string? Subject { get { return subject; } set { subject = value; OnPropertyChanged("Subject"); } } public string? Description { get { return desc; } set { desc = value; OnPropertyChanged("Description"); } } public string? Date { get { return date; } set { date = value; OnPropertyChanged("Date"); } } public string? Image { get { return image; } set { image = value; OnPropertyChanged("Image"); } } public bool? IsAttached { get { return isAttached; } set { isAttached = value; OnPropertyChanged("IsAttached"); } } public bool IsFavorite { get { return isFavorite; } set { isFavorite = value; OnPropertyChanged("IsFavorite"); } } public bool IsOpened { get { return isOpened; } set { isOpened = value; OnPropertyChanged("IsOpened"); } } #endregion #region Interface Member public event PropertyChangedEventHandler? PropertyChanged; public void OnPropertyChanged(string name) { if (this.PropertyChanged != null) this.PropertyChanged(this, new PropertyChangedEventArgs(name)); } #endregion }
Then, create a ViewModel class to:
public class ViewModel : INotifyPropertyChanged { private ObservableCollection<ListViewInboxInfo>? inboxInfo; #region Constructor public ViewModel() { GenerateSource(); } #endregion public ObservableCollection<ListViewInboxInfo>? InboxInfo { get { return inboxInfo; } set { this.inboxInfo = value; OnPropertyChanged("InboxInfo"); } } #region Generate Source private void GenerateSource() { ListViewInboxInfoRepository inboxinfo = new ListViewInboxInfoRepository(); inboxInfo = inboxinfo.GetInboxInfo(); } }
Now, create the layout using SfListView to display the inbox items. Refer to the following code example to implement the .NET MAUI ListView to showcase how to bind the data and define the item template.
<syncfusion:SfListView Grid.Row="1" x:Name="listView" ItemsSource="{Binding InboxInfo}" SelectionMode="Single" AutoFitMode="Height"> <syncfusion:SfListView.ItemTemplate> <DataTemplate> --------------------------- <Grid Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" HeightRequest="40" WidthRequest="40" HorizontalOptions="Center" VerticalOptions="Center"> <Image Source="{Binding Image}" HeightRequest="40" WidthRequest="40" /> <Label Text="{Binding ProfileName}" FontSize="16" HorizontalTextAlignment="Center" HorizontalOptions="Center" VerticalOptions="Center" VerticalTextAlignment="Center" FontFamily="Roboto-Regular" CharacterSpacing="0.25"/> --------------------------- </Grid> </DataTemplate> </syncfusion:SfListView.ItemTemplate> </syncfusion:SfListView>
In the above code example, the ViewModel properties are bound to the SfListView. Now, run the project, and you will get the following output.
To show the context menu on user gestures, define the LongPressCommand and RightTappedCommand properties in the ViewModel. These commands are bound to gesture events in the SfListView and will display the context menu at the touchpoint when triggered.
public class ViewModel : INotifyPropertyChanged { private Command<object> longPressCommand; private Command<object> rightTappedCommand; public ViewModel() { DefineCommands(); } public Command<object> LongPressCommand { get { return longPressCommand; } set { longPressCommand = value; } } public Command<object> RightTappedCommand { get { return rightTappedCommand; } set { rightTappedCommand = value; } } private void DefineCommands() { undoCommand = new Command(OnUndoAction); favoritesImageCommand = new Command(OnSetFavorites); LongPressCommand = new Command<object>(OnLongPress); rightTappedCommand = new Command<object>(OnRightTap); contextMenuCommand = new Command<object>(OnContextMenuClicked); } private async void OnLongPress(object eventArgs) { var eventData = eventArgs as Syncfusion.Maui.ListView.ItemLongPressEventArgs; await Task.Delay(200); ContextMenuActions = null; GenerateContextMenuActions(); listViewItem = (ListViewInboxInfo)eventData.DataItem; ContextMenuPopup.Show((int)eventData.Position.X, (int)eventData.Position.Y); } private async void OnRightTap(object eventArgs) { var eventData = eventArgs as Syncfusion.Maui.ListView.ItemRightTappedEventArgs; await Task.Delay(200); ContextMenuActions = null; GenerateContextMenuActions(); listViewItem = (ListViewInboxInfo)eventData.DataItem; ContextMenuPopup.Show((int)eventData.Position.X, (int)eventData.Position.Y); } #endregion }
Finally, bind the commands to the SfListView.
<syncfusion:SfListView Grid.Row="1" x:Name="listView" ItemsSource="{Binding InboxInfo}" SelectionMode="Single" LongPressCommand="{Binding LongPressCommand}" RightTapCommand="{Binding RightTappedCommand}" AutoFitMode="Height"> </syncfusion:SfListView>
Executing the above code example will give us the context menu when right-clicking or long-pressing the list view items.
For more details, refer to the adding context menu to the .NET MAUI ListView GitHub demo.
Thanks for reading the blog post! Implementing context menus in .NET MAUI ListView is crucial for user-friendly interactions. Enhance your app’s interactivity by integrating quick actions like deleting or marking items as favorites in context menus.
If you’re an existing Syncfusion user, you can download the latest version of Essential Studio® from the License and Downloads page. For those new to Syncfusion, we offer a 30-day free trial so you can experience these exciting features firsthand.
Need help? Feel free to reach out via our support forum, support portal, or feedback portal. We’re always here to assist you!