Question

I want to add a possibility to type Cyrillic letters in my XNA-game. I can't use default Input.KeyboardState because Input.Keys enum contains only English letters. So, I used keyboard-hook to handle Windows input. But it returns keycode, which can be used for English letters (because ASCII-code matches key-code), but don't work properly with Cyrillic. For "a-я" can be used 848 offset, but for more specific letters, like "і" or "ї" there are different values.

Is there efficient way to convert key-code into Cyrillic letter in ASCII? Or maybe there is solution without nasty keyboard-hook?

BTW, here keyboard-hook class listing:

#region EventArgs

public class CharacterEventArgs : EventArgs
{
    private readonly char character;
    private readonly int lParam;
    private readonly int keyLayout;

    public CharacterEventArgs(char character, int lParam, int keyLayout)
    {
        this.character = character;
        this.lParam = lParam;
        this.keyLayout = keyLayout;
    }

    #region Properties

    public int KeyLayout
    {
        get { return keyLayout; }
    }

    public char Character
    {
        get { return character; }
    }

    public int Param
    {
        get { return lParam; }
    }

    public int RepeatCount
    {
        get { return lParam & 0xffff; }
    }

    public bool ExtendedKey
    {
        get { return (lParam & (1 << 24)) > 0; }
    }

    public bool AltPressed
    {
        get { return (lParam & (1 << 29)) > 0; }
    }

    public bool PreviousState
    {
        get { return (lParam & (1 << 30)) > 0; }
    }

    public bool TransitionState
    {
        get { return (lParam & (1 << 31)) > 0; }
    }

    #endregion
}

public class KeyEventArgs : EventArgs
{
    private Keys keyCode;

    public KeyEventArgs(Keys keyCode)
    {
        this.keyCode = keyCode;
    }

    public Keys KeyCode
    {
        get { return keyCode; }
    }
}

#endregion

#region delegats

public delegate void CharEnteredHandler(object sender, CharacterEventArgs e);
public delegate void KeyEventHandler(object sender, KeyEventArgs e);

#endregion

public static class EventInput
{
    #region events

    /// <summary>
    /// Event raised when a character has been entered.
    /// </summary>
    public static event CharEnteredHandler CharEntered;

    /// <summary>
    /// Event raised when a key has been pressed down. May fire multiple times due to keyboard repeat.
    /// </summary>
    public static event KeyEventHandler KeyDown;

    /// <summary>
    /// Event raised when a key has been released.
    /// </summary>
    public static event KeyEventHandler KeyUp;

    #endregion

    delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);

    static bool initialized;
    static IntPtr prevWndProc;
    static WndProc hookProcDelegate;
    static IntPtr hIMC;

    static GameWindow gameWindow;

    #region constants

    const int GWL_WNDPROC = -4;
    const int WM_KEYDOWN = 0x100;
    const int WM_KEYUP = 0x101;
    const int WM_CHAR = 0x102;
    const int WM_IME_SETCONTEXT = 0x0281;
    const int WM_INPUTLANGCHANGE = 0x51;
    const int WM_GETDLGCODE = 0x87;
    const int WM_IME_COMPOSITION = 0x10f;
    const int DLGC_WANTALLKEYS = 4;

    #endregion

    #region Win32-functions

    //to handle input

    [DllImport("Imm32.dll")]
    static extern IntPtr ImmGetContext(IntPtr hWnd);

    [DllImport("Imm32.dll")]
    static extern IntPtr ImmAssociateContext(IntPtr hWnd, IntPtr hIMC);

    [DllImport("user32.dll")]
    static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

    [DllImport("user32.dll")]
    static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);


    //to get keyboard layout

    [DllImport("user32.dll", SetLastError = true)]
    static extern int GetWindowThreadProcessId(
        [In] IntPtr hWnd,
        [Out, Optional] IntPtr lpdwProcessId
        );

    [DllImport("user32.dll", SetLastError = true)]
    static extern ushort GetKeyboardLayout(
        [In] int idThread
        );

    #endregion

    static ushort GetKeyboardLayout()
    {
        return GetKeyboardLayout(GetWindowThreadProcessId(gameWindow.Handle, IntPtr.Zero));
    }

    #region Initialize

    /// <summary>
    /// Initialize the TextInput with the given GameWindow.
    /// </summary>
    /// <param name="window">The XNA window to which text input should be linked.</param>
    public static void Initialize(GameWindow window)
    {
        if (initialized)
            throw new InvalidOperationException("TextInput.Initialize can only be called once!");

        gameWindow = window;

        hookProcDelegate = new WndProc(HookProc);
        prevWndProc = (IntPtr)SetWindowLong(window.Handle, GWL_WNDPROC,
            (int)Marshal.GetFunctionPointerForDelegate(hookProcDelegate));

        hIMC = ImmGetContext(window.Handle);
        initialized = true;
    }

    #endregion

    static IntPtr HookProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
    {
        IntPtr returnCode = CallWindowProc(prevWndProc, hWnd, msg, wParam, lParam);

        switch (msg)
        {
            case WM_GETDLGCODE:
                returnCode = (IntPtr)(returnCode.ToInt32() | DLGC_WANTALLKEYS);
                break;

            case WM_KEYDOWN:
                if (KeyDown != null)
                    KeyDown(null, new KeyEventArgs((Keys)wParam));
                break;

            case WM_KEYUP:
                if (KeyUp != null)
                    KeyUp(null, new KeyEventArgs((Keys)wParam));
                break;

            case WM_CHAR:
                if (CharEntered != null)
                {
                    CharEntered(null, new CharacterEventArgs((char) wParam, lParam.ToInt32(), GetKeyboardLayout()));
                }
                break;

            case WM_IME_SETCONTEXT:
                if (wParam.ToInt32() == 1)
                    ImmAssociateContext(hWnd, hIMC);
                break;

            case WM_INPUTLANGCHANGE:
                ImmAssociateContext(hWnd, hIMC);
                returnCode = (IntPtr)1;
                break;
        }

        return returnCode;
    }
}
Was it helpful?

Solution

Have you considered using Windows Forms for character input? I don't speak Russian, but I can only assume that the KeyPress event must handle Cyrillic correctly in Russian locales, and it would be much simpler than pumping Windows messages yourself.

using System.Windows.Forms;

...

var form = (Form)Form.FromHandle(window.Handle);
form.KeyPress += form_KeyPress;

...

private void form_KeyPress(Object sender, KeyPressEventArgs e)
{  
    Console.WriteLine(e.KeyChar);
    e.Handled = true;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top