WPF: DataContext for ContextMenu


Problem

Often times WPF  developers want to throw their computer out the window because of a small and very irritating truth about the ContextMenu control. I’m talking about the fact that ContextMenu is not in the same visual tree as its containing parent, resulting in many data binding issues. Since the ContextMenu is not in the same visual tree, ElementName, RelativeSouce (FindAncestor), etc bindings will not work. You can get around this through the use of the PlacementTarget property and some complex DataContext re-routing, but it is a pain, confusing, and does not scale well (at all). I will present a very simple attached property that relieves the situation.

Solution

The idea is simple and follows the pattern set fourth in the built-in ContextMenuService class. ContextMenuService has several attached properties that you set on the containing element that affect the behavior of its ContextMenu. The ContextMenuServiceExtensions class adds one more attached property to this tool-set: ContextMenuServiceExtensions.DataContext. When you set this attached property on any FrameworkElement or derivative, it immediately sets the DataContext property on the element’s ContextMenu to the value set in the attached property, which can be a binding. Its easier to explain in code.

public static class ContextMenuServiceExtensions
{
    public static readonly DependencyProperty DataContextProperty =
        DependencyProperty.RegisterAttached("DataContext",
        typeof(object), typeof(ContextMenuServiceExtensions),
        new UIPropertyMetadata(DataContextChanged));

    public static object GetDataContext(FrameworkElement obj)
    {
        return obj.GetValue(DataContextProperty);
    }

    public static void SetDataContext(FrameworkElement obj, object value)
    {
        obj.SetValue(DataContextProperty, value);
    }

    private static void DataContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        FrameworkElement frameworkElement = d as FrameworkElement;
        if (frameworkElement == null)
            return;

        if (frameworkElement.ContextMenu != null)
            frameworkElement.ContextMenu.DataContext = GetDataContext(frameworkElement);
    }
}

Example Usage

Below is an example of how to use the attached property. The example shows setting the DataContext property on the ContextMenu to the DataContext of the Button using the attached property, and then binding to the source within the ContextMenu.

<Button Content="Right-click me!"
        local:ContextMenuServiceExtensions.DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Button.ContextMenu>
        <ContextMenu ItemsSource="{Binding SomeItemsSourcePropertyOnTheButton}">
            <ContextMenu.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding ItemDisplayName}" />
                </DataTemplate>
            </ContextMenu.ItemTemplate>
        </ContextMenu>
    </Button.ContextMenu>
</Button>

Conclusion

The attached property demonstrated here saves a lot of time and allows for much cleaner xaml markup. The only limitation here is that logic for the ContextMenu property being reset or being set after the attached property is evaluated is not currently supported; although adding support would be rather simple.

, , , , , , ,

  1. #1 by Aviv on December 15, 2011 - 2:38 pm

    works like a charm

  2. #2 by joancomasfdz on April 17, 2012 - 4:31 am

    Can you provide any guideline about ho to add support for a logic setted after the attached property is evaluated, please?

    • #3 by Tim Valentine on April 22, 2012 - 3:44 am

      attach an event handler to ContextMenuOpening from within DataContextChanged, then in the the handler get a reference to the object the ContextMenu is on, and set it’s ContextMenu property to the value of the attached property. But you’d probably want to put some logic in to prevent attaching to the ContextMenuOpening event multiple times on the same object.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.