I have a static resource defined as follows:
<ContextMenu x:Key="TestContextMenu" DataContext="{Binding Path=PlacementTarget, RelativeSource={x:Static RelativeSource.Self}}">
<MenuItem Command="ApplicationCommands.Cut"/>
<!--<ContextMenu.ItemsSource>
<CompositeCollection>
<MenuItem Command="ApplicationCommands.Cut"/>
</CompositeCollection>
</ContextMenu.ItemsSource>-->
</ContextMenu>
My application works fine like this. However, I want to be able to add extra items to the context menu. So instead of adding menu items I want to use a CompositeCollection and then I run into binding issues. I minimized the problem and ended up with this. When I comment the MenuItem and uncomment the ContextMenu.ItemSource I get this error:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.ItemsControl', AncestorLevel='1''. BindingExpression:Path=HorizontalContentAlignment; DataItem=null; target element is 'MenuItem' (Name=''); target property is 'HorizontalContentAlignment' (type 'HorizontalAlignment')
If the datacontext is correct in the first situation why is it not correct anymore in the second situation?
Edit: I don't want to add items to the contextmenu dynamically. I want to use this contextmenu as kind of a 'base context menu' providing cut/copy and paste from a composite collection resource. In some places I want more than just these three and there I could use a custom context menu that use that same collection combined with those extra items. Just to clarify this is the xaml I have in mind, but I cut the problem down to the simpler piece above.
<CompositeCollection x:Key="TreeViewItemContextMenuItems">
<MenuItem Command="ApplicationCommands.Cut" CommandTarget="{Binding}"/>
<MenuItem Command="ApplicationCommands.Copy" CommandParameter="{Binding}"/>
<MenuItem Command="ApplicationCommands.Paste" CommandParameter="{Binding}"/>
<Separator/>
<MenuItem Command="ApplicationCommands.Delete" CommandParameter="{Binding}"/>
</CompositeCollection>
<ContextMenu x:Key="TreeViewItemContextMenu" DataContext="{Binding Path=PlacementTarget, RelativeSource={x:Static RelativeSource.Self}}">
<ContextMenu.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{StaticResource TreeViewItemContextMenuItems}" />
</CompositeCollection>
</ContextMenu.ItemsSource>
</ContextMenu>
If you want to dynamically populate ContextMenu the better solution is do to as follows:
<ContextMenu
x:Key="TestContextMenu"
ItemsSource="{Binding MenuItems}">
</ContextMenu>
I removed DataContext="{Binding Path=PlacementTarget...}}" and added this ItemsSource="{Binding MenuItems}". MenuItems is a property of an object used as a data context. Here is an example of usage:
<Window>
...
<ItemsControl ContextMenu="{StaticResource ResourceKey=TestContextMenu}">
...
</ItemsControl>
</Window
In this case the context menu will inherit a data context from ItemsControl which in turn will inherit a data context from Window.
If you don't want to remove DataContext="{Binding Path=PlacementTarget...}}" then use the following code:
<ContextMenu
x:Key="TestContextMenu"
DataContext="{Binding Path=PlacementTarget, RelativeSource={x:Static RelativeSource.Self}}"
ItemsSource="{Binding DataContext.MenuItems}">
</ContextMenu>
EDIT:
You receive these errors because MenuItem controls by default try to bind some of their properties (HorizontalContentAlignment and VerticalContentAlignment) to ItemsControl using RelativeSource. The problem is that their are embeded inside CompositeCollection which doesn't support this kind of binding - see this article.
The problem will occure even if you override this binding in your XAML in this way:
<MenuItem Command="ApplicationCommands.Cut" CommandTarget="{Binding}"
HorizontalContentAlignment="Center" VerticalContentAlignment="Center"/>
Probably there is some workaround of this problem but personally I'll ignore these errors. They don't spoil anything in your application, don't they?
Related
As the title says, I'm trying to bind a function to the RecordContextMenu of a Syncfusion SfTreeGrid, the problem is that it's not working on this particular element, every time I tried binding the function the "No target found for method" exception is thrown.
I´ve tried the Tag and TargetWithoutContext method on a "regular" SfTreeGrid ContextMenu and it works without problems
<syncfusion:SfTreeGrid Tag="{Binding DataContext, ElementName=Container}">
...
...
<syncfusion:SfTreeGrid.ContextMenu>
<ContextMenu Name="cm" cal:Action.TargetWithoutContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Test Action"
cal:Message.Attach="Test"/>
</ContextMenu>
</syncfusion:SfTreeGrid.ContextMenu>
The Container is a Grid
Now when using the same method on the RecordContextMenu the exception is thrown
<syncfusion:SfTreeGrid.RecordContextMenu>
<ContextMenu Name="cm" cal:Action.TargetWithoutContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Test Action"
cal:Message.Attach="Test"/>
</ContextMenu>
</syncfusion:SfTreeGrid.RecordContextMenu>
Maybe I'm using the wrong RelativeSource here but I don't know how can I solve it.
PS: Already read the documentation for the SfTreeGrid and I know that you can bind the commands using the VM as a StaticResource but that gives me a new instance of the VM making my properties inaccessible.
I have TreeView with HierarchicalDataTemplate. On TreeView I have ContextMenu
<TreeView Name="_packageTreeView" ItemsSource="{Binding PackageExtendedList}" Behaviors:TreeViewInPlaceEditBehavior.IsEditable="True">
<TreeView.ContextMenu>
<ContextMenu StaysOpen="true">
<MenuItem Header="Добавить пакет" Height="20" Command="{Binding AddPackageCommand}"
CommandParameter="{Binding ElementName=_packageTreeView, Path=SelectedItem}">
<MenuItem.Icon>
<Image Source="/Resources/ManualAdd.png" Width="15" Height="15"></Image>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</TreeView.ContextMenu>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Childs}">bla bla bla</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
As you can see, I bind Command to menu item. AddPackageCommand defined in ViewModell class as usually. Invoke command works fine, but I always have null in CommandParameter. I found some questions similar to my, but I don't understand solutions. For example:
CommandParameters in ContextMenu in WPF
Anyway it doesn't work for me :( What am I doing wrong?
Updated
That seems to be working, but it's all the same, I don't understand why CommandParameter doesn't work with TreeView.Name.
CommandParameter="{Binding PlacementTarget, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"
for examplle, such a sample works fine
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding PackageTreeItemChangeCommand}" CommandParameter="{Binding ElementName=_packageTreeView, Path=SelectedItem}"/>
</i:EventTrigger>
What's the hell...
And anyway, I have TreeView object in CommandParameter, not TreeViewItem. I can get SelectedItem from TreeView, but how can I send exactly TreeViewItem as CommandParameter?
to Sheridan
Question was WHY this doesn't work.
CommandParameter="{Binding ElementName=_packageTreeView, Path=SelectedItem}"
And this works
CommandParameter="{Binding PlacementTarget, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"
WHY sometimes I can use direct TreeView control name and sometimes I cannot.
As I understand, matter is different DataContext of TreeView control and ContextMenu because ContextMenu has its own VisualTree and it is not the part of TreeView ViaualTree.
Unfortunately, that approach doesn't work too, I have null again. I set TreeView.Tag, sure.
<ContextMenu DataContext="{Binding PlacementTarget.Tag, RelativeSource={
RelativeSource Self}}" StaysOpen="true">
<MenuItem Header="Добавить пакет" Height="20" Command="{Binding AddPackageCommand}"
CommandParameter="{Binding ElementName=_packageTreeView, Path=SelectedItem}">
<MenuItem.Icon>
<Image Source="/Resources/ManualAdd.png" Width="15" Height="15"></Image>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
This is the easiest way, but if I have SelectedItem property in ViewModel it has no sense bind it to CommandParameter, because I already have it in ViewModel.
<MenuItem Header="Добавить пакет" Height="20" Command="{Binding AddPackageCommand}"
CommandParameter="{Binding SelectedItem}">
<MenuItem.Icon>
<Image Source="/Resources/ManualAdd.png" Width="15" Height="15"></Image>
</MenuItem.Icon>
</MenuItem>
You showed us that you already have an answer... why on earth did you post yet another question on this same subject instead of simply following the example in the answer? It doesn't work for you, because you didn't copy the answer properly.
In your example post answer, the Tag property is set to the TreeView control that the menu is applied on, but you haven't done this.
Your next problem is that you have ignored this Tag property again in the CommandParameter... somehow, you have changed this from the correct answer:
CommandParameter="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource
FindAncestor, AncestorType={x:Type ContextMenu}}}
to this in your question:
CommandParameter="{Binding PlacementTarget, RelativeSource={RelativeSource
FindAncestor, AncestorType={x:Type ContextMenu}}}"
All you needed to do was copy and paste it. All the same, you might have even more luck doing this:
<TreeView Tag="{Binding DataContext, RelativeSource={RelativeSource Self}}"
Name="_packageTreeView" ItemsSource="{Binding PackageExtendedList}"
Behaviors:TreeViewInPlaceEditBehavior.IsEditable="True">
<TreeView.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.Tag, RelativeSource={
RelativeSource Self}}" StaysOpen="true">
<MenuItem Header="Добавить пакет" Height="20" Command="{Binding AddPackageCommand}"
CommandParameter="{Binding ElementName=_packageTreeView, Path=SelectedItem}">
<MenuItem.Icon>
<Image Source="/Resources/ManualAdd.png" Width="15" Height="15"></Image>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</TreeView.ContextMenu>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Childs}">bla bla bla</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Look at the TreeView.Tag property... this is set to its own DataContext, which means that whatever is set as the DataContext of the TreeView is now available in the Tag property object.
Next, look at the ContextMenu.DataContext property... this is set to the Tag property of the PlacementTarget, which is the control that the ContextMenu is applied to, or in this case, the Treeview.
If you haven't worked it out yet, this means that the DataContext of the ContextMenu is now set to the same object as the DataContext of the TreeView. If this is not what you want because your Command is on a different object, then just change the Binding path in the Tag property to point to wherever the object that had the Command is.
The last thing that you can do to make this simpler is to add a property to your view model/code behind that binds to the TreeView.SelectedItem property:
<TreeView SelectedItem="{Binding SelectedItem}"... />
Then you can simply refer to this property for your CommandParameter:
<MenuItem Header="Добавить пакет" Height="20" Command="{Binding AddPackageCommand}"
CommandParameter="{Binding SelectedItem}">
<MenuItem.Icon>
<Image Source="/Resources/ManualAdd.png" Width="15" Height="15"></Image>
</MenuItem.Icon>
</MenuItem>
This last part of course assumes that you have set your view model/code behind as the Tag property of the TreeView. If you still don't understand this, take a look at the Context Menus in WPF page on WPF Tutorial.NET.
UPDATE >>>
I simply don't understand why you posted this question. First you said you couldn't do something, but then provided us with a link to a valid solution in another post. After trying to help you, you then say that it did work, but you don't know why... but then you correctly answered your own question again:
As I understand, matter is different DataContext of TreeView control and ContextMenu because ContextMenu has its own VisualTree and it is not the part of TreeView ViaualTree.
As you said, the ContextMenu has its own visual tree. This means that it is not aware of controls, named or otherwise, in another visual tree. However, if the ContextMenu.DataContext is provided with an object such as the containing view, then it can be aware of controls in another visual tree (more specifically, the visual tree of the controls in the view).
This whole issue seems to be down to a lack of knowledge on your part about Binding in general and Binding.Path syntax more specifically. Please take a look at the following articles on MSDN for more help on this topic:
Binding.Path Property
Property Path Syntax
RelativeSource MarkupExtension
So many people try to run with WPF before they can walk.
I have 3 listboxes all bound two 3 separate observable collections of the same type. My ViewModel has the observable collections exposed via properties. This is for some drag and drop grouping, source list box can have items dragged onto two different lists. But I want to give the user the ability to right click on a listboxitem and set the item's properties. Things like Type, Name, etc. I am using a data template in the since I want all three boxes to be the same in functionality. This works well, and I can get the context menu to pop up when I click on individual items with no problem. My trouble is that I have one propery called FieldType. It is an enum that has 4 potential values. I can't, for the life of me, figure out how to bind the IsChecked property of the MenuItem to that property... functionally anyway. Here is what I have tried....
<DataTemplate x:Key="SFTemplateWithContextMenu">
<TextBlock Text="{Binding Path=FieldName}" ><!--Tag="{Binding DataContext, ElementName=Window}"-->
<TextBlock.ContextMenu>
<ContextMenu >
<ContextMenu.Resources>
<Configurator:EnumToBooleanConverter x:Key="EnumToBooleanConverterc" />
</ContextMenu.Resources>
<MenuItem Header="Rename..." />
<MenuItem Header="Field Type">
<MenuItem.Resources>
<Configurator:EnumToBooleanConverter x:Key="EnumToBooleanConverter" />
</MenuItem.Resources>
<MenuItem Header="String" IsCheckable="True" IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=DataContext.FieldType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource EnumToBooleanConverterc}, ConverterParameter={x:Static Configurator:TypeDesc.String}, PresentationTraceSources.TraceLevel=High}"/>
<MenuItem Header="Date" IsCheckable="True" IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=DataContext.FieldType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static Configurator:TypeDesc.Date}}"/>
<MenuItem Header="Barcode" IsCheckable="True" IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=DataContext.FieldType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static Configurator:TypeDesc.BarCode}}" />
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
In the code above you can see on the String, Date, and Barcode MenuItems what I was trying to do (gotta love code that is a work in process). My issue is the exposed property that it should call. I don't know how, in my ViewModel property, to get to the item in the observable collection that corresponds to the item clicked. I have a value converter EnumToBoolean that will sit on the binding to get the checked or not. The problem is the property that is setting/getting that particular item in the observable collection.
Any thoughts? Need more code? Need me to clarify anything? How close am I? By the way, the ViewModel code is written in VB 2010.
Thanks
Bryce
EDIT:
I have tried the following using Angel's suggestion...
<DataTemplate x:Key="SFTemplateWithContextMenu">
<TextBlock x:Name="Field" Text="{Binding Path=FieldName}" >
<TextBlock.ContextMenu PlacementTarget="{Binding ElementName=Field}">
<MenuItem Header="Rename..." />
<MenuItem Header="Field Type">
<MenuItem.Resources>
<Configurator:EnumToBooleanConverter x:Key="EnumToBooleanConverter" />
</MenuItem.Resources>
<MenuItem Header="Date" IsCheckable="True" IsChecked="{Binding PlacementTarget.DataContext.FieldType, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static Configurator:TypeDesc.String}, PresentationTraceSources.TraceLevel=High}"/>
</MenuItem>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
But this gives me an error that says...Cannot set properties on property elements. Not sure if this is a TextBlox vs TextBox issue? You used TextBox in your example... my guess is that your code would do the same. So I then tried the following...
<DataTemplate x:Key="SFTemplateWithContextMenu">
<TextBlock x:Name="Field" Text="{Binding Path=FieldName}" ><!--Tag="{Binding DataContext, ElementName=Window}"-->
<TextBlock.ContextMenu>
<ContextMenu PlacementTarget="{Binding ElementName=Field}" >
<MenuItem Header="Rename..." />
<MenuItem Header="Field Type">
<MenuItem.Resources>
<Configurator:EnumToBooleanConverter x:Key="EnumToBooleanConverter" />
</MenuItem.Resources>
<MenuItem Header="Date" IsCheckable="True" IsChecked="{Binding PlacementTarget.DataContext.FieldType, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static Configurator:TypeDesc.String}, PresentationTraceSources.TraceLevel=High}"/>
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
But this causes binding errors...
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=Field'. BindingExpression:(no path); DataItem=null; target element is 'ContextMenu' (Name=''); target property is 'PlacementTarget' (type 'UIElement')
So it appears that the binding is not working. Any thoughts?
ContextMenu is not part of visual tree. So it wont, by default, connect \ bind to the datacontext of the TextBlock on which it is applied...
So 2 ways to do this...
Set ContextMenu.PlacementTarget and refer that as the Path in individual MenuItem's Binding.
e.g.
<TextBox x:Name="MyTextBlock">
<TextBox.ContextMenu PlacementTarget="{Binding ElementName=MyTextBlock}">
<MenuItem
Header="{Binding PlacementTarget.DataContext.MyHeader,
RelativeSource={RelativeSource
AncestorType={x:Type ContextMenu}}}"
</TextBox.ContextMenu>
</TextBox>
So in the example above... you want to connect menu item with the data context of the text box. So you define PlacementTarget on the ContextMenu. This placement target can only be set with 2 types of bindings... ElementName or StaticResource. And once the context menu is connected to the visual element via PlacementTarget, use the Path in the binding of the meuitem to resolve the data context property i.e. MyHeader.
OR
Use proxy element approach...
Bind datagrid column visibility MVVM
I have a treeview bound to a Observable collection of some property type. There is a HierarchicalDataTemplate that shows the data in treeview. Now i need to show specific context menu for each HierarchicalDataTemplate item.
I am using the following XAML to show context menu:
<HierarchicalDataTemplate ItemsSource="{Binding Collections}">
<TextBlock Text="{Binding Path=Name}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Create" Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.AddCommand}" CommandParameter="{Binding}"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</HierarchicalDataTemplate>
Here the AddCommand is written in the view model that is bound to this under control..
I am able to see the context menu, but event is not firing on click on menu item.
Please help..
Your command binding will not work because the ContextMenu is not on the same logical tree as your UserControl is, therefore it will not find the UserControl ancestor. However your ContextMenu should inherit its container's datacontext automatically.
So this should work -
<ContextMenu>
<MenuItem Header="Create" Command="{Binding AddCommand}" CommandParameter="{Binding}"/>
</ContextMenu>
However the AddCommand property should exist on your HierarchicalDataTemplate bound item.
EDIT:
If your Command is not defined in your HierarchicalDataTemplate's bound item and instead in your UserControl. Then another think you may try is giving your UserControl a name, and then bind the command to it by ElementName. Like this
Updated again:
<ContextMenu>
<MenuItem Header="Create" Command="{Binding ElementName="MyUserControl" Path="DataContext.AddCommand"}" CommandParameter="{Binding}"/>
</ContextMenu>
I'm trying to bind to a property of a container from inside a DataTemplate. A simplified version of my markup looks like:
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type myCustomItem}">
<!--Visual stuff-->
<StackPanel>
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="Add Item"
Command="{Binding myCustomItemsICommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type CustomContainerType}}, Path=ContainerProperty}"/>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</DataTemplate>
</Grid.Resources>
<CustomContainerType/>
</Grid>
My approach is based on this post but it doesn't seem to be working. The issue seems to arise from the placement of the ContextMenu within the visual tree. Basically I am trying to bind the Command to the DataContext of the DataTemplate but bind the CommandParameter to a DataContext outside the DataTemplate.
ContextMenus are not in the same visual tree as the rest of the controls, there are a few questions regarding how to do bindings accross that boundary but this might be somewhat difficult without specifying names.
ElementName fails as well because of the lacking tree connection, but you could use x:Reference in the Binding.Source instead.