Table of Contents

Context Menus

Data Grid supports built-in context menus for specific elements. This topic shows how to replace and modify these menus, and display custom popup menus for the Data Grid's elements that do not have built-in context menus.

Built-in Column Header Context Menu

A right-click on a column header displays the built-in column header menu. The menu contains commands to sort and group data, show the search panel, and display the column chooser.

datagrid-columnheadermenu1

The DataGridControl.ColumnMenu property allows you to access and customize this menu.

To replace the default menu, assign a Eremex.AvaloniaUI.Controls.Bars.PopupMenu object to the DataGridControl.ColumnMenu property.

To customize the existing column header menu (add new items, or remove default items), access the menu after it has been initialized (for instance, within your DataGrid's Initialized event handler), and then modify the menu.

Data Context

The DataContext property of the column header menu and its items (ToolbarButtonItem objects) specifies the GridColumn object for which the menu has been invoked.

Example - How to replace the default column header menu

The following code creates a custom column header menu that contains the Copy Column menu item. The code binds the menu item to the CopyColumnDataCommand command defined in a ViewModel.

datagrid-contextmenus-columnmenu-replace-example

Take note of the initialization of the Command and CommandParameter properties for the menu item in the code below. The expression CommandParameter="{Binding FieldName}" specifies binding to the FieldName property of the menu item's DataContext (GridColumn.FieldName).

A GridColumn's DataContext matches the Data Grid's DataContext (a ViewModel object in this example). This allows you to access the View Model and its CopyColumnCommand command using the expression: Command="{Binding DataContext.CopyColumnCommand}".

xmlns:mxdg="https://schemas.eremexcontrols.net/avalonia/datagrid"
xmlns:mxe="https://schemas.eremexcontrols.net/avalonia/editors"
xmlns:mxb="https://schemas.eremexcontrols.net/avalonia/bars"

<mxdg:DataGridControl Name="dataGrid1">
    <mxdg:DataGridControl.ColumnMenu>
        <mxb:PopupMenu>
            <mxb:ToolbarButtonItem
                Header="Copy Column"
                Command="{Binding DataContext.CopyColumnCommand}"
                CommandParameter="{Binding FieldName}">
            </mxb:ToolbarButtonItem>
        </mxb:PopupMenu>
    </mxdg:DataGridControl.ColumnMenu>
</mxdg:DataGridControl>
public MainView()
{
    DataContext = new ViewModel();
}

public partial class ViewModel : ObservableObject
{
    [RelayCommand]
    void CopyColumn(string fieldName)
    {
        //...
    }
}

Example - How to modify the existing column header menu

The following example adds a custom command to the default column header menu.

The example handles the DataGridControl.Initialized event to access the default column header menu after it has been initialized, and adds a Refresh Data command to the menu.

datagrid-contextmenus-columnmenu-customize-example

xmlns:mxdg="https://schemas.eremexcontrols.net/avalonia/datagrid"
xmlns:mxe="https://schemas.eremexcontrols.net/avalonia/editors"
xmlns:mxb="https://schemas.eremexcontrols.net/avalonia/bars"

<mxdg:DataGridControl Name="dataGrid1" Initialized="OnInitialized">
    ...
</mxdg:DataGridControl>
using Eremex.AvaloniaUI.Controls.Bars;

private void OnInitialized(object sender, System.EventArgs e)
{
    ToolbarButtonItem btn1 = new ToolbarButtonItem();
    btn1.Header = "Refresh Data";
    btn1.ShowSeparator = true;
    btn1.Command = new RelayCommand<DataGridControl>(UpdateDataGrid);
    btn1.CommandParameter = dataGrid1;
    dataGrid1.ColumnMenu.Items.Add(btn1);
}

[RelayCommand]
void UpdateDataGrid(DataGridControl dataGrid)
{
    //...
}

Row Cell Menu

Data Grid supports a built-in context menu for row cells (see the DataGridControlBase.RowCellMenu property). This menu is initially empty, and therefore hidden. To show the row cell menu, populate it with items in XAML or in code-behind.

Data Context

The DataContext of the row cell menu and its items contains a Eremex.AvaloniaUI.Controls.DataControl.Visuals.CellData object, which allows you to access context specific information:

  • CellData.DataControl — Returns the container control (DataGridControl) for which the menu is invoked.
  • CellData.Row — Returns the clicked row's underlying data object.

Example - How to show the same context menu commands for all rows

The following example adds the "Copy Row" command to the row cell menu (DataGridControlBase.RowCellMenu) for all rows.

datagrid-contextmenus-rowmenu-copyrow-example

The XAML code below assigns a popup menu to the DataGridControl.RowCellMenu property. The popup menu contains a single item (ToolbarButtonItem) bound to the CopyRowCommand command defined in a View Model (a Data Grid's DataContext).

xmlns:mxdg="https://schemas.eremexcontrols.net/avalonia/datagrid"
xmlns:mxe="https://schemas.eremexcontrols.net/avalonia/editors"
xmlns:mxb="https://schemas.eremexcontrols.net/avalonia/bars"

<mxdg:DataGridControl Name="dataGrid1">
    <mxdg:DataGridControl.RowCellMenu>
        <mxb:PopupMenu>
            <mxb:PopupMenu.Items>
                <mxb:ToolbarButtonItem Header="Copy Row"
                 Command="{Binding DataControl.DataContext.CopyRowCommand}"
                 CommandParameter="{Binding Row}"/>
            </mxb:PopupMenu.Items>
        </mxb:PopupMenu>
    </mxdg:DataGridControl.RowCellMenu>
</mxdg:DataGridControl>
public partial class MainView : UserControl
{
    ViewModel viewModel = new ViewModel();
    public MainView()
    {
        DataContext = viewModel;
        InitializeComponent();
    }
}

public partial class ViewModel : ObservableObject
{
    public ViewModel() { }
    [RelayCommand]
    void CopyRow(object row)
    {
        //...
    }
}

Example - How to show different context menu commands for different rows

A Data Grid control in this example displays a list of Employee objects. The example initializes the context menu for rows (DataGridControlBase.RowCellMenu), and displays different menu items for different data rows.

datagrid-contextmenus-rowcellmenu-different-example

The context menu displays the Show Working Hours menu item for rows that refer to part-time employees (the Employee.IsPartTime property returns true).

The context menu displays the Open Contract Details menu item for rows that refer to contractors (the Employee.IsContract property returns true).

The XAML code below adds two items ("Show Working Hours" and "Open Contract Details") to the context menu. Visibility of these items is managed dynamically by the business object's IsPartTime and IsContract properties.

xmlns:mxdg="https://schemas.eremexcontrols.net/avalonia/datagrid"
xmlns:mxb="https://schemas.eremexcontrols.net/avalonia/bars"

<mxdg:DataGridControl Name="dataGrid1" AutoGenerateColumns="True" ItemsSource="{Binding Employees}">
    <mxdg:DataGridControl.RowCellMenu>
        <mxb:PopupMenu>
            <mxb:PopupMenu.Items>
                <mxb:ToolbarButtonItem
                    Header="Show Working Hours"
                    Command="{Binding DataControl.DataContext.ShowWorkingHoursCommand}"
                    CommandParameter="{Binding Row}"
                    Glyph="{SvgImage 'avares://AvaloniaApplication3/Assets/time.svg'}"
                    GlyphSize="20,20"
                    IsVisible="{Binding Row.IsPartTime}">
                </mxb:ToolbarButtonItem>
                <mxb:ToolbarButtonItem
                    Header="Open Contract Details"
                    Command="{Binding DataControl.DataContext.OpenContractDetailsCommand}"
                    CommandParameter="{Binding Row}"
                    Glyph="{SvgImage 'avares://AvaloniaApplication3/Assets/details.svg'}"
                    GlyphSize="20,20"
                    IsVisible="{Binding Row.IsContract}"
                    >
                </mxb:ToolbarButtonItem>
            </mxb:PopupMenu.Items>
        </mxb:PopupMenu>
    </mxdg:DataGridControl.RowCellMenu>
</mxdg:DataGridControl>
public partial class MainView : UserControl
{
    MainViewModel viewModel = new MainViewModel();

    public MainView()
    {
        DataContext = viewModel;

        viewModel.Employees.Add(new Employee() { 
            Name = "Herbert McGill", EmploymentType=EmploymentType.FullTime, 
            Position= "Customer Service" 
        });
        viewModel.Employees.Add(new Employee() { 
            Name = "Cora Gilmore", EmploymentType = EmploymentType.PartTime, 
            Position = "Accountant" 
        });
        viewModel.Employees.Add(new Employee() { 
            Name = "Earl Case", EmploymentType = EmploymentType.PartTime, 
            Position = "Software Engineer" 
        });
        viewModel.Employees.Add(new Employee() { 
            Name = "Julius Howell", EmploymentType = EmploymentType.Contract, 
            Position = "Financial Analyst" 
        });
        viewModel.Employees.Add(new Employee() { 
            Name = "Trevor Cameron", EmploymentType = EmploymentType.Contract, 
            Position = "Accountant" 
        });
        viewModel.Employees.Add(new Employee() { 
            Name = "Lon Monroe", EmploymentType = EmploymentType.FullTime, 
            Position = "Management" 
        });

        InitializeComponent();
    }
}

public partial class MainViewModel : ViewModelBase
{
    public MainViewModel() { }

    public ObservableCollection<Employee> Employees { get; } = new();

    [RelayCommand]
    void OpenContractDetails(Employee emp)
    {
        if(emp.EmploymentType == EmploymentType.Contract)
        {
            //...
        }
    }

    [RelayCommand]
    void ShowWorkingHours(Employee emp)
    {
        if(emp.EmploymentType == EmploymentType.PartTime)
        {
            //...
        }
    }
}

public partial class Employee : ObservableObject
{
    [ObservableProperty]
    public string name = "";

    [ObservableProperty]
    public string position = "";

    [ObservableProperty]
    public EmploymentType employmentType;

    [Browsable(false)]
    public bool IsContract => EmploymentType == EmploymentType.Contract;

    [Browsable(false)]
    public bool IsPartTime => EmploymentType == EmploymentType.PartTime;
}

public enum EmploymentType
{
    [Display(Name = "Full Time")]
    FullTime,
    [Display(Name = "Part Time")]
    PartTime,
    Contract
}

Example - How to generate context menu commands from a ViewModel

This example shows how to populate a DataGrid control's row cell menu with items defined in a ViewModel.

datagrid-contextmenus-rowcellmenu-fromViewModel-example

The row cell menu (DataGridControl.RowCellMenu) is populated with items (ToolbarButtonItem objects) from an item source specified by the PopupMenu.ItemsSource collection. In this example, the PopupMenu.ItemsSource property is bound to the MenuItems collection defined in the main View Model using the following binding expression:

<mxb:PopupMenu ItemsSource="{Binding DataControl.DataContext.MenuItems}">

When a PopupMenu is displayed for a DataGrid cell, the menu's DataContext contains a Eremex.AvaloniaUI.Controls.DataControl.Visuals.CellData object. The CellData object exposes the DataControl property, which allows you to access the control for which the menu is displayed. The main View Model is assigned to the control's DataContext. Thus, the DataControl.DataContext.MenuItems syntax refers to the MenuItems collection defined in the main View Model.

The CellData object also contains other properties that allow you to access cell-related information (column, row object, etc.).

The menu items are initialized using styles. The DataContext of the menu items are elements of the PopupMenu.ItemsSource collection. In this example, the PopupMenu.ItemsSource property stores a collection of MenuItemViewModel objects. The following snippet binds the ToolbarButtonItem.Header and ToolbarButtonItem.Command properties to the MenuItemViewModel.Header and MenuItemViewModel.Command properties, respectively.

<mxb:PopupMenu.Styles>
    <Style Selector="mxb|ToolbarButtonItem">
        <Setter Property="Header" Value="{Binding Header}"/>
        <Setter Property="Command" Value="{Binding Command}"  />
    </Style>
</mxb:PopupMenu.Styles>

A ToolbarButtonItem's command requires information about the row that has been right-clicked. To pass the data row to the command, the XAML code sets the ToolbarButtonItem.CommandParameter property, as follows:

xmlns:mxvis="clr-namespace:Eremex.AvaloniaUI.Controls.DataControl.Visuals;
 assembly=Eremex.Avalonia.Controls"

<mxb:PopupMenu.Styles>
    <Style Selector="mxb|ToolbarButtonItem">
        ...
        <Setter Property="CommandParameter" 
         Value="{Binding $parent[mxvis:CellControl].DataContext.Row}"  />
    </Style>
</mxb:PopupMenu.Styles>

Here, the $parent[mxvis:CellControl] expression traverses the logical tree to locate a CellControl object (it is a parent of the ToolbarButtonItem's DataContext). The CellControl.DataContext object contains an Eremex.AvaloniaUI.Controls.DataControl.Visuals.CellData object, which allows you to access the data row from the CellData.Row property.

The complete code is shown below.

xmlns:mxdg="https://schemas.eremexcontrols.net/avalonia/datagrid"
xmlns:mxb="https://schemas.eremexcontrols.net/avalonia/bars"
xmlns:mxvis="clr-namespace:Eremex.AvaloniaUI.Controls.DataControl.Visuals;
 assembly=Eremex.Avalonia.Controls"

<mxdg:DataGridControl Name="dataGrid1" AutoGenerateColumns="True" ItemsSource="{Binding Employees}">
    <mxdg:DataGridControl.RowCellMenu>
        <mxb:PopupMenu ItemsSource="{Binding DataControl.DataContext.MenuItems}">
            <mxb:PopupMenu.Styles>
                <Style Selector="mxb|ToolbarButtonItem">
                    <Setter Property="Header" Value="{Binding Header}"/>
                    <Setter Property="Command" Value="{Binding Command }"  />
                    <Setter Property="CommandParameter" 
                     Value="{Binding $parent[mxvis:CellControl].DataContext.Row}"  />
                </Style>
            </mxb:PopupMenu.Styles>
        </mxb:PopupMenu>
    </mxdg:DataGridControl.RowCellMenu>
</mxdg:DataGridControl>
public partial class MainView : UserControl
{
    MainViewModel viewModel = new MainViewModel();

    public MainView()
    {
        DataContext = viewModel;

        viewModel.Employees.Add(new Employee() { 
            Name = "Herbert McGill", EmploymentType=EmploymentType.FullTime, 
            Position= "Customer Service" 
        });
        viewModel.Employees.Add(new Employee() { 
            Name = "Cora Gilmore", EmploymentType = EmploymentType.PartTime, 
            Position = "Accountant" 
        });
        viewModel.Employees.Add(new Employee() { 
            Name = "Earl Case", EmploymentType = EmploymentType.PartTime, 
            Position = "Software Engineer" 
        });

        InitializeComponent();
    }
}

public partial class MainViewModel : ViewModelBase
{
    public ObservableCollection<Employee> Employees { get; } = new();

    public MainViewModel()
    {
        MenuItemViewModel menuItem1 = new MenuItemViewModel();
        menuItem1.Header = "Copy Row";
        menuItem1.Command = new RelayCommand<object>(CopyRow);
        MenuItems.Add(menuItem1);

        MenuItemViewModel menuItem2 = new MenuItemViewModel();
        menuItem2.Header = "Delete Row";
        menuItem2.Command = new RelayCommand<object>(DeleteRow);
        MenuItems.Add(menuItem2);

    }

    public ObservableCollection<MenuItemViewModel> MenuItems { get; } = new();

    [RelayCommand]
    void CopyRow(object row)
    {
        //...
    }
    [RelayCommand]
    void DeleteRow(object row)
    {
        //...
    }
}

public partial class MenuItemViewModel : ObservableObject
{
    [ObservableProperty]
    string? header;

    [ObservableProperty]
    ICommand? command;
}

public partial class Employee : ObservableObject
{
    [ObservableProperty]
    public string name = "";

    [ObservableProperty]
    public string position = "";

    [ObservableProperty]
    public EmploymentType employmentType;

    [Browsable(false)]
    public bool IsContract => EmploymentType == EmploymentType.Contract;

    [Browsable(false)]
    public bool IsPartTime => EmploymentType == EmploymentType.PartTime;
}

public enum EmploymentType
{
    [Display(Name = "Full Time")]
    FullTime,
    [Display(Name = "Part Time")]
    PartTime,
    Contract
}

Customize Menus on Showing

You can handle the PopupMenu.Opening event to dynamically customize a DataGrid's context menus. The event occurs when a PopupMenu is about to be displayed.

Example - How to show a context menu for the first column

The following example assign an empty PopupMenu to the DataGridControlBase.RowCellMenu property, and then handles the PopupMenu.Opening event to populate the menu with items when a user right-clicks cells within the first visible DataGrid column. The menu remains empty (and thus it's not displayed) when a user right-clicks within other columns.

The created menu contains the "Show/Hide Group Panel" check button that toggles the visibility of the DataGrid's Group Panel (the DataGridControlBase.ShowGroupPanel property).

datagrid-contextmenus-customizeonshowing

<mxdg:DataGridControl Name="dataGrid1" ...>
    <mxdg:DataGridControl.RowCellMenu>
        <mxb:PopupMenu Opening="RowCellMenuOpening"/>
    </mxdg:DataGridControl.RowCellMenu>
</mxdg:DataGridControl>
using Eremex.AvaloniaUI.Controls.DataControl.Visuals;

void RowCellMenuOpening(object sender, CancelEventArgs e)
{
    if (sender == null) return;
    PopupMenu menu = sender as PopupMenu;
    menu.Items.Clear();

    CellData cellData = menu.DataContext as CellData;
    DataGridControl control = cellData.DataControl as DataGridControl;
    GridColumn column = cellData.Column as GridColumn;
    //// Access the underlying data row, when required.
    //object dataRow = cellData.Row;

    if(column.VisibleIndex == 0)
    {
        ToolbarCheckItem btn1 = new ToolbarCheckItem();
        btn1.IsChecked = control.ShowGroupPanel;
        btn1.Header = (control.ShowGroupPanel) ? "Hide Group Panel" : "Show Group Panel";
        btn1.Command = new RelayCommand<DataGridControl>(ShowGroupPanelCommand);
        btn1.CommandParameter = control;
        menu.Items.Add(btn1);
    }
}

[RelayCommand]
void ShowGroupPanelCommand(DataGridControl dataGrid)
{
    dataGrid.ShowGroupPanel = !dataGrid.ShowGroupPanel;
}

Show a Context Menu for Controls Using a ToolbarManager's Attached Property

The Eremex.AvaloniaUI.Controls.Bars.ToolbarManager component provides the ContextPopup attached property that allows you to assign a context menu to any control, including DataGrid. This context menu is displayed for DataGrid regions that have no default context menus, and for regions with empty default menus.

Example - How to assign a context menu using the ToolbarManager.ContextPopup attached property

The following code uses the ToolbarManager.ContextPopup attached property to specify a context menu for a DataGrid control. The menu contains the Show Column Header Panel/Hide Column Header Panel menu check item which toggles the visibility of the DataGridControl.ShowColumnHeaders option.

datagrid-contextmenus-toolbarmanager-example

<mxdg:DataGridControl Name="dataGrid1">
    <mxdg:DataGridControl.Styles>
        <Style Selector="mxdg|DataGridControl">
            <Setter Property="mxb:ToolbarManager.ContextPopup" >
                <Template>
                    <mxb:PopupMenu Tag="dataGrid1">
                        <mxb:ToolbarCheckItem Header="Show Column Header Panel" 
                         IsChecked="{Binding $parent[mxdg:DataGridControl].ShowColumnHeaders, Mode=TwoWay}" 
                         IsVisible="{Binding !$parent[mxdg:DataGridControl].ShowColumnHeaders}"/>
                        <mxb:ToolbarCheckItem Header="Hide Column Header Panel" 
                         IsChecked="{Binding $parent[mxdg:DataGridControl].ShowColumnHeaders, Mode=TwoWay}" 
                         IsVisible="{Binding $parent[mxdg:DataGridControl].ShowColumnHeaders}"/>
                    </mxb:PopupMenu>
                </Template>
            </Setter>
        </Style>
    </mxdg:DataGridControl.Styles>
</mxdg:DataGridControl>