See the question and my original answer on StackOverflow

In your case, the WPF control is hosted as a WS_CHILD (this is important) of a Win32 dialog box (this is also important), which eats some messages for its own good, as all dialog boxes do.

You can control that if you handle the WM_GETDLGCODE message

Sent to the window procedure associated with a control. By default, the system handles all keyboard input to the control; the system interprets certain types of keyboard input as dialog box navigation keys. To override this default behavior, the control can respond to the WM_GETDLGCODE message to indicate the types of input it wants to process itself.

Something like this:

private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    const int WM_GETDLGCODE = 0x0087;
    const int DLGC_WANTALLKEYS = 4;

    if (msg == WM_GETDLGCODE)
    {
        handled = true;
        return new IntPtr(DLGC_WANTALLKEYS);
    }
    return IntPtr.Zero;
}