WPF ContextMenu woes: How do I set the DataContext of the ContextMenu?
Asked Answered
M

4

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?

Mazur answered 22/2, 2013 at 21:30 Comment(0)
W
55

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.

Weinhardt answered 22/2, 2013 at 21:47 Comment(8)
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.Mazur
Are you sure they don't have the same DataContext (ie outerControl and inner itemsControl)?Weinhardt
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.Mazur
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.Mazur
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.Weinhardt
it's not the most elegant thing, but using Tag works :) If you could update your answer, I'll mark this as correct.Mazur
Yeah, my thoughts exactly, but that's the best I've been able to do.Weinhardt
Be careful! Don't put Mode=OneTimeon the ContextMenu's DataContext value as I did. This will cause the bindings to fail for some reason.Reinstate
M
16

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>
Matrimony answered 2/11, 2015 at 20:8 Comment(2)
Nice idea, but maybe this only works for menus within an ItemsControl? I could not get it working in a ContentPresenter for example.Hobbs
This worked for me whereas lots didn't. Thanks. 👍Eadie
J
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>
Jehu answered 19/7, 2016 at 10:49 Comment(0)
R
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>
Reticule answered 21/10, 2020 at 11:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.