Silverlight: right-click ContextMenu for a DataGrid
December 28, 2013 Leave a comment
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>