← 全部文章 WinForm

C# 实现一个虚拟键盘

2022 年 8 月 25 日 · 3 分钟读完

目录
  1. keybd_event
  2. 常用虚拟键码
  3. 使用
  4. 单个键
  5. 组合键
  6. 中文输入
  7. 其他方法(未实践)
  8. 延伸

触摸屏工控机外接键盘不方便,需要上位机自己提供一个虚拟键盘。网上方法很多,这里先记录用 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 切到中文输入法,然后照常打拼音。

其他方法(未实践)

  • 用 WinIO 直接写键盘端口,模拟硬件扫描码,能骗过一些屏蔽了模拟输入的程序:WinIO 原理、C# 操作。需要驱动和管理员权限,工控软件里基本用不上。
  • 用键盘钩子:C# 实现屏幕键盘。

延伸

  • 最关键的一点:键盘窗体不能抢焦点。模拟的按键会发给当前有焦点的窗口,如果点虚拟键盘的按钮时焦点跑到了键盘窗体上,字就打到键盘自己身上了。重写键盘窗体的 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";。

# WinForm # 控件 # 工控

原文首发于 blog.csdn.net