C# 实现一个虚拟键盘
2022 年 8 月 25 日 · 3 分钟读完
触摸屏工控机外接键盘不方便,需要上位机自己提供一个虚拟键盘。网上方法很多,这里先记录用 keybd_event 的实现。
keybd_event
微软文档:合成一次击键,系统会据此产生 WM_KEYDOWN、WM_KEYUP 消息,效果和按真实键盘一样。
void keybd_event(
BYTE bVk, // 虚拟键码,1 ~ 254
BYTE bScan, // 硬件扫描码,一般填 0
DWORD dwFlags, // 0 按下,2 弹起,1 表示扩展键
ULONG_PTR dwExtraInfo // 附加信息,一般填 0
);
| dwFlags | 值 | 意义 |
|---|---|---|
| KEYEVENTF_KEYDOWN | 0x0000 | 键按下 |
| KEYEVENTF_EXTENDEDKEY | 0x0001 | 这是一个扩展键 |
| KEYEVENTF_KEYUP | 0x0002 | 键弹起 |
虚拟键码和扫描码:按下一个键时,键盘把扫描码发给电脑,扫描码和硬件有关,不同厂商可能不同;键盘驱动再把它转换成虚拟键码,虚拟键码和硬件无关,同一个键永远相同。之后系统把这些信息封装成键盘消息,送到当前有焦点的窗口。
扩展键:101/102 键盘上后加的那些键,包括右边的 Alt 和 Ctrl,数字键盘左边的 Insert、Delete、Home、End、PageUp、PageDown 和方向键,NumLock、Break、PrintScreen,数字键盘上的 / 和 Enter。
常用虚拟键码
完整列表见微软文档,做键盘常用的是这些:
| 键 | 常量 | 值 |
|---|---|---|
| Backspace | VK_BACK | 0x08 |
| Tab | VK_TAB | 0x09 |
| Enter | VK_RETURN | 0x0D |
| Shift / Ctrl / Alt | VK_SHIFT / VK_CONTROL / VK_MENU | 0x10 / 0x11 / 0x12 |
| Caps Lock | VK_CAPITAL | 0x14 |
| Esc | VK_ESCAPE | 0x1B |
| 空格 | VK_SPACE | 0x20 |
| 左上右下 | VK_LEFT ~ VK_DOWN | 0x25 ~ 0x28 |
| Delete | VK_DELETE | 0x2E |
| 0 ~ 9 | 和字符 ‘0’ ~ ‘9’ 相同 | 0x30 ~ 0x39 |
| A ~ Z | 和大写字母相同 | 0x41 ~ 0x5A |
| 小键盘 0 ~ 9 | VK_NUMPAD0 ~ 9 | 0x60 ~ 0x69 |
| F1 ~ F12 | VK_F1 ~ VK_F12 | 0x70 ~ 0x7B |
- = [ ] ; ' , . / 等符号 |
VK_OEM_* | 0xBA ~ 0xDE |
WinForm 里不用自己记,System.Windows.Forms.Keys 枚举的值就是虚拟键码,比如 (byte)Keys.E 就是 0x45。
使用
[DllImport("user32.dll", EntryPoint = "keybd_event", SetLastError = true)]
public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, uint dwExtraInfo);
const uint KEYEVENTF_KEYUP = 0x0002;
单个键
在按钮的 Click 事件里先按下再弹起:
keybd_event((byte)Keys.E, 0, 0, 0); // 按下
keybd_event((byte)Keys.E, 0, KEYEVENTF_KEYUP, 0); // 弹起

不要把按下和弹起分别写在按钮的 MouseDown、MouseUp 里。触摸屏上这两个事件的时序不可靠(手指滑出按钮时收不到 MouseUp),容易出现键「卡住」一直按着的情况。
组合键
比如 Shift + A 输入大写 A:先按住 Shift,再按 A,最后松开 Shift。
keybd_event((byte)Keys.ShiftKey, 0, 0, 0);
keybd_event((byte)Keys.A, 0, 0, 0);
keybd_event((byte)Keys.A, 0, KEYEVENTF_KEYUP, 0);
keybd_event((byte)Keys.ShiftKey, 0, KEYEVENTF_KEYUP, 0);

如果 Shift 只发了按下、没发弹起,之后所有按键都会变成 Shift + xx。利用这一点可以做一个「Shift 锁定」按钮。
中文输入
模拟的按键会正常经过输入法:按一下 Shift 切到中文输入法,然后照常打拼音。

其他方法(未实践)
延伸
- 最关键的一点:键盘窗体不能抢焦点。模拟的按键会发给当前有焦点的窗口,如果点虚拟键盘的按钮时焦点跑到了键盘窗体上,字就打到键盘自己身上了。重写键盘窗体的
CreateParams,加上WS_EX_NOACTIVATE,并让它置顶:
protected override CreateParams CreateParams
{
get
{
var cp = base.CreateParams;
cp.ExStyle |= 0x08000000; // WS_EX_NOACTIVATE:点击不激活
cp.ExStyle |= 0x00000008; // WS_EX_TOPMOST
return cp;
}
}
protected override bool ShowWithoutActivation => true;
按钮也要设 TabStop = false。
keybd_event已被SendInput取代:微软文档建议用SendInput,它可以一次提交一串输入,不会被其他程序的输入插队;还能用KEYEVENTF_UNICODE直接发送任意 Unicode 字符(包括汉字),不经过输入法。- 如果只是需要个键盘,可以直接调系统的:
Process.Start("osk.exe")打开屏幕键盘(64 位系统里 32 位程序调用可能找不到,要关闭文件系统重定向);Windows 10/11 的触摸键盘是C:\Program Files\Common Files\microsoft shared\ink\TabTip.exe。系统键盘的缺点是太大,而且操作员可能把它关掉找不回来,所以工控项目常常还是自己做一个只有数字和常用键的小键盘。 - 只在本程序里输入数字:不需要模拟按键,键盘按钮直接改当前文本框的内容更简单可靠:记住最后获得焦点的 TextBox,按钮点击时
tb.SelectedText = "5";。
原文首发于 blog.csdn.net