-1

I got some problems accessing a specific DataContext from within a Style.
I have a DataGrid defined like this:

<DataGrid Name="ReferenceDataGrid" ItemsSource="{Binding Items}" AutoGenerateColumns="False" RowHeaderWidth="0" IsReadOnly="True">
    <DataGrid.Resources>
        <Style TargetType="DataGridRow">
            <Setter Property="ContextMenu">
                <Setter.Value>
                    <ContextMenu>
                        <MenuItem Header="Delete" Command="???" />
                    </ContextMenu>
                </Setter.Value>
            </Setter>
        </Style>
    </DataGrid.Resources>
    ...

The DataContext property of the Page containing this DataGrid is set to

DataContext="{Binding RelativeSource={RelativeSource Self}}"

I want to bind a RelayCommand to the MenuItem and tried several different ways to do this:

  • Command="{Binding DeleteCommand}"
  • Command="{Binding ElementName=Root, Path=DeleteCommand}" // Page element's name set to Root
  • Command="{Binding ElementName=Root, Path=DataContext.DeleteCommand}"
  • Command="{Binding Path=DataContext.DeleteCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGrid}}}"

I set a breakpoint in the command handler of DeleteCommand but none of the variations above got there.

Let's say Items contains elements of type Foo.
If I define the command in Foo the handler gets triggered so it seems that whatever I've done above, the DataContext of each DataGridRow seems to be the list element itself.

Any idea what to do about it?

Edit:
I also tried to extract the DataContext into a separate class and reference this instead of Relative Self as I thought the list elements might use Relative Self as their DataContext instead of the Page instance it refers to. Unfortunately I was wrong.

TorbenJ
  • 4,462
  • 11
  • 47
  • 84
  • Possible duplicate of [WPF ContextMenu woes: How do I set the DataContext of the ContextMenu?](https://stackoverflow.com/questions/15033522/wpf-contextmenu-woes-how-do-i-set-the-datacontext-of-the-contextmenu) – Rekshino Apr 03 '18 at 11:38
  • Nope, doesn't work either. Maybe I have to do something different because I use Styles and not DataTemplates? – TorbenJ Apr 03 '18 at 11:43
  • Will you only ever be using commands which are in the same datacontext as the DataGrid? – Andy Apr 03 '18 at 12:10
  • @Andy So far I always only had one DataContext per Page/Window etc. I really don't want to clutter up the model classes with this stuff so I store them in the Page's DataContext. I also need to update several other properties in the DataContext so extracting parts of it into separate ones wouldn't give any benefit – TorbenJ Apr 03 '18 at 12:28
  • Actually... that's a good thing. I'll post a solution. – Andy Apr 03 '18 at 14:44

2 Answers2

1

Since you are only interested in commands in one place, there's another way to do this you might find useful. Define the contextmenu as a resource where it can grab that datacontext, then apply that to the datagrid rows. I've done this on a sample which was originally intended for another purpose so my objects and stuff are different.

<DataGrid 
    ItemsSource="{Binding Users}" 
    Background="White" 
    Name="dg"
    SelectedItem="{Binding SelectedUser}"
          >
    <DataGrid.Resources>
        <ContextMenu x:Key="dgContextMenu"
                     DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType=DataGrid}}">
            <MenuItem Header="Up" Command="{Binding UpCommand}" />
        </ContextMenu>
    </DataGrid.Resources>
    <DataGrid.RowStyle>
        <Style TargetType="{x:Type DataGridRow}">
            <Setter Property="ContextMenu" Value="{StaticResource dgContextMenu}"/>

DataContext inherits down the visual tree, so the datagrid gets the same datacontext as a page or window or whatever it's in. My contextmenu is actually in the datagrid and can get it's datacontext.

I also bind selecteditem so I can use that to work out which specific row was clicked.

In the viewmodel for the window I have a command "UpCommand" that uses properties of the selected user.

    public RelayCommand UpCommand { get; set; }
    public MainWindowViewModel()
    {
          UpCommand = new RelayCommand(UpExecute);
    }
    public User SelectedUser { get; set; }
    private void UpExecute()
    {
        MessageBox.Show($"You Upped {SelectedUser.Title}");
    }

You could remove the selected user from Users and or use it's ID to delete the record off a database or whatever delete is to do.

Andy
  • 11,864
  • 2
  • 17
  • 20
0
<ContextMenu  DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}">
    <MenuItem Header="Delete"  Command="{Binding DataContext.DeleteCommand}" />
</ContextMenu>

As you could see in answer to the duplicate question, that the problem is:

The ContextMenu is outside of the visual tree

If you don't want to put the DeleteCommand to the object, which is an entry in Items, than you can setSource propery for Binding with x:Reference.

<DataGrid Name="ReferenceDataGrid" ItemsSource="{Binding Items}" AutoGenerateColumns="False" RowHeaderWidth="0" IsReadOnly="True">
    <DataGrid.Resources>
        <Style TargetType="DataGridRow">
            <Setter Property="ContextMenu">
                <Setter.Value>
                    <ContextMenu  DataContext="{Binding Path=DataContext, Source={x:Reference ReferenceDataGrid}}">
                        <MenuItem Header="Delete"  Command="{Binding DeleteCommand}" />
                    </ContextMenu>
                </Setter.Value>
            </Setter>
        </Style>
    </DataGrid.Resources>
Rekshino
  • 6,954
  • 2
  • 19
  • 44
  • I already tried this solution but it doesn't work like I said in the comments. – TorbenJ Apr 03 '18 at 12:10
  • It works for me. Are you sure you have a GridRowDataContextDeleteCmd in object which is DataContext for the DataGridRow? – Rekshino Apr 03 '18 at 12:12
  • Please, see that `DataContext` of `ContextMenu` is set. – Rekshino Apr 03 '18 at 12:14
  • I am trying to reproduce the issue and it is showing error 4. I have also tried your solution with `PresentationTraceSources.TraceLevel=High` to no avail. I think an event handler might be the solution here. – XAMlMAX Apr 03 '18 at 12:20
  • @Rekshino I copy pasted your code to assure there are no typos on my side but it still doesn't work. The command instance exists. I created a button next to the DataGrid and let it trigger this command with success, so I assume that the command instance itself is not the problem. – TorbenJ Apr 03 '18 at 12:24
  • `DeleteCommand` must be in object, which is DataContext for DataGridRow(!!!), not in main ViewModel – Rekshino Apr 03 '18 at 12:24
  • Try `Header="{Binding DataContext}"` to see what you are dealing with – Rekshino Apr 03 '18 at 12:27
  • `DataContext` for `MenuItem` is `DataGridRow`. – Rekshino Apr 03 '18 at 12:29
  • It rather depends on what delete is going to do of course. Usually it's going to involve removing a row. That is often much more practical if the command is in the viewmodel which has the collection you bind to the itemssource of a datagrid. – Andy Apr 03 '18 at 18:52
  • @Andy Either way I have posted solutions for both cases. I would expect that question has mCve and here I can only guess where is the command. – Rekshino Apr 04 '18 at 06:10