Reputation: 1161
Goal: Right click on ListBox and get a binded contextMenu.
public class MyViewModel
{
public List<string> ContextMenuItems{ get; set; }
public ItemObject MyObject{ get; set; }
}
public class ItemObject{
public List<OtherObjects> SomeCollection{ get; set; }
}
Now my ListBox is Binded to "SomeCollection", but my ContextMenu should access a Binding outside the listbox's Binding. I have tried and cannot get it working at all, my context menu is always empty. Any idea why? This is in UserControl and not a Window, not that it is relevant. I am just pointing out why my AncestorType points to a UserControl
<ListBox ItemsSource="{Binding SomeCollection}">
<ListBox.ContextMenu >
<ContextMenu DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" ItemsSource="{Binding ContextMenuItems}">
<ContextMenu.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding}" Command="{Binding MyCommand}"/>
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
Upvotes: 0
Views: 109
Reputation: 7004
Context menus are notoriously tricky to bind with in WPF. The reason for this is they exist outside of the Visual Tree that the rest of the components do. (If you think about it, this makes sense, as they're in their own pop-up window).
As they're in a different visual tree, useful things like being able to bind by name causes binding errors, as you have discovered. "Cannot find source" means it can't find the named element in the visual tree.
One way to get around it is to use the "PlacementTarget" property of the context menu itself. This refers to the parent control, in which you can access the data context like this:
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}" ItemsSource="{Binding ContextMenuItems}">
I also find if I'm doing anything really complex and need to refer to multiple things in big context menus I like to effectively store the things I want to access in a proxy static resources.
Create a custom proxy class:
public class BindingProxy : Freezable
{
protected override Freezable CreateInstanceCore()
{
return 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), new UIPropertyMetadata(null));
}
In your resources, create a proxy object whenever you have difficulty accessing the right DataContext:
<UserControl.Resources>
<local:BindingProxy x:Key="Proxy" Data="{Binding}" />
</UserControl.Resources>
Then anywhere in your xaml you can access it very easily:
<ContextMenu ItemsSource="{Binding Data.ContextMenuItems, Source={StaticResource Proxy}}">
Upvotes: 1