原文链接
在使用输入法的时候,为了输入一些特殊字符我们往往会用到软键盘和符号大全。符号大全和软件盘是一类程序,主要实现的就是通过界面点击向目标程序输出对应的符号。
这里介绍一下如何通过QT编写软键盘类程序,实现软键盘主要有两个关键问题要解决。
1.创建一个没有焦点的界面程序
2.向目标程序发送对应的输入字符,实现符号上屏
之所以要创建没有焦点的界面程序,是为了防止软键盘在点击的过程中抢占目标程序的焦点。QT要实现无焦点程序,需要对窗口的属性进行设置,设置成无焦点属性,设置方法如下。
LONG exs = GetWindowLong((HWND)this->winId(),GWL_EXSTYLE);exs |= WS_EX_NOACTIVATE;exs |= WS_EX_APPWINDOW;exs |= WS_EX_TOPMOST;SetWindowLong((HWND)this->winId(),GWL_EXSTYLE,exs);
这里主要对窗口设置了三个属性,WS_EX_NOACTIVATE会让窗口不会被激活,因此在点击窗口的时候窗口不会抢占目标程序的焦点,WS_EX_APPWINDOW属性是为了使程序窗口是普通进程窗口,这样在最小化的时候才能在任务栏上显示对应的图标,WS_EX_TOPMOST是为了让窗口始终保持在最上层。
通过上面的属性设置,产生的界面在点击的过程中就不会抢占目标程序的焦点。windows向目标程序发送文本的方法有很多种,有keybd_event和sendinput,
keybd_event函数会产生一次按键事件。系统通过这个事件来产生WM_KEYUP或WM_KEYDOWN消息,键盘驱动程序的中断处理程序调用keybd_event函数。在Windows NT中该函数己被使用SendInput替代。通过SendInput函数我们可以向当前的焦点程序发送对应的按键事件,从而实现符号的输入。实现方法如下:
void InputService::SendKeys(QString msg){wstring data = msg.toStdWString();SendUnicode(data.at(0));}void SendUnicode(const wchar_t data){INPUT input[4];HWND current_hwnd = GetForegroundWindow();SetFocus(current_hwnd);input[0].type = INPUT_KEYBOARD;input[0].ki.wVk = 0;input[0].ki.wScan = data;input[0].ki.dwFlags = KEYEVENTF_UNICODE;input[0].ki.time = 0;input[0].ki.dwExtraInfo = GetMessageExtraInfo();SendInput(1, &input[0], sizeof(INPUT));input[1].type = INPUT_KEYBOARD;input[1].ki.wVk = 0;input[1].ki.wScan = data;input[1].ki.dwFlags = KEYEVENTF_UNICODE | KEYEVENTF_KEYUP;input[1].ki.time = 0;input[1].ki.dwExtraInfo = GetMessageExtraInfo();SendInput(1, &input[1], sizeof(INPUT));}
通过上面的方法可以实现普通字符和Unicode字符的输入,但是还是会存在一些问题。比如,如果当前的输入法是中文输入法,SendInput发送a-z的字符的时候会调用对应的输入法而阻碍符号的输入。还有SendInput需要目标程序实现了对Unicode字符输入的支持,否则发送的符号会失败。
针对上面的问题,现有的最佳的解决方案是通过剪切板辅助通信,首先将符号拷贝到剪切板,然后向目标程序发送Ctrl+V指令,通过剪切板实现伪通信。效果是一样的。实现方法如下:
void InputService::SendKeys(QString msg){wstring data = msg.toStdWString();QClipboard* clipborad = QApplication::clipboard();clipborad->setText(msg);SendCopyMessage();}void InputService::SendCopyMessage(){// Create a generic keyboard event structureINPUT ip;ip.type = INPUT_KEYBOARD;ip.ki.wScan = 0;ip.ki.time = 0;ip.ki.dwExtraInfo = 0;// Press the "Ctrl" keyip.ki.wVk = VK_CONTROL;ip.ki.dwFlags = 0; // 0 for key pressSendInput(1, &ip, sizeof(INPUT));// Press the "V" keyip.ki.wVk = 'V';ip.ki.dwFlags = 0; // 0 for key pressSendInput(1, &ip, sizeof(INPUT));// Release the "V" keyip.ki.wVk = 'V';ip.ki.dwFlags = KEYEVENTF_KEYUP;SendInput(1, &ip, sizeof(INPUT));// Release the "Ctrl" keyip.ki.wVk = VK_CONTROL;ip.ki.dwFlags = KEYEVENTF_KEYUP;SendInput(1, &ip, sizeof(INPUT));}
解决了这两个关键问题之后,后面的问题就是单纯的业务实现问题了。