模拟非ASCII字符的输入

时间:2013-04-01 11:57:18

标签: c# windows winapi windows-7

我正在寻找一种使用Windows API模拟非ASCII字符(例如CJK字符)输入的方法。 keybd_eventSendInput 不会工作,因为输入字符可能不是有效的虚拟密钥。

我想要什么

我想使用Windows API模拟Unicode字符的输入,而无需编写IME或TSF提供程序。

我尝试了什么

我尝试将WM_IME_CHAR消息发送到当前关注的窗口:

// Gets the currently focused input control (if any)
internal static IntPtr GetActiveWindowControl()
{
    IntPtr activeWin = GetForegroundWindow();
    if (activeWin == IntPtr.Zero)
    {
        return IntPtr.Zero;
    }
    uint threadId = GetWindowThreadProcessId(activeWin, IntPtr.Zero);
    GUITHREADINFO guiThreadInfo = new GUITHREADINFO();
    guiThreadInfo.cbSize = Marshal.SizeOf(guiThreadInfo);
    GetGUIThreadInfo(threadId, ref guiThreadInfo);
    if (guiThreadInfo.hwndFocus == IntPtr.Zero)
    {
        return activeWin; // Example: console
    }
    else
    {
        return guiThreadInfo.hwndFocus;
    }
}

internal void SimulateInput()
{
    // Encoding.Default on my computer is CP950 (Big5)
    byte[] b = Encoding.Default.GetBytes("我"); // one single Chinese character
    IntPtr activeHwnd = GetActiveWindowControl();
    if (activeHwnd != IntPtr.Zero)
    {
        int ch = 0;
        for (int i = 0; i < b.Length; i++) // Assumed b has <=2 elements
        {
            ch = (ch << 8) | b[i];
        }
        uint WM_IME_CHAR = 0x0286;
        SendMessage(activeHwnd, WM_IME_CHAR, (IntPtr)ch, (IntPtr)0)
    }
}

遇到问题

到目前为止,这段代码似乎在大多数情况下运行良好,然而它在Notepad ++上不起作用。而不是所需的角色,我改为垃圾输入。

使用Spy ++进行的进一步调查表明,当使用IME输入时,没有发送到Notepad ++编辑窗口的WM_IME_CHAR消息。我得到的只有WM_IME_STARTCOMPOSITIONWM_IME_ENDCOMPOSITIONWM_IME_REQUESTWM_IME_NOTIFYWM_IME_SETCONTEXT。似乎Notepad ++处理与其他程序不同的IME输入。 (另外我相信存在更多类似于Notepad ++的程序,但我还没有找到一个。)

此外,如果字符超出目标操作系统的默认字符编码,则此代码将不起作用(例如,简体中文不适用于Big5)。

我想阻止的事情

  • 将字符放在剪贴板中并在目标窗口上调用粘贴

其他信息

我意识到Windows 7 Tablet PC输入面板可以很好地执行文本插入(甚至适用于GTK +应用程序)。我试图模仿它发送的内容但它似乎只是重复最后一个IME输入的字符。即使看起来与Spy ++中看到的完全相同,它也不起作用。

SendMessage(active, WM_IME_STARTCOMPOSITION, (IntPtr)0, (IntPtr)0);
//SendMessage(active, WM_IME_COMPOSITION, (IntPtr)ch, (IntPtr)0x0800);
SendMessage(active, WM_IME_COMPOSITION, (IntPtr)0xA7DA, (IntPtr)0x0800);
SendMessage(active, WM_IME_NOTIFY, (IntPtr)0x010D, (IntPtr)0);
SendMessage(active, WM_IME_ENDCOMPOSITION, (IntPtr)0, (IntPtr)0);
SendMessage(active, WM_IME_NOTIFY, (IntPtr)0x010E, (IntPtr)0);

我还在.NET中找到了一个SendKeys类。可以在我的申请中使用吗?

1 个答案:

答案 0 :(得分:2)

经过一番搜索,结果发现SendInput实际上可以KEYEVENTF_UNICODE发送Unicode输入。

现在我的代码看起来像这样(InputSender是我自己的类):

internal static void InputString(string str)
{
    char[] chars = str.ToCharArray();
    InputSender.Input[] inputs = new InputSender.Input[chars.Length * 2];

    for (int i = 0; i < chars.Length; i++)
    {
        ushort ch = (ushort)chars[i];
        // Key down
        inputs[i * 2].type = InputSender.InputType.Keyboard;
        inputs[i * 2].ki.wScan = ch;
        inputs[i * 2].ki.dwFlags = InputSender.KeyboardEventFlags.Unicode;
        // Key up
        inputs[i * 2 + 1].type = InputSender.InputType.Keyboard;
        inputs[i * 2 + 1].ki.wScan = ch;
        inputs[i * 2 + 1].ki.dwFlags = InputSender.KeyboardEventFlags.Unicode | InputSender.KeyboardEventFlags.KeyUp;
    }
    InputSender.SendInput(inputs);
}

但事实证明它是doesn't work quite well with GTK+ programs。稍微搜索显示它should be fixed a while ago但使用旧版GTK +的程序可能仍会遇到问题。刚试用最新的GTK +与Geany和最新的GTK +很好,而Inkscape拒绝解释输入。

无论如何,如果我忽略了GTK +的问题,使用SendInput就足够了(并且应该优于WM_IME_CHAR)。我没有尝试this answer which fixes GTK+'s problem,因为我不知道该怎么做,并认为这是不可行的。