Going Up.
Accessing ContextMenus via RightClick is a very common (and expected) paradigm used in Windows desktop apps. WPF provides the ContextMenu element and you normally “attach it” in the XAML through the ContextMenu Property of some visual.
<Image.ContextMenu> <ContextMenu ItemsSource="{Binding TileConfigItems}" ItemTemplateSelector="{StaticResource templateSelector}" ItemContainerStyle="{StaticResource StretchMenuItem}"/> </Image.ContextMenu>
A very interesting (and surprising) thing to know about the ContextMenu is that it is essentially a separate “Visual Tree.” This means that if you try to use the RelativeSource syntax in your ContextMenu Binding to traverse upwards to find the DataContext of some Parent visual you will be limited to the ContextMenu itself! What I’m trying to say is that using RelativeSource Mode=AncestorType or AncestorLevel etc … will not be able to access anything other than the visuals within the ContextMenu definition, with the ContextMenu visual being the root.
Nice Place You’ve Got Here.
To solve this problem Microsoft provides the PlacementTarget DependencyProperty to allow you a way to “jump” to the Visual Tree that had invoked the ContextMenu.
In this example a simple context menu is being invoked on a Button within a UserControl. By using the PlacementTarget you can access the Button.DataContext. Without the PlacementTarget you would have no way of getting any farther than the ContextMenu.DataContext.
<Button Grid.Row="1" Content="Right Click For Context Menu"> <Button.ContextMenu> <ContextMenu> <MenuItem Header="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Grid}}, Path=DataContext.ZooName}" /> <MenuItem Header="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.DataContext.ZooName}" /> </ContextMenu> </Button.ContextMenu> </Button>
The Button gets its DataContext from the UserControl. The UserControl has its DataContext set to a ViewModel (ZooVM.cs) which exposes a public string property ZooName. To illustrate how PlacementTarget allows you get to the underlying visual’s tree, I attempt to set the MenuItem.Header to the ZooName property of the Button’s DataContext. As you can see from the screen shot the first MenuItem Binding doesn’t display the ZooName but the second does.
How come?
The reason the second binding works is because I am employing the PlacementTarget of the ContextMenu, which in this case is the Button. In the first binding I am attempting to traverse to AncestorType = Button. Since the binding starts in the MenuItem the “Max Ancestor” it can get to is the ContextMenu.
OK, Simple Enough. Not!
As the title of this article suggests, using the PlacementTarget won’t help you when you need to get to an “outer” DataContext in a nested situation. (i.e. Grid, ListBox, ListItem)
This diagram below illustrates a typical WPF pattern. The “enclosing” UserControl has a DataContext of ZooVM. When you bind the ItemSource of an ItemsControl to ZoomVm.Cages WPF sets the DataContext to CageVM for the list’s items. Similarly, when you bind an additional ItemsControl to the CageVM.Animals WPF sets the DataContext for each item to an individual Animal.
As I demonstrated in the previous example, the PlacementTarget allows you to get to the VisualTree that your ContextMenu was nested within. But . . . there is no way using XAML to get the Ancestor of the PlacementTarget. As the nested diagram shows, you can’t reach the ZooVM from a PlacementTarget of Animal.
Tag, you’re it!
I reasoned that I would be able to access the Ancestral DataContext of the PlacementTarget if I could somehow store it in an exposed property. My solution was to use a strategy from my old Win/Office Forms days.
Just like in WinForms, WPF has many elements that support a Tagproperty. You can populate the Tag with pretty much anything. I store the Ancestors DataContext in the Tag property of the “inner ListBox” so I can access it from the ContextMenu’s PlacementTarget.Tag.
The following XAML is from the definition of the DataTemplate for a ListItem in the “innermost ListBox” of the diagram. It’s DataContext is {Animal}. The Label is in the VisualTree of the UserControl, so I can use the RelativeSource AncestorType syntax to access the WrapPanel’s DataContext, which is ZooVM.
<Label Content="{Binding Name}" Tag="{Binding RelativeSource={RelativeSource AncestorType={x:Type WrapPanel}}, Path=DataContext}">
Now that the desired DataContext has been stored I can access it from the ContextMenu’s PlacementTarget without any problem.
<Label.ContextMenu> <ContextMenu > ... <MenuItem Header="Option 2" Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.Tag.OuterVMCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Header}" /> </ContextMenu> </Label.ContextMenu>
So there you have it!
I have attached a sample app for your enjoyment. 🙂
[download id=”167″]