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>

WPF: DataGrid with integrated field (column) chooser

following a discussion in the comments of this post
this is the WPF version of a DataGrid control with integrated columns chooser that opens when right-clicking one of the headers.
(see Silverlight demo)
full source code over here

this is how it look like:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;

namespace WPF_DataGridWithColumnChooser.Controls
{
    public class DataGridExtended : DataGrid
    {
        public DataGridExtended()
            : base()
        {
            theContextMenu = new ContextMenu();
            this.Columns.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Columns_CollectionChanged);
        }

        private ContextMenu theContextMenu; //context menu for the field chooser.
        private string AllColumnsHeaders { get; set; }
        private bool oneTime = true;

        protected override Size ArrangeOverride(Size finalSize)
        {
            if (oneTime)// do this only once.
            {
                oneTime = false;
                var headersPresenter = FindChild(this);
                // attach the context menu.
                ContextMenuService.SetContextMenu(headersPresenter, theContextMenu);

                // update VisibleColumns as necessary.
                if (String.IsNullOrEmpty(VisibleColumns))
                {
                    VisibleColumns = AllColumnsHeaders;
                }
                else
                {
                    string s = VisibleColumns;
                    VisibleColumns = null;
                    VisibleColumns = s;
                }
            }
            return base.ArrangeOverride(finalSize);
        }

        private void MenuItem_Click(object sender, RoutedEventArgs e)
        {
            MenuItem mi = sender as MenuItem;
            Image icon = (Image)mi.Icon;
            List splited = VisibleColumns.Split(';').ToList();
            string colName = mi.Header.ToString();

            // remove empty items.
            for (int i = 0; i  1)
                {
                    splited.Remove(colName);
                }
            }

            // update the VisibleColumns.
            string build = "";
            foreach (string name in splited)
            {
                build = string.Format("{0};{1}", name, build);
            }
            VisibleColumns = build;
        }

        private void Columns_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {

            DataGridColumn col = e.NewItems[0] as DataGridColumn;

            // keep a list of all clomuns headers for later use.
            AllColumnsHeaders = String.Format("{0};{1}", col.Header.ToString(), AllColumnsHeaders);

            // make a new menu item and add it to the context menu.
            MenuItem menuItem = new MenuItem();
            Image img;
            menuItem.Click += new RoutedEventHandler(MenuItem_Click);
            menuItem.Header = col.Header.ToString();
            img = new Image();
            img.Source = Application.Current.Resources["Vmark"] as ImageSource;
            menuItem.Icon = img;
            theContextMenu.Items.Add(menuItem);

        }

        #region VisibleColumns (DependencyProperty)

        /// <summary>
        /// Gets or sets a value indicating the names of columns (as they appear in the column header) to be visible, seperated by a semicolon.
        /// columns that whose name is not here will be hidden.
        /// </summary>
        public string VisibleColumns
        {
            get { return (string)GetValue(VisibleColumnsProperty); }
            set { SetValue(VisibleColumnsProperty, value); }
        }

        public static readonly DependencyProperty VisibleColumnsProperty =
            DependencyProperty.Register(
                "VisibleColumns",
                typeof(string),
                typeof(DataGridExtended),
                new PropertyMetadata("", new PropertyChangedCallback(OnVisibleColumnsChanged))
                );

        private static void OnVisibleColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            DataGridExtended dg = (DataGridExtended)d;
            dg.VisibleColumnsChanged(e);

        }

        private void VisibleColumnsChanged(DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue != null)
            {
                string[] showTheseColumns = e.NewValue.ToString().Split(';');
                string colName;

                // update the columns visibility.
                foreach (DataGridColumn col in this.Columns)
                {
                    colName = col.Header.ToString();

                    if (showTheseColumns.Contains(colName))
                        col.Visibility = Visibility.Visible;
                    else
                        col.Visibility = Visibility.Collapsed;
                }

                // update the context menu items.
                if (theContextMenu != null)
                {
                    foreach (MenuItem menuItem in theContextMenu.Items)
                    {
                        colName = menuItem.Header.ToString();
                        if (showTheseColumns.Contains(colName))
                            ((Image)menuItem.Icon).Visibility = Visibility.Visible;
                        else
                            ((Image)menuItem.Icon).Visibility = Visibility.Collapsed;
                    }
                }
            }
        }

        #endregion

        public static T FindChild(DependencyObject depObj) where T : DependencyObject
        {
            // Confirm obj is valid.
            if (depObj == null) return null;

            // success case
            if (depObj is T)
                return depObj as T;

            for (int i = 0; i &lt; VisualTreeHelper.GetChildrenCount(depObj); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(depObj, i);

                T obj = FindChild(child);

                if (obj != null)
                    return obj;
            }

            return null;
        }
    }

}

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