WPF Error: Cannot find governing FrameworkElement for target element - wpf

I've got a DataGrid with a row that has an image. This image is bound with a trigger to a certain state. When the state changes I want to change the image.
The template itself is set on the HeaderStyle of a DataGridTemplateColumn. This template has some bindings. The first binding Day shows what day it is and the State changes the image with a trigger.
These properties are set in a ViewModel.
Properties:
public class HeaderItem
{
public string Day { get; set; }
public ValidationStatus State { get; set; }
}
this.HeaderItems = new ObservableCollection<HeaderItem>();
for (int i = 1; i < 15; i++)
{
this.HeaderItems.Add(new HeaderItem()
{
Day = i.ToString(),
State = ValidationStatus.Nieuw,
});
}
Datagrid:
<DataGrid x:Name="PersoneelsPrestatiesDataGrid" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
AutoGenerateColumns="False" SelectionMode="Single" ItemsSource="{Binding CaregiverPerformances}" FrozenColumnCount="1" >
<DataGridTemplateColumn HeaderStyle="{StaticResource headerCenterAlignment}" Header="{Binding HeaderItems[1]}" Width="50">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{ Binding Performances[1].Duration,Converter={StaticResource timeSpanConverter},Mode=TwoWay}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock TextAlignment="Center" Text="{ Binding Performances[1].Duration,Converter={StaticResource timeSpanConverter}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid>
Datagrid HeaderStyleTemplate:
<Style x:Key="headerCenterAlignment" TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Day}" />
<Image x:Name="imageValidation" Grid.Row="1" Width="16" Height="16" Source="{StaticResource imgBevestigd}" />
</Grid>
<ControlTemplate.Triggers>
<MultiDataTrigger >
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding State}" Value="Nieuw"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="imageValidation" Property="Source" Value="{StaticResource imgGeenStatus}"/>
</MultiDataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now when I startup the project the images doesn't show and I get this error:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=HeaderItems[0]; DataItem=null; target element is 'DataGridTemplateColumn' (HashCode=26950454); target property is 'Header' (type 'Object')
Why is this error showing?

Sadly any DataGridColumn hosted under DataGrid.Columns is not part of Visual tree and therefore not connected to the data context of the datagrid. So bindings do not work with their properties such as Visibility or Header etc (although these properties are valid dependency properties!).
Now you may wonder how is that possible? Isn't their Binding property supposed to be bound to the data context? Well it simply is a hack. The binding does not really work. It is actually the datagrid cells that copy / clone this binding object and use it for displaying their own contents!
So now back to solving your issue, I assume that HeaderItems is a property of the object that is set as the DataContext of your parent View. We can connect the DataContext of the view to any DataGridColumn via something we call a ProxyElement.
The example below illustrates how to connect a logical child such as ContextMenu or DataGridColumn to the parent View's DataContext
<Window x:Class="WpfApplicationMultiThreading.Window5"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vb="http://schemas.microsoft.com/wpf/2008/toolkit"
Title="Window5" Height="300" Width="300" >
<Grid x:Name="MyGrid">
<Grid.Resources>
<FrameworkElement x:Key="ProxyElement" DataContext="{Binding}"/>
</Grid.Resources>
<Grid.DataContext>
<TextBlock Text="Text Column Header" Tag="Tag Columne Header"/>
</Grid.DataContext>
<ContentControl Visibility="Collapsed"
Content="{StaticResource ProxyElement}"/>
<vb:DataGrid AutoGenerateColumns="False" x:Name="MyDataGrid">
<vb:DataGrid.ItemsSource>
<x:Array Type="{x:Type TextBlock}">
<TextBlock Text="1" Tag="1.1"/>
<TextBlock Text="2" Tag="1.2"/>
<TextBlock Text="3" Tag="2.1"/>
<TextBlock Text="4" Tag="2.2"/>
</x:Array>
</vb:DataGrid.ItemsSource>
<vb:DataGrid.Columns>
<vb:DataGridTextColumn
Header="{Binding DataContext.Text,
Source={StaticResource ProxyElement}}"
Binding="{Binding Text}"/>
<vb:DataGridTextColumn
Header="{Binding DataContext.Tag,
Source={StaticResource ProxyElement}}"
Binding="{Binding Tag}"/>
</vb:DataGrid.Columns>
</vb:DataGrid>
</Grid>
</Window>
The view above encountered the same binding error that you have found if I did not have implemented the ProxyElement hack. The ProxyElement is any FrameworkElement that steals the DataContext from the main View and offers it to the logical child such as ContextMenu or DataGridColumn. For that it must be hosted as a Content into an invisible ContentControl which is under the same View.
I hope this guides you in correct direction.

A slightly shorter alternative to using a StaticResource as in the accepted answer is x:Reference:
<StackPanel>
<!--Set the DataContext here if you do not want to inherit the parent one-->
<FrameworkElement x:Name="ProxyElement" Visibility="Collapsed"/>
<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn
Header="{Binding DataContext.Whatever, Source={x:Reference ProxyElement}}"
Binding="{Binding ...}" />
</DataGrid.Columns>
</DataGrid>
</StackPanel>
The main advantage of this is: if you already have an element which is not a DataGrid's ancestor (i.e. not the StackPanel in the example above), you can just give it a name and use it as the x:Reference instead, hence not needing to define any dummy FrameworkElement at all.
If you try referencing an ancestor, you will get a XamlParseException at run-time due to a cyclical dependency.

The way without a proxy is to set bindings in the constructor:
var i = 0;
var converter = new BooleanToVisibilityConverter();
foreach(var column in DataGrid.Columns)
{
BindingOperations.SetBinding(column, DataGridColumn.VisibilityProperty, new Binding($"Columns[{i++}].IsSelected")
{
Source = ViewModel,
Converter = converter,
});
}

The Proxy Element didn't work for me, for a tooltip. For an infragistics DataGrid I did this, you might change it easily to your kind of grid:
<igDP:ImageField Label="_Invited" Name="Invited">
<igDP:Field.Settings>
<igDP:FieldSettings>
<igDP:FieldSettings.CellValuePresenterStyle>
<Style TargetType="{x:Type igDP:CellValuePresenter}">
<Setter Property="ToolTip">
<Setter.Value>
<Label Content="{Binding DataItem.InvitationSent, Converter={StaticResource dateTimeConverter}}"/>
</Setter.Value>
</Setter>
</Style>
</igDP:FieldSettings.CellValuePresenterStyle>
</igDP:FieldSettings>
</igDP:Field.Settings>
</igDP:ImageField>

Related

MVVM How to bind command to ContextMenu

I am completely confused using relativeSource and ancestorLevel.
Relative source is used for to get source from another elements. But to do that successfully you have to count at what level is that element. (How to debug?) It is most confusing part in WPF.
In my example i have context menu that i want to bind datasource and then command. How would binding must be to get command in my vm? Thank you
<Page.DataContext>
<PDB:UsersViewModel x:Name="vm"/>
</Page.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!--Page Header info content-->
<Grid Grid.Row="0">
<TextBlock Text="{Binding SelectedUser.Name}"/>
<TextBlock Text="{Binding ElementName=myGrd, Path=CurrentColumn.DisplayIndex}"/>
</Grid>
<!--Datagrid content-->
<DataGrid x:Name="myGrd"
SelectionMode="Single"
SelectionUnit="Cell"
CurrentItem="{Binding SelectedUser, Mode=TwoWay}"
CurrentColumn="{Binding CurrentColumn, Mode=TwoWay}"
IsReadOnly="True"
Grid.Row="1"
ItemsSource="{Binding FilteredUserList}"
AutoGenerateColumns="True"
CanUserAddRows="False"
>
<DataGrid.Resources>
<ContextMenu x:Key="ContextMenu">
<ContextMenu.Items>
<MenuItem Header="{Binding
RelativeSource={RelativeSource
FindAncestor,
AncestorType={x:Type Page},
AncestorLevel=4}, Path=vm}" />
</ContextMenu.Items>
</ContextMenu>
</DataGrid.Resources>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="ContextMenu" Value="{StaticResource ContextMenu}"/>
</Style>
</DataGrid.CellStyle>
</DataGrid>
</Grid>
You can't use RelativeSource in ContextMenu, because the menu is not a part of the visual tree. However this can be avoided by using Binding Source and x:Reference.
I assume your ViewModel looks like this
public class UserViewModel
{
public string Header { get; set; }
public ICommand MyCommand { get; }
... more code
}
Now let's bind Header and MyCommand properties of the VM
<ContextMenu x:Key="ContextMenu">
<ContextMenu.Items>
<MenuItem Header="{Binding Header, Source={x:Reference vm}}"
Command="{Binding MyCommand, Source={x:Reference vm}}"/>
</ContextMenu.Items>
</ContextMenu>
The important part is to have the ViewModel somewhere in the visual tree and set its x:Name, just like you've done in your example
<Page.DataContext>
<PDB:UsersViewModel x:Name="vm"/>
</Page.DataContext>
If you still want to know more about RelativeSource, this question seems to have the same problem as you. Basically Path of the binding has to be DataContext.MyViewModelProperty and the RelativeSource of the binding must be and element with DataContext set to the ViewModel.

Dynamic treeview expansion not working with HeirarchicalDataTemplate

I have a treeview control that picks item controls dynamically through a template selector. I have bound the property IsExpanded on my model to IsExpanded on TreeViewItem using a Setter. (I know this is hooked up, because if I set IsExpanded to true in the model constructor, the entire tree is expanded, as expected.)
Here's the problem. After the I load the tree, just the root node is visible (as expected), but if I set IsExpanded on a node to true, the tree is supposed to expand to the node that changed, but it does not. (I've put debug markers in to make sure the property is actually changing. )
Here is my xaml:
<Window.Resources>
<HierarchicalDataTemplate x:Key="RegularNodeTemplate"
ItemsSource="{Binding Path=Children}" >
<StackPanel Orientation="Horizontal">
<Border Width="8" Height="15" >
<Label Content="*" Padding="0" HorizontalAlignment="Right" Visibility="{Binding ModifiedCueVisibility}" />
</Border>
<TextBlock Text="{Binding Path=ModelDisplayName}"/>
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="RootNodeTemplate"
ItemsSource="{Binding Path=Children}" >
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock FontWeight="Bold" Text="{Binding Path=ModelDisplayName}"/>
</StackPanel>
</Grid>
</HierarchicalDataTemplate>
<local:ManifestNodeTemplateSelector x:Key="manifestNodeTemplateSelector"/>
</Window.Resources>
<Grid>
<TreeView Name="TheManifestTreeView" Grid.Row="0" ItemsSource="{Binding ManifestRoot}"
ItemTemplateSelector="{StaticResource manifestNodeTemplateSelector}"
SelectedItemChanged="TreeView_SelectedItemChanged" >
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Grid>
Here is The code for IsExpanded:
private bool _isExpanded;
public bool IsExpanded
{
get => _isExpanded;
set
{
_isExpanded = value;
NotifyPropertyChanged("IsExpanded");
}
}
My problem is that I was expecting the tree to automatically expand to a leaf node if that node expanded. If I want the tree to expand to a node, I need to walk up the parentage and expand those.
It seems like ItemContainerStyle only applies for first level of TreeViewItem.Try to put the style inside TreeView.Resources instead
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded}"/>
</Style>
</TreeView.Resources>

WPF DataBinding issue: UserControls in DataGrid Cells not binding

I am trying to build up a DataGrid control for managing a property grid. I have an abstract class for my parameter base class that parameters of various types implement. This base class contains variables that are shared across each parameter (like Group, Name, IsLocked, etc.), and then each parameter manages the information that relevant to the particular type. For example, a number parameter may use a slider for the user control, which also then uses min/max values, etc.
I have set up a class which is an Observable Collection of my base parameter, and established it as a static resource for a CollectionViewSource:
<UserControl.Resources>
<local:Parameters x:Key="Parameters"/>
<CollectionViewSource x:Key="cvsParameters" Source="{StaticResource Parameters}" />
</UserControl.Resources>
Then in my DataGrid, I am using style triggers to switch between user controls for each of my parameters:
<DataGrid x:Name="DataGrid_Globals"
ItemsSource="{Binding Source={StaticResource cvsParameters}}"
AutoGenerateColumns="False"
CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Key" Binding="{Binding Key}" IsReadOnly="True"/>
<DataGridTemplateColumn Width="*" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding UIType}" Value="Text">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<controls:ucText DataContext="{Binding}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding UIType}" Value="Number">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<controls:ucNumber DataContext="{Binding}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
My user controls are set up with their data context declared in XAML:
<UserControl.DataContext>
<local:TestNumber_cls/>
</UserControl.DataContext>
<Grid HorizontalAlignment="Stretch">
<Slider
Value="{Binding NumberValue}"
Minimum="{Binding Min}"
Maximum="{Binding Max}"
/>
</Grid>
I can't figure out how to ensure that my usercontrols bind correctly. My other column works fine, with each Key value appearing, being correctly bound and updating (for this example it's just the "Key" column, but all of the fields from the base class behave just fine.) The correct UserControls are also selected based on the UIType DataTrigger, but the UserControls just instantiate from an empty constructor and don't bind.
I have also confirmed that standalone instances of my UserControls work fine and bind correctly. For, example, it works when I set the datacontext from code behind (which I don't want to do):
this.OneOffNumberUserControl.DataContext = ((Parameters)this.ParameterGrid.Resources["Parameters"])[2];
I have also tried to remove the DataContext="{Binding}" from my user controls, but this doesn't change anything.
In conjunction with Mechanic's suggestion above, which greatly simplified the XAML for my property grid, through further research I also learned that I needed to implement a DependencyProperty for each subclass-dependent usercontrol. Where I set the DataTemplate for each DataType in my property grid UserControl, I set the binding to my DependencyProperty.
<UserControl.Resources>
<local:Parameters x:Key="Parameters"/>
<CollectionViewSource x:Key="cvsParameters" Source="{StaticResource Parameters}" />
<DataTemplate DataType="{x:Type local:TestText_cls}">
<controls:ucText TextObject="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:TestNumber_cls}">
<controls:ucNumber NumberObject="{Binding}"/>
</DataTemplate>
</UserControl.Resources>
<Grid>
<DataGrid x:Name="DataGrid_Globals"
ItemsSource="{Binding Source={StaticResource cvsParameters}}"
AutoGenerateColumns="False"
CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Key" Binding="{Binding Key}" IsReadOnly="True"/>
<DataGridTemplateColumn Width="*" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
Then in the code behind my UserControls, I implement the DependencyProperty:
public static readonly DependencyProperty TextObjectProperty =
DependencyProperty.Register("TextObject",
typeof(TestText_cls),
typeof(ucText),
new PropertyMetadata(new TestText_cls()));
[Bindable(true)]
public TestText_cls TextObject
{
get { return (TestText_cls)this.GetValue(TextObjectProperty); }
set { this.SetValue(TextObjectProperty, value); }
}
public ucText()
{
InitializeComponent();
}
Finally, within each UserControl, I ensure that I have given it a name, and then set my binding to the correct variable in my DependencyProperty:
<UserControl x:Class="Testing.ucText"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Testing.Classes"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Name="TextUI">
<Grid HorizontalAlignment="Stretch">
<TextBox Text="{Binding ElementName=TextUI, Path=TextObject.Value}"/>
</Grid>
</UserControl>
I hope this helps anyone else!
If you want to put different controls for different subclasses, specify different datatemplates, and then
<!-- subtype-specific column -->
<DataGridTemplateColumn Header="">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

WPF TreeView IsSelected Binding Throwing Null Reference Exception

I've got a tabcontrol that is bound to a tab collection in my view model. Right now there are 2 types of tabs and one of them has a treeview in it. When the tab is first created, the treeview selection works. When I switch tabs and come back to the tab with the treeview in it, the treeview items seem to become unbound. When I try to select one I get the following error:
System.Windows.Data Error: 8 : Cannot save value from target back to source.
BindingExpression:Path=IsSelected; DataItem='NavigationItem' (HashCode=50956576);
target element is 'TreeViewItem' (Name=''); target property is 'IsSelected' (type
'Boolean') NullReferenceException:'System.NullReferenceException:
Object reference not set to an instance of an object.
at System.ComponentModel.ReflectPropertyDescriptor.SetValue(
Object component, Object value)
at MS.Internal.Data.PropertyPathWorker.SetValue(Object item, Object value)
at MS.Internal.Data.ClrBindingWorker.UpdateValue(Object value)
at System.Windows.Data.BindingExpression.UpdateSource(Object value)'
I'm not sure why this is occurring. When debugging through the application my model still has all of the correct data, and the treeview even displays the information from the model meaning it has to have been bound correctly when the tab was initially changed. It's just when I try to select an item after having left the tab and come back. Here's the source for my tab control and the tab in question with a treeview in it:
<TabControl ItemsSource="{Binding Tabs}">
<TabControl.ItemTemplate>
...
</TabControl.ItemTemplate>
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</TabControl.ItemContainerStyle>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type tabs:ArchitectureTabViewModel}">
<views:ArchitectureTab/>
</DataTemplate>
<DataTemplate DataType="{x:Type tabs:TestOrderTabViewModel}">
<views:TestOrderTab/>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
And the tab in question:
<Grid DataContext="{Binding GraphNavigationModel}">
...
<TreeView ItemsSource="{Binding NavigationTree}" Background="#00ffffff" Margin="5" BorderThickness="0" MinWidth="200">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Text}">
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=OneWayToSource}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Grid>
</Grid>

ContextMenu.PlacementTarget is not getting set, no idea why

<DataTemplate x:Key="_ItemTemplateA">
<Grid Tag="{Binding Path=DataContext.Command, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ContentControl Content="{Binding}" ContentTemplate="{StaticResource ContentTemplateB}" Grid.Row="0" />
<ContentControl Name="uiContentPresenter" Content="{Binding ContentView}" Grid.Row="1" Height="0" />
<ContentControl DataContext="{Binding IsContentDisplayed}" DataContextChanged="IsDisplayed_Changed" Visibility="Collapsed" />
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Text"
Command="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"
CommandParameter="{Binding}" />
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</DataTemplate>
The above data template is applied to an ItemsControl. The issue is that for the ContextMenu that is specified for the Grid, the PlacementTarget property is never actually getting set to anything so I cannot get to the Tag property of the Grid which is necessary for passing the Command that should execute on the parent UserControl down to the context menu. I've based this approach off of similar examples such as this: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/0244fbb0-fd5f-4a03-bd7b-978d7cbe1be3/
I've not been able to identify any other good way to pass this command down. This is setup this way because we are using an MVVM approach so the command we have to execute lives in the View Model of the user control this template is applied in. I've tried explicitly setting the PlacementTarget in a few different ways but it still always shows up as not set.
I realise that this is old and answered, but it doesn't seem properly answered. I came across a similar post and left a full answer. You might like to take a look as you can get it working with just a few adjustments to your code.
First, name your view UserControl... I generally name all of mine This for simplicity. Then remembering that our view model is bound to the DataContext of the UserControl, we can bind to the view model using {Binding DataContext, ElementName=This}.
So now we can bind to the view model, we have to connect that with the ContextMenu.DataContext. I use the Tag property of the object with the ContextMenu (the PlacementTarget) as that connection, in this example, a Grid:
<DataTemplate x:Key="YourTemplate" DataType="{x:Type DataTypes:YourDataType}">
<Grid ContextMenu="{StaticResource Menu}" Tag="{Binding DataContext,
ElementName=This}">
...
</Grid>
</DataTemplate>
We can then access the view model properties and commands in the ContextMenu by binding the ContextMenu.DataContext property to the PlacementTarget.Tag property (of the Grid in our example):
<ContextMenu x:Key="Menu" DataContext="{Binding PlacementTarget.Tag, RelativeSource=
{RelativeSource Self}}">
<MenuItem Header="Delete" Command="{Binding DeleteFile}" CommandParameter=
"{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource
AncestorType=ContextMenu}}" CommandTarget="{Binding PlacementTarget,
RelativeSource={RelativeSource Self}}" />
</ContextMenu>
Note the binding on the MenuItem.CommandTarget property. Setting this ensures that the target element on which the specified command is raised is the PlacementTarget, or the Grid in this case.
Also note the CommandParameter binding. This binds to the DataContext of the PlacementTarget, or the Grid in this case. The DataContext of the Grid will be inherited from the DataTemplate and so your data item is now bound to the object parameter in your Command if you're using some implementation of the ICommand interface:
public bool CanExecuteDeleteFileCommand(object parameter)
{
return ((YourDataType)parameter).IsInvalid;
}
public void ExecuteDeleteFileCommand(object parameter)
{
Delete((YourDataType)parameter);
}
Or if you are using some kind of RelayCommand delegates directly in your view model:
public ICommand Remove
{
get
{
return new ActionCommand(execute => Delete((YourDataType)execute),
canExecute => return ((YourDataType)canExecute).IsInvalid);
}
}
We have the same problem, but it works randomly. A contextmenu inside the controltemplate in a style for a listbox. We have tried to move the contextmenu to different levels inside the template but the same error occurs.
We think it might be connected to the refreshing of our ICollectionView that is the itemssource of the ListBox.
It seems that when the view refreshes the relative source binding inside the contextmenu is being evaluated before the PlacementTarget is being set.
It feels like a bug in either collectionviewsource or the ContextMenu of WPF...
Here is a working standalone XAML-only example based on your test case: a ContextMenu that retrieves a Command from the DataContext of its PlacementTarget using a Tag. You can reintroduce portions of your code until it stops working to try to find where the problem is:
<Grid>
<Grid.Resources>
<PointCollection x:Key="sampleData">
<Point X="10" Y="20"/>
<Point X="30" Y="40"/>
</PointCollection>
<DataTemplate x:Key="_ItemTemplateA">
<Grid Tag="{Binding Path=DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DockPanel}}}">
<TextBlock Text="{Binding X}"/>
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}" CommandParameter="{Binding}"/>
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</DataTemplate>
</Grid.Resources>
<DockPanel DataContext="{x:Static ApplicationCommands.Open}">
<ListBox ItemTemplate="{StaticResource _ItemTemplateA}" ItemsSource="{StaticResource sampleData}"/>
</DockPanel>
</Grid>
In this post,
ContextMenu.PlacementTarget is filled up from ContextMenuService.PlacementTarget when you do right click with mouse on button.
It means ContextMenu.PlacementTarget is filled up when the menu is shown up. You can check that by snoop.
EDIT 1
This code works fine.
<DataGrid.RowStyle>
<Style TargetType="DataGridRow" BasedOn="{StaticResource DataGridRowBaseStyle}">
<Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}, Path=DataContext}"/>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu Style="{StaticResource ContextMenuStyle}"
ItemContainerStyle="{StaticResource MenuItemStyle}">
<MenuItem Header="Enable" Command="{Binding Path=PlacementTarget.Tag.(viewModels:PrinterListPageViewModel.EnableCommand), RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</DataGrid.RowStyle>

Resources