37

I am having some trouble figuring out how to set the correct DataContext on a ContextMenu.

I have a collection of view models who are the source of an ItemsControl. Each view model has a collection of items which are also the source of another ItemsControl. Each item is used to draw image which has a ContextMenu. The MenuItems in that ContextMenu need to bind to a command on the view model, but the PlacementTarget of the ContextMenu is pointing to the individual item.

My Xaml looks something like this:

<ItemsControl ItemsSource="{Binding Markers"}>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <ItemsControl ItemsSource="{Binding Items}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Image>
                            <Image.ContextMenu>
                                <ContextMenu>
                                     <MenuItem Header="Edit" Command="{Binding EditCommand}" />
                                </ContextMenu>
                            </Image.ContextMenu>
                        </Image>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

How can I set the DataContext of the ContextMenu to the item's corresponding parent view model?

Ashley Grenon
  • 9,305
  • 4
  • 41
  • 54

4 Answers4

52

The ContextMenu is outside of the visual tree. Below is the xaml that should get you the datacontext:

<ItemsControl ItemsSource="{Binding Markers}" Tag="{Binding ElementName=outerControl, Path=DataContext}">
   ...
   <ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
      <MenuItem Header="Edit"
                Command="{Binding EditCommand}" />
   </ContextMenu>
   ...
</ItemsControl>

This post explains how this works.

kevindaub
  • 3,293
  • 6
  • 35
  • 46
  • 1
    The problem with this is I do not want to bind to what the PlacementTarget is. I want to bind to the DataContext of the outer control. – Ashley Grenon Feb 22 '13 at 21:48
  • Are you sure they don't have the same DataContext (ie outerControl and inner itemsControl)? – kevindaub Feb 22 '13 at 21:51
  • 2
    Yes, the DataContext I get using PlacementTarget is a level too deep. I get back the an item, but what I need is the view model that has the collection that contains that item. If I could bind to the DataContext of the outer control, that would be perfect. – Ashley Grenon Feb 22 '13 at 21:55
  • I guess it would be, I need the DataContext of the Inner ItemsControl, but that is the DataContext of the outer control to the Image. – Ashley Grenon Feb 22 '13 at 21:56
  • I have gotten around this by storing the outercontrol's datacontext in the Tag property of the innercontrol. This should allow you to use the PlacementTarget.Tag. – kevindaub Feb 22 '13 at 21:57
  • it's not the most elegant thing, but using Tag works :) If you could update your answer, I'll mark this as correct. – Ashley Grenon Feb 22 '13 at 22:04
  • Yeah, my thoughts exactly, but that's the best I've been able to do. – kevindaub Feb 22 '13 at 22:14
  • 1
    **Be careful!** Don't put `Mode=OneTime`on the `ContextMenu`'s `DataContext` value as I did. This will cause the bindings to fail for some reason. – NathanAldenSr Jun 21 '16 at 03:16
17

You can use a markupextension:

using System;
using System.Windows.Controls;
using System.Windows.Markup;
using System.Xaml;

[MarkupExtensionReturnType(typeof(ContentControl))]
public class RootObject : MarkupExtension
{
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        var rootObjectProvider = (IRootObjectProvider)serviceProvider.GetService(typeof(IRootObjectProvider));
        return rootObjectProvider?.RootObject;
    }
}

It lets you do:

<ItemsControl ItemsSource="{Binding Markers}">
   ...
   <ContextMenu DataContext="{Binding DataContext, Source={local:RootObject}}">
      <MenuItem Header="Edit"
                Command="{Binding EditCommand}" />
   </ContextMenu>
   ...
</ItemsControl>
Johan Larsson
  • 17,112
  • 9
  • 74
  • 88
  • Nice idea, but maybe this only works for menus within an `ItemsControl`? I could not get it working in a `ContentPresenter` for example. – g t Feb 18 '20 at 07:42
2

I don't like use Tag. I prefer attached property.

You need add attached property:

public static readonly DependencyProperty DataContextExProperty =
   DependencyProperty.RegisterAttached("DataContextEx",
                                       typeof(Object), 
                                       typeof(DependencyObjectAttached));

public static Object GetDataContextEx(DependencyObject element)
{
    return element.GetValue(DataContextExProperty);
}

public static void SetDataContextEx(DependencyObject element, Object value)
{
    element.SetValue(DataContextExProperty, value);
}

In XAML:

<Button attached:DependencyObjectAttached.DataContextEx="{Binding ElementName=MyDataContextElement, Path=DataContext}">
    <Button.ContextMenu>
        <ContextMenu DataContext="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget.(attached:DependencyObjectAttached.DataContextEx)}">
        </ContextMenu>
    </Button.ContextMenu>
</Button>
g t
  • 7,287
  • 7
  • 50
  • 85
Smagin Alexey
  • 305
  • 2
  • 6
0

This code allows you to use both the global and local DataContext (of entire userControl and of current TreeViewItem):

<TreeView Grid.Row="0" ItemsSource="{Binding Path=SelectableEnterprises}">
<TreeView.ItemTemplate>
    <HierarchicalDataTemplate ItemsSource="{Binding Children}">
        <Grid Height="20" Tag="{Binding DataContext, ElementName=userControl}">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="25"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Grid.ContextMenu>
                <ContextMenu DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}" >
                    <ContextMenu.Visibility>
                        <MultiBinding Converter="{converters:SurveyReportVisibilityConverter}">
                            <Binding Path="DataContext"/> <!--local DataContext-->
                            <Binding Path="Tag.SelectedSurvey"/> <!--global DataContext-->
                        </MultiBinding>
                    </ContextMenu.Visibility>

                    <MenuItem Header="Show HTML-report" Command ="{Binding Tag.OpenHTMLReportCommand}" 
                              CommandParameter="{Binding DataContext}" />
                </ContextMenu>
            </Grid.ContextMenu>
Gennady Maltsev
  • 371
  • 2
  • 5