WPF Databinding ContextMenuItem's CommandParameter to TreeViewItem's DataContext - c#

I am using the Command pattern with (among other things) a context menu on a TreeViewItem, which uses HierarchicalDataTemplates. The MainWindowViewModel (a static resource for the Window) has properties that expose singleton objects that in turn have properties that represent the commands. I am able to execute the command just fine, but some of the commands need to pass the TreeViewItem's DataContext as the CommandParameter.
Here's a specific example:
One node of the tree has the ItemsSource bound to an ObservableCollection of individual AnalysisMain objects. Each of the resulting subnodes has a ContextMenu (with a DataContext bound to the AnalysisController) which has (among others) a Remove MenuItem. The Remove MenuItem's Command property is bound to the CommandRemove command on the AnalysisController singleton object (and it executes just fine). But this also requires the CommandParameter to be bound to the AnalysisMain object that serves as the DataContext for the subnodes in the tree. I have tried using RelativeSource with the AncestorType set to TreeViewItem and the Path set to DataContext:
<HierarchicalDataTemplate DataType="{x:Type vmAnalysis:AnalysisMain}">
<WrapPanel Orientation="Horizontal">
<Image Source="Analysis\Icon_Analysis_Main_16_Normal.png" Margin="0,0,2,0" Width="16"/>
<TextBlock Text="{Binding TreeViewTitle}">
<TextBlock.ContextMenu>
<ContextMenu DataContext="{StaticResource mainWindowViewModel}">
<MenuItem Header="Remove" Command="{Binding Path=AnalysisController.CommandRemove}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type TreeViewItem}, AncestorLevel=4}, Path=DataContext}">
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</WrapPanel>
</HierarchicalDataTemplate>
Without the AncestorLevel set, when I open the ContextMenu, I get the following in the Output window:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.TreeViewItem', AncestorLevel='4''. BindingExpression:Path=DataContext; DataItem=null; target element is 'MenuItem' (Name=''); target property is 'CommandParameter' (type 'Object')
I have tried several values for the AncestorLevel to no avail.
From examining the Visual Tree in Christian Mosers WPF Inspector, I don't see the context menu in the visual tree. Although the TreeViewItem shows the DataContext object. I just need a way to "navigate" to it in order to bind to it.
As an alternative, I have tried leaving the ContextMenu DataContext alone and setting the Command Binding's Source to point back to the AnalysisController. This also works for executing the command, but I am not able to bind to the TreeViewItem's DataContext for the CommandParameter:
<ContextMenu>
<MenuItem Header="Remove" Command="{Binding Source={StaticResource mainWindowViewModel}, Path=AnalysisController.CommandRemove}"
CommandParameter="{Binding Source={RelativeSource Self}, Path=DataContext}">
</MenuItem>
</ContextMenu>
I have also tried just using CommandParameter="{Binding}", which also doesn't work. (In both cases, I just get null sent as the parameter. No warning / error is written to the Output window.) EDIT: For anyone else with this problem, the second option was doomed from the beginning, because I mistakenly put in Source={RelativeSource Self}, which would refer to the MenuItem and not the TreeViewItem. However, changing this with AncestorType / AncestorLevel makes no difference.

You have to use PlacementTarget property of the ContextMenu
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu
DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
</ContextMenu>
</Setter.Value>
</Setter>
And on your MenuItem do a RelativeSource to ContextMenu then use PlacementTarget.DataContext as your binding to your CommandParameter

Related

Problems when binding The Viewmodel to the contextMenu of a control WPF

I am currently having a problem with my project where I pass the binding of the main viewmodel to the contextmenu.
opening the context menu (using the right mouse button obviously) for the first time gives me this error
System.Windows.Data Error: 40 : BindingExpression path error: '(attached:DependencyObjectAttached.DataContextEx)' property not found on 'object' ''TextBlock' (Name='')'. BindingExpression:Path=PlacementTarget.(attached:DependencyObjectAttached.DataContextEx).QuotationCommandProcessor.ConvertProductCommand; DataItem='ContextMenu' (Name=''); target element is 'MenuItem' (Name=''); target property is 'Command' (type 'ICommand')
It may not be super big of a deal since the binding succeeds the second time the context menu opens, but as OCD as I was, I wish to fix this issue.
So here's what I have
I have a Page, inside the page is a Datagrid, the Datagrid has a Column whose cell template is the PlacementTarget of the ContextMenu
The context menu Command binds to a command of the view model of the page
The implementation i used was through attached property like this
<DataGrid ItemSource="{Binding MyItemSources}">
<DataGrid.Columns>
<DataGridTemplateColumn CellTemplate="{StaticResource MyCellStyle}"/>
<DataGrid.Columns>
</DataGrid>
The style looks like this
<DataTemplate x:Key="MyCellStyle">
<TextBlock Text="{Binding}" attached:DependencyObjectAttached.DataContextEx="{Binding DataContext, RelativeSource={RelativeSource AncestorType={x:Type local:MyPage}}}">
<TextBlock.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Click Here To Run Command" Command="{Binding PlacementTarget.(attached:DependencyObjectAttached.DataContextEx).CommandFromTheViewModel, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" CommandParameter="{Binding}"/>
</ContextMenu>
<TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
the DependencyObjectAttached.DataContextEx
attached:DependencyObjectAttached.DataContextEx
is an attached property used to pass the viewmodel to the contextmenu
I have already tried using the Tag of the placementtarget (the Textblock) and it is working fine, however, I am using the Tag for some other purpose so attached property was the only option i can think of. Any suggestion?
Please try below code, this achieves the datacontext access of your main window or Page in your case.
Trick is not to create a DataTemplate and instead create ContextMenu as resource directly and then use this context menu for your DataGridCell as shown below.
<Window.Resources>
<ContextMenu x:Key="ContextMenu1">
<ContextMenu.Items>
<MenuItem Header="{Binding DataContext.Title,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window} }"/>
<MenuItem Header="{Binding DataContext.Title,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" />
<MenuItem Header="{Binding DataContext.Title,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" />
</ContextMenu.Items>
</ContextMenu>
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding VentingTypesCollection}">
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="ContextMenu" Value="{StaticResource ContextMenu1}" />
</Style>
</DataGrid.CellStyle>
</DataGrid>
</Grid>
Please remember, DataContext.Title property is a simple string property from my viewmodel.
I think once you achieve access to datacontext, binding anything from viewmodel will be very straight forward.
Hope this helps you.

WPF Context Menu won't Bind To VIewModel property

I am having a WPF binding issue that I cannot figure out. I have a ContextMenu template that is formatted as shown:
<ContextMenu x:Key="CopyPasteContextMenu">
<MenuItem Header="AlternateDelete"
Command="{Binding Path=PlacementTarget.Tag.DataContext.AlternateDeleteCommand,
RelativeSource={RelativeSource Self}, Mode=OneWay}"/>
</ContextMenu>
The context menu is being used in the DataTemplat, and the binding for the Tag on the Border is finding the PropertyEditorView correctly, I just can't get it from the border to the contextmenu.
<DataTemplate x:Key="PropertyValueCellViewingTemplate" DataType="viewModels:IConfigurationItemViewModel">
<Border x:Name="ValueCellBorder"
Tag="{Binding RelativeSource={RelativeSource AncestorType={x:Type views:PropertyEditorView}}}"
ContextMenu="{StaticResource CopyPasteContextMenu}"
Style="{StaticResource PropertyGridValueCellBorderStyle}">
(...)
</Border>
</DataTemplate>
The tag can bind properly to my view model which is called “PropertyEditorViewModel”. I can see this while debugging the system in the visual tree. When I drill into my Context Menu, the binding is not happening properly.
For my Command to work, I need it to bind properly to the Command to PropertyEditorView view model command called “AlternateDeleteCommand”.
public class PropertyEditorViewModel : DisposableViewModelBase, IPropertyEditorViewModel
{
public ICommand AlternateDeleteCommand { get; set; }
Looked at this for a day so far, and not sure why my binding isn't working on the context menu, anyone got something I'm missing?
Thanks!
Doesn't the relative source need to be on the context menu and not on the menu item? Since you are checking the placement target of the context menu?
<MenuItem Header="AlternateDelete"
Command="{Binding Path=PlacementTarget.Tag.DataContext.AlternateDeleteCommand,
RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Mode=OneWay}" />

Command in a MenuItem parent not executing when having MenuItem childs

I have this code structure in a C# WPF program:
<MenuItem Header="father"
Visibility="{Binding Path=IsEnabled,
RelativeSource={RelativeSource Self},
Converter ={StaticResource BoolToVisibleConverter}}"
Command="{Binding SetFatherCommand}">
<MenuItem Header="son1"
Command="{Binding SetSon1Command}"
CommandParameter="{x:Static Types:CableType.Phase1}"
Visibility="{Binding Path=IsVisible, Converter={StaticResource BoolToVisibleConverter}}"/>
</MenuItem>
When I click on the MenuItem father the command SetFatherCommand is not executed but if I comment the lines corresponding to the MenuItem son1 the command from the MenuItem father will be executed.
I am a bit lost, I think the command binding in the child is somehow affecting the command binding in the father but I don´t understand why and how can I solve it. I will appreciate any input.
The Command property does not work on MenuItem's which have sub MenuItem's

ContextMenu in Window Resources, bind to DataGrid property

I would like to reuse a ContextMenu on several DataGrid.
So I placed the context menu in the Resources of my Window.
I have trouble to bind to the SelectedItem property of the DataGrid on which the ContextMenu is placed.
In this example, I'm trying to have the Name property of the SelectedItem displayed in the context menu.
<Window.Resources>
<ContextMenu x:Key="DgContextMenu"
DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="{Binding SelectedItem.Name, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</ContextMenu>
</Window.Resources>
<DataGrid ItemsSource="{Binding CollectionView}"
ContextMenu="{StaticResource DgContextMenu}"
Tag="{Binding DataContext, RelativeSource={RelativeSource Self}}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Name" Binding="{Binding Name}" />
</DataGrid.Columns>
</DataGrid>
Thanks in advance
The way you have written your example has binding error and that's why your context menu doesn't work. You have binded menu item header to SelectedItem.Name of ContextMenu object which doesn't have SelectedItem property (you can tell that from RelativeSource part of the menu item binding). One possible solution, among others, would be to bind DataContext of ContextMenu to DataGrid through PlacementTarget (not PlacementTarget.Tag). Since child controls „inherit“ DataContext of the parent you can just specify Path in the menu item binding. This is how it would look:
<Window.Resources>
<ContextMenu x:Key="DgContextMenu"
DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}">
<MenuItem Header="{Binding Path=SelectedItem.Name}" />
</ContextMenu>
</Window.Resources>
<DataGrid ItemsSource="{Binding CollectionView}"
ContextMenu="{StaticResource DgContextMenu}"
>
</DataGrid>
Basically you can find those errors if you run application in VS debugger and watch output in Output window (Debug -> Window -> Output). In output window you should look for System.Windows.Data Error line and in that line you will see the type of an object and property you are trying to bind and that will give you a clue what's wrong with your binding in XAML.

WPF Databinding with RelativeSource and AncestorType

I am trying to get some binding code working. Bascially I want to bind the IsEnabled property of an element of my grid's context menu with a value of the selected row in the grid.
I have it working with this:
<my:DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.SelectedItem, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Grant Access" IsEnabled="{Binding Connectable}"/>
</ContextMenu>
</my:DataGrid.ContextMenu>
But I want to do it this way and it's not working. It doesn't error but just doesn't disable the menu item. Any idea why?
<my:DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Grant Access" IsEnabled="{Binding Path=SelectedItem.Connectable, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type my:DataGrid}}}"/>
</ContextMenu>
</my:DataGrid.ContextMenu>
Try using ElementName binding instead of Ancestor binding. ContextMenu is not part of Grid's visual tree.
--edit--
Ah, I was wrong. ElementName binding (example given below) would also not work with ContextMenu. It is not part of DataGrid's visual tree. That is why it cannot see that DataGrid and thus cannot reference it. You will have to do it using the first method.
Any reason why you don't want to do it that way?
<DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding SelectedItem, ElementName=DataGrid1}">
<MenuItem Header="Grant Access"
IsEnabled="{Binding Connectable}" />
</ContextMenu>
</DataGrid.ContextMenu>
If you look at the output window in Visual Studio while in Debug mode, you'll get details of the binding error - which may shed some light on your problem.

Categories

Resources