2

I have a ListBox with a custom data template which contains a CheckBox, a TextBlock and a TextBox. Normally when you select an item in a ListBox, the underlying ListBoxItem is actually what has the focus and as such, it responds to the up and down keys. Additionally, if the CheckBox has focus, since it doesn't do anything with the up and down keys itself, it just happily ignores them and they're handled by the underlying ListBoxItem as well. All's good so far.

However, a TextBox has its own processing rules for the up and down keys, namingly moving the caret up or down a line in text, which doesn't apply here because in this case it's a single line (it's a number actually.) As such, if the TextBox has focus, the up and down keys break the navigation of the ListBox's selection, nor do they really help with editing.

Now while I could handle the PreviewKeyDownEvent (which I do below, but for different reasons) and manually process the behaviors depending on the pressed keys, that's a very specific solution and requires the control to have knowledge of its container's behavior.

In a perfect world (and in pseudocode), I'd like to just say MyTextBox.KeysToIgnore(Up, Down) or something similar and have it do just that... ignore those keys as if it wasn't even there. (Again, not swallow, but ignore so they pass through.)

But until then, here's what I've come up with, which seems to work, but just looks so 'hacky' to me...

private void PreviewKeyDownHandler(object sender, KeyEventArgs e) {

    switch (e.Key){
        case Key.Up:
        case Key.Down:
        case Key.OtherKeyToIgnore
        case Key.AndAnother

            e.Handled = true;

            FrameworkElement target = VisualTreeHelper.GetParent(
                e.Source as DependencyObject) as FrameworkElement;

            target.RaiseEvent(
                new KeyEventArgs(
                    e.KeyboardDevice,
                    PresentationSource.FromVisual(target),
                    0,
                    e.Key
                ){
                    RoutedEvent=Keyboard.KeyDownEvent
                }
            );
            break;

    }

}

This also has the added negative of not sending the PreviewKeyDown event to the target. Now I could work around that and fake it by sending that event first, then looking at the e.Handled before sending the actual KeyDown message, which makes sense, but then I hit another wall with the PreviewKeyUp and KeyUp events since thanks to setting e.Handled above, I never get the real 'key up' events to know when to send the fake ones. Plus I'm pretty sure I'd also be breaking the direction of the PreviewKeyxxx messages since they bubble the opposite direction from the regular non non-preview versions. (Maybe that's handled internally but I don't think so.)

Like I said... hacky, hacky, hacky!

But it does work so there's that. And I can implement this via Attached Behaviors which is why I even went this route. (In the attached behaviors implementation, it's not a case statement but rather a check against a collection of keys that I specify in XAML.) I just don't like the idea of losing all the other behaviors that I want.

Again, I just want to say 'Hey TextBox... when you see the Up or Down keys being pressed, STFU ya b*stard!!' and otherwise make it key-transparent.

Thoughts anyone?

Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286

2 Answers2

1

Man... not even one comment, let alone an answer in almost a month! Oh well... guess my 'hacky' solution above is the way to do this so I'm marking this as the answer.

Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286
1

Yeh, TextBox key event handling sure can give you a headache. The trouble is that it is impossible to determine call order of callbacks, registered via EventManager.RegisterClassEventHandler. I.e. your callback gets called on unhandled event, then event is handled, and thats it... I found a way to "unhandle" key events by subclassing TextBox and by calling "AddEventHandler(KeyDownEvent, callback, true)" in constructor. Then set e.Handled = false by circumstances. Seems like callback gets called after event is processed by TextBox. It is very not nice to have instance of delegate per instance of TextBox, rather than to have one delegate instance per class, but i can't see any other way to workaround.

John Whiter
  • 1,492
  • 1
  • 11
  • 5
  • Can you provide sample code? I haven't really followed what you've said here. (I think I do, but I'm not 100% sure as I don't know what object you're calling 'AddEventHandler' on that takes three parameters like that.) Again, code would go a long way here. Can you edit your answer and put them there? If so and it does what I need, I'll change yours to the accepted answer. Thanks! – Mark A. Donohoe Dec 09 '10 at 07:13
  • Sorry, lately i'm visiting site rarely. I got into situation, where text box was situated in modal window, and there was input binding on "Enter" key to accept dialog. And TextBox was eating key input, regardless of AcceptsReturn property. This is workaround, that is used: – John Whiter Nov 29 '11 at 09:22
  • public class TextBox2 : TextBox { private void OnKeyDown(object sender, KeyEventArgs e) { if (e.Handled) switch (e.Key) { case Key.Return: if (!AcceptsReturn) e.Handled = false; break; case Key.Tab: if (!AcceptsTab) e.Handled = false; break; } } public TextBox2() { AddHandler(KeyDownEvent, new KeyEventHandler(OnKeyDown), true); } } – John Whiter Nov 29 '11 at 09:24