views:

76

answers:

2

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
    {
    }
}
+1  A: 

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.

Wallstreet Programmer
A context menu for the listbox opens in the middle of the listbox, not where the selected item is. Also, I'd like to declare the context menu items in XAML, which makes globalization easier than putting it in code. Keyboard focus is paramount for visually impaired users, or users that for other reasons avoid mice.
Guge
A: 

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;
    }
Guge