2
votes

I have a ListBox with different classes of items. DataTemplates are used to present those objects in the appropriate way. I want to have different context menus in the DataTemplates of these classes.

Everything works fine using the mouse, but using the keyboard I can't bring up the context menu.

This is probably because the keyboard-focus is not on the contents of the DataTemplate, but on the ListBoxItem.

How can I get the ListBoxItem to refer to the Content's ContextMenu?

Sample code:

<Window x:Class="WpfApplication8.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:my="clr-namespace:WpfApplication8"
    Title="MainWindow" Height="350" Width="525">
<Window.Resources>
    <DataTemplate DataType="{x:Type my:Orange}">
        <TextBlock>
            Orange
            <TextBlock.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="Peel"/>
                </ContextMenu>
            </TextBlock.ContextMenu>
        </TextBlock>
    </DataTemplate>
    <DataTemplate DataType="{x:Type my:Apple}">
        <TextBlock>
            Apple
            <TextBlock.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="Uncore"/>
                </ContextMenu>
            </TextBlock.ContextMenu>
        </TextBlock>
    </DataTemplate>
</Window.Resources>
<Grid>
    <ListBox ItemsSource="{Binding Fruits}"/>
</Grid>
</Window>


using System.Windows;
using System.Collections.ObjectModel;

namespace WpfApplication8
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Fruits = new ObservableCollection<Fruit>();
            Fruits.Add(new Apple());
            Fruits.Add(new Apple());
            Fruits.Add(new Orange());
            this.DataContext = this;
        }

        public ObservableCollection<Fruit> Fruits { get; set; }
    }

    public class Fruit
    {
    }

    public class Apple : Fruit
    {
    }

    public class Orange : Fruit
    {
    }
}
3

3 Answers

3
votes

I too had this problem. Reading Bea Stollnitz' blog gave me an idea.

I started with a data template like this in my resources:

<ContextMenu x:Key="MyMenu">
    <MenuItem Header="A" />
    <MenuItem Header="B" />
    <MenuItem Header="C" />
</ContextMenu>

<DataTemplate x:Key="MyTemplateKey" DataType="{x:Type local:myType}">
   <TextBlock ContextMenu="{StaticResource MyMenu}" >
        <Run Text="{Binding Path=MyBindingPath}" FontSize="20" FontWeight="Bold" />
   </TextBlock>
</DataTemplate>

As described above, this causes the keyboard menu key not to invoke the context menu, although right clicking does work. The problem is the context menu needs to be on the ListBoxItem, not the template inside.

Hey presto!

<Style x:Key="ContextLBI" TargetType="{x:Type ListBoxItem}">
    <Setter Property="ContextMenu" Value="{StaticResource MyMenu}">

    </Setter>
</Style>

Now, just remove the ContextMenu from the data template, and set your style on your list box like this:

<ListBox ItemTemplate="{StaticResource MyTemplateKey}" 
         ItemContainerStyle="{StaticResource ContextLBI}"
... >
</ListBox>
1
votes

This guy have similar problem as you: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/5737a331-2014-4e39-b87c-215ae6a7cdd4.

Instead of fighting with focus, add a context menu to the listbox. Add a ContextMenuOpening event handler to your listbox. In that handler, depending on data context of currently selected item, add whatever menuitems you need programmatically.

0
votes

I found a solution. In the code-behind I will give each ListBoxItem the context menu I find from its visual children.

It gives me the possibility of adding the context menus to the DataTemplates for the various class, thus giving me the polymorphism I like. I also prefer to declare the menus in XAML. And it works with keyboard navigation, as well as mouse use.

The code could probably have been put in an attached property or something for elegance.

I add a loaded event handler and this code:

    void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        foreach (var item in list.Items)
        {
            ListBoxItem lbItem = list.ItemContainerGenerator.ContainerFromItem(item) as ListBoxItem;
            lbItem.ContextMenu = FindContextMenu(lbItem);
        }
    }

    private ContextMenu FindContextMenu(DependencyObject depObj)
    {
        ContextMenu cm = depObj.GetValue(ContextMenuProperty) as ContextMenu;
        if (cm != null)
            return cm;
        int children = VisualTreeHelper.GetChildrenCount(depObj);
        for (int i = 0; i < children; i++)
        {
            cm = FindContextMenu(VisualTreeHelper.GetChild(depObj, i));
            if(cm != null)
                return cm;
        }
        return null;
    }