0

I have a DataGrid with a ContextMenu, I'm trying to figure out how to properly bind the context.

So far I have read that the context menu its ouside the visual tree, and therefore the DataContext is different. With that in mind the provided solution is to use the property Tag, but I still cannot make it work:

<UserControl>
    <!--#region DataGrid-->

    <DataGrid ItemsSource="{Binding Model.Collection}"
              Tag="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}, Path=DataContext}">

        <!--#region Resources-->

        <DataGrid.Resources>

            <!--#region DataGridCell-->

            <Style TargetType="DataGridCell">

                <Setter Property="ContextMenu">
                    <Setter.Value>
                        <ContextMenu DataContext="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
                            <MenuItem Header="Open Details"
                                      Command="{Binding DataContext.OpenRowDetailsCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
                                      CommandParameter="{Binding DataContext.SelectedIndex, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
                        </ContextMenu>
                    </Setter.Value>
                </Setter>

            </Style>

            <!--#endregion DataGridCell-->

        </DataGrid.Resources>

        <!--#endregion Resources-->

        <!--#region DataGridColumns-->

        <DataGrid.Columns>
            <DataGridTextColumn Header="Filename"
                                Binding="{Binding FileInfo.Name}"
                                Width="Auto" />
        </DataGrid.Columns>

        <!--#endregion DataGridColumns-->

    </DataGrid>

    <!--#endregion DataGrid-->

The DataContext of the UserControl is working fine as I have other commands which uses the DataContext that way.

Anyone sees any error or has any other approach?

Thanks in advance.

mm8
  • 163,881
  • 10
  • 57
  • 88
Nekeniehl
  • 1,633
  • 18
  • 35

2 Answers2

1

This should work provided that the OpenRowDetailsCommand and SelectedIndex properties are defined in the view model class of the parent UserControl:

<Style TargetType="DataGridCell">
    <Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}" />
    <Setter Property="ContextMenu">
        <Setter.Value>
            <ContextMenu>
                <MenuItem Header="Open Details"
                          Command="{Binding PlacementTarget.Tag.DataContext.OpenRowDetailsCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
                          CommandParameter="{Binding PlacementTarget.Tag.DataContext.SelectedIndex, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
            </ContextMenu>
        </Setter.Value>
    </Setter>
</Style>
mm8
  • 163,881
  • 10
  • 57
  • 88
  • Hi @mm8, thanks for your answer as always, Is there a way to make it work without having selectedIndex or SelectedItem as properties on the VM? Like changing the Binding on the command Parameter to the relativeSource of the DataGrid or making two tags? – Nekeniehl Feb 05 '18 at 15:23
  • @Nekeniehl: I am not sure I understand. Where are the properties defined? – mm8 Feb 05 '18 at 15:27
  • SelectedItem or SelectedIndex nowhere. OpenRowDetailsCommand in the ViewModel, that is why I ask, I don't really need the SelectedItem/Index in the viewModel, but if is only the way to go, then I will do it. I just ask because it might be "easier" to bind SelectedItem from the DataGrid than make a property in the VM – Nekeniehl Feb 05 '18 at 15:31
  • @Nekeniehl: Try to change the Tag binding to `{Binding RelativeSource={RelativeSource AncestorType=DataGrid}}` and the CommandParameter binding to `{Binding PlacementTarget.Tag.SelectedIndex, RelativeSource={RelativeSource AncestorType=ContextMenu}}`. Then you are binding to the SelectedIndex property of the DataGrid itself. – mm8 Feb 05 '18 at 15:35
  • Didn't work: `BindingExpression path error: 'SelectedIndex' property not found on 'object' ''ResultViewModel' (HashCode=23120425)'. BindingExpression:Path=PlacementTarget.Tag.DataContext.SelectedIndex; DataItem='ContextMenu' (Name=''); target element is 'MenuItem' (Name=''); target property is 'CommandParameter' (type 'Object')` – Nekeniehl Feb 05 '18 at 15:38
  • You didn't remove "DataContext" from the binding path. – mm8 Feb 05 '18 at 15:39
  • 1
    You are awesome, working as expected and another lesson learnt. Thanks you. – Nekeniehl Feb 05 '18 at 15:41
1

I like to use a BindingProxy for this (as described in this SO answer)

public class BindingProxy : Freezable
{
    protected override Freezable CreateInstanceCore()
    {
        return new BindingProxy();
    }

    public object Data
    {
        get { return (object)GetValue(DataProperty); }
        set { SetValue(DataProperty, value); }
    }

    public static readonly DependencyProperty DataProperty =
        DependencyProperty.Register("Data", typeof(object),typeof(BindingProxy));
}

Use it like this:

<DataGrid ItemsSource="{Binding Model.Collection}">
    <DataGrid.Resources>
        <local:BindingProxy x:Key="VMProxy" Data="{Binding}" />
        <local:BindingProxy x:Key="DataGridProxy" Data="{Binding RelativeSource={RelativeSource Self}}" />
        <Style TargetType="DataGridCell">
            <Setter Property="ContextMenu">
                <Setter.Value>
                    <ContextMenu>
                        <MenuItem Header="Open Details"
                                  Command="{Binding Data.OpenRowDetailsCommand, Source={StaticResource VMProxy}}" 
                                  CommandParameter="{Binding Data.SelectedIndex, Source={StaticResource DataGridProxy}}"/>
                    </ContextMenu>
                </Setter.Value>
            </Setter>
        </Style>
    </DataGrid.Resources>
Dave M
  • 2,863
  • 1
  • 22
  • 17
  • Hi, I came up to this solution before, but I don't want to use Dependency Properties and I have discarded it. But thanks, suggestions are always welcome. – Nekeniehl Feb 05 '18 at 15:56
  • What do you mean by “I don’t want to use Dependency Properties”? it is quite impossible to write a WPF app without using them, they are everywhere. – Dave M Feb 05 '18 at 16:03
  • Yes I know, but I'm trying to stick as close as possible to MVVM and so far till now every time someone had told me to use DependencyProperty there were also a solution doing it with XAML. At the moment I have not found a problem were DependencyProperty is the only way. – Nekeniehl Feb 05 '18 at 16:12
  • 1
    This solution does not violate MVVM in any way. XAML itself really has nothing to do with MVVM. MVVM is about separating *concerns* not separating *languages*. It is perfectly valid to do View specific stuff in C# instead of pure XAML You could write a perfectly good WPF app that adheres perfectly to the MVVM paradigm without using XAML at all. – Dave M Feb 05 '18 at 16:18
  • Understood, then let me change to "I'm trying to learn as much as possible XAML". And like I said before, I haven't found a problem yet were you cannot use xaml to solve it (doesn't actually mean there will non). Using DP when I'm trying to learn XAML, for me its like cheating. – Nekeniehl Feb 05 '18 at 16:30