0

I am cooking up DataGridTemplateColumns programmatically via
DataTemplate dtStringTemplate = (DataTemplate)XamlReader.Load(sr, pc); dataGridTemplateColumn.CellTemplate = dtStringTemplate;

I tried adding the ContextMenu to the DataGrid, but any editable cells used their own context menu.

So far, this post has gotten me as far as getting the TextBox context menu to appear as expected: How to add a ContextMenu in the WPF DataGridColumn in MVVM?

Using the post mentioned above as a guide, I have created the Style and the ContextMenu in App.xaml; when I right-click on the Cell in the DataGrid, my context menu appears. However, I can't get the associated command to fire, and I suspect the binding is not right. Here's the xaml in App.xaml:

<ContextMenu x:Key="DataGridContextMenu">
        <MenuItem Header="MenuItem One" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=DataContext.CmdMenuItemOne}" />
        <MenuItem Header="MenuItem Two" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=DataContext.CmdMenuItemOne}" />
    </ContextMenu>

The DataContext for the DataGrid is MyViewModel; MyViewModel has a public DelegateCommand named CmdMenuItemOne.
Unfortunately, CmdMenuItemOne is never called.
What am I misunderstanding in the binding? Thanks ...

Community
  • 1
  • 1
Number8
  • 12,322
  • 10
  • 44
  • 69
  • The problem is that columns are not part of the VisualTree. This post gives more information: https://stevencalise.wordpress.com/2010/08/28/binding-datagrid-columns-to-datacontext-items/ – Flat Eric Feb 05 '16 at 19:48
  • Thanks for the reply. I used the technique in the linked article to set the column's DC. When I run, I get this binding error: `"Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.DataGrid', AncestorLevel='1''. BindingExpression:Path=DataContext.CmdMenuItemOne; DataItem=null; target element is 'MenuItem' (Name=''); target property is 'Command' (type 'ICommand')"` – Number8 Feb 05 '16 at 20:04
  • Also, since I am creating the DataTemplate for the DataGridCell, won't the ContextMenu get the DataGridCell's DC? – Number8 Feb 05 '16 at 20:37
  • @Number8 Is your DataGrid defined in XAML? – Ilan Feb 07 '16 at 09:32
  • @Ilan : Yes, the DataGrid is defined in XAML, but the columns are added programatically. – Number8 Feb 08 '16 at 15:19
  • @Number8 Please take a look on my solution below. – Ilan Feb 08 '16 at 19:12

2 Answers2

1

Use very simple approach given below.

<Window.Resources>
    <FrameworkElement x:Key="DCKey" />
</Window.Resources>

    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = vm;

        ((FrameworkElement)this.Resources["DCKey"]).DataContext = vm;
    }

<MenuItem Header="DoSomething" Command="{Binding DataContext.Cmd, Source={StaticResource DCKey}}"/>
AnjumSKhan
  • 9,647
  • 1
  • 26
  • 38
0

since the fact that a ContextMenu isn't a part of a DataGrid's visual tree, we cannot access property defined in the DataGrid's DataContext. But we can do the next workaround to do that.

  1. Create a Boolean attached property to define the next thing; if the value of added property is true, it will find the visual parent of the object this property is attached to, and assign a parent's DataContext to the Tag property of the object this property is attached to, from the other hand if the value of the attached property is false , the Tag property will be assign to null.
  2. Create an extension helper method that will scan the visual tree of caller and will return it's(caller) parent of specific type.
  3. Create default style for DataGridCell object that will use a dependency property which was described above and will define the ContextMenu. Set this style as resource in App.xaml(take in account that this style will be used by all DataGridCell objects in your project).

style code (should be in App.xaml)

<Style TargetType="DataGridCell">
        <Setter Property="dataGridCreateColumnAndBindIteFromCodeBehind:DataGridAttached.SetDataGridDataContextToTag" Value="True"></Setter>
         <Setter Property="ContextMenu">
             <Setter.Value>
                <ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
                    <MenuItem Header="MenuItem One"
                              Command="{Binding CmdMenuItemOne}" />
                    <MenuItem Header="MenuItem Two" 
                              Command="{Binding CmdMenuItemTwo}" />
                </ContextMenu>
            </Setter.Value>
         </Setter></Style>

attached property code

public class DataGridAttached
{

    public static readonly DependencyProperty SetDataGridDataContextToTagProperty = DependencyProperty.RegisterAttached(
        "SetDataGridDataContextToTag", typeof (bool), typeof (DataGridAttached), new PropertyMetadata(default(bool), SetParentDataContextToTagPropertyChangedCallback));

    public static void SetSetDataGridDataContextToTag(DependencyObject element, bool value)
    {
        element.SetValue(SetDataGridDataContextToTagProperty, value);
    }

    public static bool GetSetDataGridDataContextToTag(DependencyObject element)
    {
        return (bool) element.GetValue(SetDataGridDataContextToTagProperty);
    }

    private static void SetParentDataContextToTagPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
    {
        PerformDataContextToTagAssignment(dependencyObject as FrameworkElement, (bool)args.NewValue);
    }

    private static void PerformDataContextToTagAssignment(FrameworkElement sender, bool isAttaching)
    {
        var control = sender;
        if (control == null) return;
        if (isAttaching == false)
        {
            control.Tag = null;
        }
        else
        {
            var dataGrid = control.FindParent<DataGrid>();
            if (dataGrid == null) return;
            control.Tag = dataGrid.DataContext;
        }
    }
}

helper code

public static class VisualTreeHelperExtensions
{
    public static T FindParent<T>(this DependencyObject child) where T : DependencyObject
    {
        while (true)
        {
            //get parent item
            DependencyObject parentObject = VisualTreeHelper.GetParent(child);

            //we've reached the end of the tree
            if (parentObject == null) return null;

            //check if the parent matches the type we're looking for
            T parent = parentObject as T;
            if (parent != null)
                return parent;
            child = parentObject;
        }
    }
}

Regards.

Ilan
  • 2,762
  • 1
  • 13
  • 24