tags:

views:

109

answers:

2

Why doesn't keyboard input work for a ScrollViewer when the child control has input focus?

This is the scenario. A WPF window opens. It sets the focus to a control that is embedded in a ScrollViewer.

I hit the up and down and left and right keys. The ScrollViewer doesn't seem to handle the key events, anyone know why?

This is the simplest possible example:

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300"
    FocusManager.FocusedElement="{Binding ElementName=control}"
    >
    <Grid>
        <ScrollViewer
            HorizontalScrollBarVisibility="Auto"
           >
            <ItemsControl
                x:Name="control"
                Width="1000"
                Height="1000"
                />
        </ScrollViewer>        
    </Grid>
</Window>

When you start the app that contains this window, "control" appears to have the focus as I intended. Pressing the key seems to result in bubbling key events reaching the ScrollViewer (I checked for this using WPF Snoop). I can't work out why it doesn't respond to the input.

A: 

In order for the ScrollViewer to react to your keyboard keys - it needs to have IsFocused="True" - right now it's child has the focus.

To prove it - try in your Loaded event to manually set focus to the ScrollViewer (you might have to set IsFocusable="True" for it to work on the ScrollViewer) - now the keys should work. If you want it to work otherwise, you need to set the appropriate EventHandlers on the ScrollViewer (KeyDown/KeyPress etc.)

Goblin
Thanks but I think you misunderstood the question. I want the child to have focus. In my app the child handles input and then the events then bubble up to the ScrollViewer. The question is why doesn't the ScrollViewer handle them?
Ashley Davis
If the ScrollViewer does not have focus, the 'automapping' of the arrow-keys does not work. You need to attach handlers to the Keyevents as I wrote in my answer. In your code you do not handle the events.
Goblin
+1  A: 

The problem

A ScrollViewer ignores all KeyDown events whose OriginalSource is not the ScrollViewer. The OriginalSource on a KeyDown is set to the focused control, therefore the ScrollViewer ignores it when a child has the focus.

The solution

Catch the KeyDown event and raise a copy of it directly on the ScrollViewer so it will have the correct OriginalSource, like this:

void ScrollViewer_KeyDown(object sender, KeyEventArgs e)
{
  if(e.Handled) return;
  var temporaryEventArgs =
    new KeyEventArgs(e.KeyboardDevice, e.InputSource, e.Timestamp, e.Key)
    {
      RoutedEvent = e.RoutedEvent
    };
  ((ScrollViewer)sender).RaiseEvent(temporaryEventArgs);
  e.Handled = temporaryEventArgs.Handled;
}

the event handler can be added in XAML:

<ScrollViewer KeyDown="ScrollViewer_KeyDown" />

or in code:

scrollViewer.AddHandler(Keyboard.KeyDownEvent, ScrollViewer_KeyDown);

The latter is more applicable if the ScrollViewer is inside a template somewhere and you have code to find it.

Ray Burns
Thanks this sounds useful I'll try it out. Although I'd love to know the design reason, if there is one, why this doesn't just work as I expect it should.
Ashley Davis
I don't know, but I do know they did a lot of usability testing. My guess is that they found usability suffered when they had the ScrollViewer scroll when it didn't have focus. In other words, I suspect users found the behavior unexpected and/or surprising when arrows caused scrolling when a button was focused.
Ray Burns