Search code examples
c#wpfxamldatagrid

Context menu for specific dynamic column for datagrid wpf


I really need help to figure out how to add a context menu for a specific column on a datagrid.

Currently my datagrid looks like this: Datagrid and all columns are added to the datagrid dynamically with the itemsource: ItemsSource="{Binding Data.DefaultView}" and I have AutoGenerateColumns="True". Currently I can add different contextmenu for all the headers by using a style and a trigger. Example:

<Style TargetType="{x:Type DataGridColumnHeader}">
                <Style.Triggers>
                    <Trigger Property="Content" Value="CoreRefDes">
                        <Setter Property="ContextMenu"
                        Value="{StaticResource DataGridColumnHeaderContextMenuSpecific}" />
                    </Trigger>
                    <Trigger Property="Content" Value="CorePartNumber">
                        <Setter Property="ContextMenu"
                        Value="{StaticResource DataGridColumnHeaderContextMenuSpecific}" />
                    </Trigger>
                    <Trigger Property="Content" Value="CoreDescription">
                        <Setter Property="ContextMenu"
                        Value="{StaticResource DataGridColumnHeaderContextMenuSpecific}" />
                    </Trigger>
                    <Trigger Property="Content" Value="Split">
                        <Setter Property="ContextMenu"
                        Value="{StaticResource DataGridColumnHeaderContextMenuSpecific}" />
                    </Trigger>
                </Style.Triggers>

I want something similar but instead of the contextmenu on the header I want it on the cells or the datagrid based on the header name. Currently my contextmenu appears on the datagrid like this:

                <Style TargetType="{x:Type DataGrid}">
                <Setter Property="ContextMenu" Value="{DynamicResource DatagridCellContextmenuItems}"/>
            </Style>

Problem with this is I get the same contextmenu for all the columns. I want a different contextmenu for column 1 and 2. Any idea on how to solve this?

Thank you very much!


Solution

  • This one is a bit of a pain to achieve, but this is how I've done in the past.

    I have a context menu on the data grid that contains every possible menu item that I might need. I then hide or show each one depending on which row/column/cell was r.clicked.

    Here's the datagrid XAML snippet:

    <DataGrid ContextMenuOpening="GridContextMenuOpening" ...>
        <DataGrid.ContextMenu>
            <ContextMenu>
                <MenuItem x:Name="mnuOpen" ... />
                <MenuItem x:Name="mnuNew" ... />
                <!-- and so on -->
            </ContextMenu>
        </DataGrid.ContextMenu>
    
        <!-- rest of datagrid - columns etc -->
    </DataGrid>     
    

    Note the DataGrid's ContextMenuOpening event handler - here is the code-behind:-

    private void GridContextMenuOpening(object sender, ContextMenuEventArgs e)
    {
        DataGridCell cell;
        DataGridRow row;
    
        var dep = DataGridMiscHelpers.FindVisualParentAsDataGridSubComponent(
            (DependencyObject)e.OriginalSource);
        if (dep == null)
        {
            return;
        }
    
        DataGridMiscHelpers.FindCellAndRow(dep, out cell, out row);
        if (dep is DataGridColumnHeader || dep is DataGridRow)
        {
            e.Handled = true;
            return;
        }
    
        // Hide/show the menu items depending on the cell and/or row clicked.
        // (You could programmatically add/remove menu items here instead).
        mnuOpen.Visibility = (cell.Column.Header == "whatever") ? Visibility.Hidden : Visibility.Visible;
        mnuNew.Visibility = ((row.Item as MyViewModel).SomeProperty == 123) ? Visibility.Hidden : Visibility.Visible;
    }
    

    And here's my "DataGridMiscHelpers" static class used by the above code:-

    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Controls.Primitives;
    using System.Windows.Media;
    using System.Windows.Media.Media3D;
    
    public static class DataGridMiscHelpers
    {
        /// <summary>
        /// Finds the visual parent of the given object
        /// </summary>
        /// <param name="originalSource">The original source.</param>
        /// <returns></returns>
        public static DependencyObject FindVisualParentAsDataGridSubComponent(
            DependencyObject originalSource)
        {
            // iteratively traverse the visual tree
            while ((originalSource != null) 
                && !(originalSource is DataGridCell) 
                && !(originalSource is DataGridColumnHeader) 
                && !(originalSource is DataGridRow))
            {
                if (originalSource is Visual || originalSource is Visual3D)
                {
                    originalSource = VisualTreeHelper.GetParent(originalSource);
                }
                else
                {
                    // If we're in Logical Land then we must walk 
                    // up the logical tree until we find a 
                    // Visual/Visual3D to get us back to Visual Land.
                    originalSource = LogicalTreeHelper.GetParent(originalSource);
                }
            }
    
            return originalSource;
        }
    
        /// <summary>
        /// Finds the cell and row data for the given source.
        /// </summary>
        /// <param name="originalSource">The original source.</param>
        /// <param name="cell">The cell.</param>
        /// <param name="row">The row.</param>
        public static void FindCellAndRow(DependencyObject originalSource, 
            out DataGridCell cell, out DataGridRow row)
        {
            cell = originalSource as DataGridCell;
            if (cell == null)
            {
                row = null;
                return;
            }
    
            // Walk the visual tree to find the cell's parent row.
            while ((originalSource != null) && !(originalSource is DataGridRow))
            {
                if (originalSource is Visual || originalSource is Visual3D)
                {
                    originalSource = VisualTreeHelper.GetParent(originalSource);
                }
                else
                {
                    // If we're in Logical Land then we must walk up the logical tree 
                    // until we find a Visual/Visual3D to get us back to Visual Land.
                    // See: http://www.codeproject.com/Articles/21495/Understanding-the-Visual-Tree-and-Logical-Tree-in
                    originalSource = LogicalTreeHelper.GetParent(originalSource);
                }
            }
    
            row = originalSource as DataGridRow;
        }
    }
    

    }

    Disclaimer: I've just copied/pasted some of these snippets out of a larger codebase so it may not be 100% correct, but hopefully enough to get you started.