Hammerite
Hammerite

Reputation: 22340

How can I make a Button that is the ItemTemplate for a ListBox receive keyboard events?

I created a ListBox and gave it an ItemTemplate which is defined like this:

<DataTemplate x:Key="myDataTemplate">
    <Button Command="{Binding MyListItemButtonCommand}" CommandParameter="{Binding .}">
        <!-- detail omitted -->
    </Button>
</DataTemplate>

If I click on one of the list items, it executes the Button's command as expected. But if I use keyboard navigation to give keyboard focus to the list item and press Enter or Space, it doesn't execute the command. This is because, as detailed in this question, it is the list item that has keyboard focus and not the button itself. I want to react to keypresses when the list item has focus in the same way as I would if it were the button that had focus.

This might be achieved through XAML or it might be achieved through codebehind, but it must not involve me having to identify the set of events I need to listen for and it must not involve me having to identify which keys and key combinations I have to test for. For example, a solution that contains code like if (e.Key == Key.Enter) is not acceptable (because I don't want to be responsible for being on top of all possible keys and key combinations that might be used to activate a native Windows button control).

This is different from the linked question because in that question, the asker had written an event handler and they needed help getting it to run. I have not written an event handler and I do not want to write one; I want the button to receive keyboard events (just as though it were the button that had keyboard focus, and not the list item) and respond to them using the event handlers that are part of the framework and have already been written for me by someone at Microsoft.

Upvotes: 0

Views: 66

Answers (2)

Clemens
Clemens

Reputation: 128097

You could get around this problem by choosing a list control that does not support any user interaction of its own.

You'd typically do this by means of an ItemsControl, optionally put inside a Scrollviewer.

<ItemsControl ...>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Button Command="{Binding MyListItemButtonCommand}"
                    CommandParameter="{Binding .}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
<ItemsControl>

Upvotes: 1

mm8
mm8

Reputation: 169360

You could handle the PreviewKeyDown event for the ListBoxItem container and programmatically focus the button:

private void ListBoxItem_PreviewKeyDown(object sender, KeyEventArgs e)
{
    ListBoxItem lbi = (ListBoxItem)sender;
    Button btn = FindVisualChild<Button>(lbi);
    if (btn != null)
        btn.Focus();
}

private static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject
{
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
    {
        DependencyObject child = VisualTreeHelper.GetChild(obj, i);
        if (child != null && child is T)
            return (T)child;
        else
        {
            T childOfChild = FindVisualChild<T>(child);
            if (childOfChild != null)
                return childOfChild;
        }
    }
    return null;
}

XAML:

<ListBox ... KeyboardNavigation.TabNavigation="Contained">
    <ListBox.Resources>
        <Style TargetType="ListBoxItem">
            <EventSetter Event="PreviewKeyDown" Handler="ListBoxItem_PreviewKeyDown" />
        </Style>
    </ListBox.Resources>
</ListBox>

Upvotes: 0

Related Questions