Silverlight: right-click ContextMenu for a DataGrid

It required some digging to get it right, all the solutions I’ve found by googling were insufficient.
Here are the problems I had to solve:
– Adding the ContextMenu on the DataGrid made it work on the headers as well, I wanted it only on the ‘body’, excluding headers.
– Adding the ContextMenu of each row using the LoadingRow event involved making lots new instances of the menu, and it raised some thinking about performance with large data and virtualization.
– I wanted it to be simple to use, easy to add commands etc. simply as we adding a ContextMenu elsewhere.
– Should be used in XAML, not code.

Parts of my code is borrowed from Craig Wardman blog, thanks.

Here is the code, and down below is how you use it:

public class DataGridContextMenuBehavior : Behavior<DataGrid>
{
    protected override void OnAttached()
    {
        base.OnAttached();

        if (ContextMenu != null) AssociatedObject.LayoutUpdated += AssociatedObjectOnLayoutUpdated;
        AssociatedObject.AddHandler(UIElement.MouseRightButtonDownEvent,
            new MouseButtonEventHandler(OnMouseRightButtonDown), true);
    }

    private void AssociatedObjectOnLayoutUpdated(object sender, EventArgs eventArgs)
    {
        if(ContextMenu == null) return;
        var rowsPresenter = FindChildControlByType<DataGridRowsPresenter>(AssociatedObject);

        if (rowsPresenter != null)
        {
            ContextMenuService.SetContextMenu(rowsPresenter, ContextMenu);
            AssociatedObject.LayoutUpdated -= AssociatedObjectOnLayoutUpdated;
        }
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();

        AssociatedObject.MouseRightButtonDown -= OnMouseRightButtonDown;
    }

    private void OnMouseRightButtonDown(object sender, MouseButtonEventArgs e)
    {
        var elementsUnderMouse = VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(null),
            AssociatedObject);

        var row = elementsUnderMouse.OfType<DataGridRow>().FirstOrDefault();

        if (row != null)
            AssociatedObject.SelectedItem = row.DataContext;
    }

    #region ContextMenu (DependencyProperty)

    public ContextMenu ContextMenu
    {
        get { return (ContextMenu)GetValue(ContextMenuProperty); }
        set { SetValue(ContextMenuProperty, value); }
    }
    public static readonly DependencyProperty ContextMenuProperty =
        DependencyProperty.Register("ContextMenu", typeof(ContextMenu), typeof(DataGridContextMenuBehavior),
            new PropertyMetadata(null)
            );

    #endregion

    #region find child control by type http://www.craigwardman.com/blog/index.php/2011/06/finding-a-child-control-by-type-in-silverlight/

    private static T FindChildControlByType<T>(DependencyObject container) where T : DependencyObject
    {
        return FindChildControlByType<T>(container, null);
    }

    private static T FindChildControlByType<T>(DependencyObject container, string name) where T : DependencyObject
    {
        T foundControl = null;

        //for each child object in the container
        for (var i = 0; i < VisualTreeHelper.GetChildrenCount(container); i++)
        {
            //is the object of the type we are looking for?
            if (VisualTreeHelper.GetChild(container, i) is T &&
                (VisualTreeHelper.GetChild(container, i).GetValue(FrameworkElement.NameProperty).Equals(name) ||
                    name == null))
            {
                foundControl = (T) VisualTreeHelper.GetChild(container, i);
                break;
            }
            //if not, does it have children?
            if (VisualTreeHelper.GetChildrenCount(VisualTreeHelper.GetChild(container, i)) > 0)
            {
                //recursively look at its children
                foundControl = FindChildControlByType<T>(VisualTreeHelper.GetChild(container, i), name);
                if (foundControl != null)
                    break;
            }
        }

        return foundControl;
    }

    #endregion
}

example of use:

<sdk:DataGrid Name="DataGrid1"
                ItemsSource="{Binding SourceCollection}">
    <i:Interaction.Behaviors>
        <shemesh:DataGridContextMenuBehavior>
            <shemesh:DataGridContextMenuBehavior.ContextMenu>
                <toolkit:ContextMenu>
                    <toolkit:MenuItem Header="Cut"
                                        Click="MenuItem_OnCut" />
                    <toolkit:MenuItem Header="Copy"
                                        Command="{Binding OnCopyCommand}" />
                </toolkit:ContextMenu>
            </shemesh:DataGridContextMenuBehavior.ContextMenu>
        </shemesh:DataGridContextMenuBehavior>
    </i:Interaction.Behaviors>
</sdk:DataGrid>
Advertisements