I use the context menu to change the rows DataGrid . Each menu item represents a color that is applied to the rows of the table. In the Colors collection, the colors themselves. The user can delete or add their own.

He brought a transparent color from the collection so that the user could not remove it and this item was always available.

The problem with linking the menu item "Without color". The command binds, but the command parameter that contains the rows of the table that need to be repainted is not attached. Although in the ItemContainerStyle the command parameter is bound in the same way and everything else works as it should. But with the point "Without color" this way, for some reason, does not work.

What is the problem?

 <DataGrid.ContextMenu> <MenuItem Header="Цвет"> <MenuItem.ItemsSource> <CompositeCollection> <MenuItem Header="Без цвета" Command="{Binding ResultsVM.ResetColorCommand}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.SelectedItems}"/> <Separator /> <CollectionContainer Collection="{Binding Source={StaticResource Colors}}" /> <Separator /> <MenuItem Header="Редактировать метки" Click="miColorEdit_Click" /> </CompositeCollection> </MenuItem.ItemsSource> <MenuItem.ItemContainerStyle> <Style TargetType="{x:Type MenuItem}"> <Setter Property="Command" Value="{Binding ChangeColorCommand}" /> <Setter Property="CommandParameter" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.SelectedItems}" /> </Style> </MenuItem.ItemContainerStyle> </MenuItem> </DataGrid.ContextMenu> 

UPDATE

 <DataGrid.ContextMenu> <ContextMenu x:Name="ContextMenu"> <MenuItem Header="Цвет"> <MenuItem.ItemsSource> <CompositeCollection> <MenuItem Header="Без цвета" Command="{Binding ResultsVM.ResetColorCommand}" CommandParameter="{Binding ElementName=ContextMenu, Path=PlacementTarget.SelectedItems}" /> <Separator /> <CollectionContainer Collection="{Binding Source={StaticResource Colors}}" /> <Separator /> <MenuItem Header="Редактировать метки" Click="miColorEdit_Click" /> </CompositeCollection> </MenuItem.ItemsSource> </MenuItem> </ContextMenu> <DataGrid.ContextMenu> 

UPDATE 2

ColorVM:

 public class ColorVM : BaseVM { private string _title; private string _value; public string Title { get { return _title; } set { _title = value; OnPropertyChanged(); } } public string Value { get { return _value; } set { _value = value; OnPropertyChanged(); } } public ICommand ChangeColorCommand { get; set; } } 

Collection:

 public static ObservableCollection<ColorVM> Colors { get; set; } = new ObservableCollection<ColorVM>() { new ColorVM {Title = "White", Value = "#ffffff"}, new ColorVM {Title = "Black", Value = "#000000"}, new ColorVM {Title = "Transparent", Value = "#00ffffff}, new ColorVM {Title = "Yellow", Value = "#ffff00"} }; 

xaml:

 <CollectionViewSource Source="{Binding Colors}" x:Key="Colors"/> 
  • Try using {Binding ElementName = contextMenu} instead of RelativeSource - Gardes
  • @ S.Kost, It also does not help. - trydex
  • Hmm, strangely, the name should work. - VladD
  • @VladD, Completed the response code with ElementName . Maybe I'm doing something wrong? - trydex
  • Hm It should seem to work. I'll try to reproduce. - VladD

2 answers 2

See, you have a difficult problem. The fact is that names and DataContext are inherited across the border of elements, but not through any.

You have two points that break this inheritance. The first one is ContextMenu : it is not a child of the DataGrid (since it is displayed not as nested, but as a new window). The second is a CompositeCollection , which is not a DependencyObject at all, and therefore does not participate in the name transfer mechanism and DataContext .

Your code has an approach to solving the first part of the problem ( ContextMenu ): using PlacementTarget , but not the second part. I will show a technique that was invented for such cases (the author seems to be Josh Smith). This trick is known as Freezable trick.

We will use the fact that the classes inheriting Freezable , participate in inheritance DataContext '. If you put an instance of such a class into resources, then you can put the necessary information into it, and you can attach it to it via the StaticResource from anywhere, even from a place that does not inherit contexts.

Let's get started To start the class:

 public class BindingProxy : Freezable { protected override Freezable CreateInstanceCore() => 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)); } 

Now you can use it like this:

 <DataGrid Name="MyDataGrid"> <DataGrid.Resources> <local:BindingProxy x:Key="DGProxy" Data="{Binding ElementName=MyDataGrid}"/> </DataGrid.Resources> <DataGrid.ContextMenu> <ContextMenu> <MenuItem Header="Цвет"> <MenuItem.ItemsSource> <CompositeCollection x:Name="MyCollection"> <MenuItem Header="Без цвета" Command="{Binding ResultsVM.ResetColorCommand}" CommandParameter="{Binding Source={StaticResource DGProxy}, Path=Data.Content}" /> <Separator /> <!-- остальная часть меню --> </CompositeCollection> </MenuItem.ItemsSource> </MenuItem> </ContextMenu> </DataGrid.ContextMenu> <!-- остальная часть DataGrid --> </DataGrid> 

You can also put in the BindingProxy ContextMenu :

 <ContextMenu Name="MyCM"> <ContextMenu.Resources> <local:BindingProxy x:Key="ContextMenuProxy" Data="{Binding ElementName=MyCM}"/> </ContextMenu.Resources> 

and attach to him:

 CommandParameter="{Binding Source={StaticResource ContextMenuProxy}, Path=Data.PlacementTarget.Content}" 

But it does not work, because the name MyCM will not be available inside ContextMenu (do not forget that names do not pass through borders!). In order for this to work, you need in the code-behind (best of all in the constructor, right after InitializeComponent() ) to "hem" the ContextMenu namespace to the outer part:

 NameScope.SetNameScope(MyCM, NameScope.GetNameScope(this)); 

After that, it works the same way.


Detailed description of Freezable trick here:

  • Magic :) Thank you so much! - trydex
  • one
    @maxwell: Good magic, take it to your arsenal :) - VladD
  • one
    @maxwell: Please! - VladD

I’ll add another answer to the @VladD answer to overcome the DataContext inheritance gap, tackled here :

 <FrameworkElement x:Name="dummyElement" Visibility="Collapsed"/> <DataGrid> <DataGrid.Columns> <DataGridTextColumn Header="Test" Binding="{Binding Name}" Visibility="{Binding DataContext.IsEnable, Source={x:Reference dummyElement}}"/> </DataGrid.Columns> </DataGrid> 
  • one
    Wow, another spell, thanks! By the way, in theory, FrameworkElement should not be needed, you can just hang the name on the DataGrid . - VladD