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>

Silverlight: DataGrid with integrated field (column) chooser

Not much to say here, it speaks for itself.

Right click on a column header to open the field (columns) chooser and show/hide columns.

The VisibleColumns property is always up-to-date and can be binded TwoWay, in my case I am saving it to the user preferences for consistency.

Take a look at the code to see how it’s done.

Notice: ContextMenu causes memory leak! http://silverlight.codeplex.com/workitem/7089. as long as you don’t need to remove the DataGrid you are OK.

Demo is Here
Source is here

the WPF version is over here

Follow

Get every new post delivered to your Inbox.