触摸屏 GDI+ 绘制键盘






4.94/5 (25投票s)
自定义绘制的键盘用户控件和带键盘弹出的文本框。
引言
最近,我有一个项目需要一个专业的虚拟键盘控件,用于我的 WinForm 项目。我想要一个适合我应用程序界面的控件。在线上,我找到了很多键盘,但其中许多都有明显的缺点,例如无法自定义、设置不方便、速度慢、无法将按键发送到非活动窗口。因此,我决定自己制作一个屏幕键盘,它具有许多自定义操作选项。在此过程中,我不仅实现了一个键盘,还实现了一个带有键盘选择器的 TextBox
。
背景
VirtualKeyBoard
继承自 System.Windows.Forms.Control
,为了扩展其功能,您需要熟悉 GDI+ 编程。该控件充当模拟器,可以将文本输入到当前聚焦的输入字段中。它允许用户更改键盘设计,还可以操作各种属性,例如不同元素的颜色、按钮的字体,并且您还可以隐藏/显示不同的按钮区域。
TextBoxWithKeyboard
是扩展的标准 System.Windows.Forms.TextBox
控件。该组件会在 TextBox
下方显示实现的虚拟键盘,并在用户单击键盘以外的区域时将其隐藏。
Using the Code
要使用此处提供的 VirtualKeyboard
,您只需下载提供的 VirtualKeyboard.DLL 并将其包含在您的 Visual Studio 项目中。如果您想使用 TextBoxWithKeyboard
控件,则必须将 VirtualKeyboard.dll 和 TextBoxKeyboard.dll 添加到您的应用程序中,或者您可以将 TextBoxKeyboard.csproj 项目附加到您的解决方案。此外,如果您想更改弹出键盘的视觉设计,您需要手动修改它,可以在 OnScreenKeyboardForm.cs 窗体上找到该键盘。
默认情况下,虚拟键盘会将消息发送到当前活动窗口,但当您用手指(在触摸屏上)或鼠标单击另一个应用程序(记事本、Word 等)时,被单击的应用程序将成为活动窗口。如果您想阻止这种情况,可以通过“SPY++”来检查“OSK.exe”窗口的样式。该窗口具有扩展样式 WS_EX_NOACTIVATE
和 WS_EX_TOPMOST
。它们可以阻止窗口成为活动窗口并抢占输入焦点。通过重写 CreataParams
可以很容易地使用这些样式。
const uint WS_EX_NOACTIVATE = 0x08000000;
const uint WS_EX_TOPMOST = 0x00000008;
protected override CreateParams CreateParams
{
get
{
CreateParams baseParams = base.CreateParams;
baseParams.ExStyle |= (int)(WS_EX_NOACTIVATE | WS_EX_TOPMOST);
return baseParams;
}
}
这种技巧不允许输入您在 .NET Visual Studio 应用程序中创建的另一个 Windows 窗体中的字段值。有很多方法可以将数据从一个 Windows 窗体传输到另一个。我找到了两种不同的方法来实现这一点。
第一种方法是线程池。只需从另一个线程调用 Application.Run
并显示带有键盘的指定窗体。
...
ThreadPool.QueueUserWorkItem(KeyboardLoop);
...
private void KeyboardLoop(object state)
{
Application.Run(new KeyboradExampleForm() { });
}
我建议的第二种方法是重写键盘所在窗体的 CreataParams
。
const int WS_EX_NOACTIVATE = 0x08000000;
const int WS_EX_TOPMOST = 0x00000008;
const int WS_CHILD = 0x40000000;
const int WS_BORDER = 0x00800000;
const int WS_DLGFRAME = 0x00400000;
const int WS_CAPTION = WS_BORDER | WS_DLGFRAME;
const int WS_SYSMENU = 0x00080000;
const int WS_MAXIMIZEBOX = 0x00010000;
const int WS_MINIMIZEBOX = 0x00020000;
private const int WS_THICKFRAME = 0x00040000;
private const int WS_SIZEBOX = WS_THICKFRAME;
protected override CreateParams CreateParams
{
get
{
CreateParams ret = base.CreateParams;
ret.Style = WS_CAPTION |
WS_SIZEBOX | WS_SYSMENU |
WS_MINIMIZEBOX |
WS_MAXIMIZEBOX | WS_CHILD;
ret.ExStyle |= (WS_EX_NOACTIVATE | WS_EX_TOPMOST);
StartPosition = FormStartPosition.CenterScreen;
return ret;
}
}
控件的属性
VirtualKeyBoard
控件具有以下属性
FirstRowCustomButtons
:第一行的按钮列表SecondRowCustomButtons
:第二行的按钮列表ThirdRowCustomButtons
:第三行的按钮列表FourthRowCustomButtons
:第四行的按钮列表FifthRowCustomButtons
:第五行的按钮列表- 用于应用键盘状态的属性(例如,Shift 状态、CapsLock 状态等)
- 用于显示/隐藏键盘特殊按钮的属性(例如,Ctrl、Tab、Del 按钮等)
- 用于应用键盘视觉属性的属性(颜色、字体等)
键盘的每个按钮 VirtualKbButton
都有自己的属性
TopText
:按钮的顶部文本BottomText
:按钮的底部文本Picture
:按钮的图像TopFont
:按钮顶部文本的字体BottomFont
:按钮底部文本的字体CanSendCommand
:按钮是否可以发送文本命令Tag
:按钮的标签ButtonName
:按钮的名称
此外,VirtualKeyBoard
具有 ButtonClick
事件,该事件在单击键盘按钮时发生。
实现
VirtualKeyBoard
类包含创建键盘按钮列表的代码。每个键盘按钮都是 VirtualKbButton.cs 类,具有矩形、按钮文本、按钮名称等属性。该组件使用这些属性通过其坐标绘制键盘按钮。为了填充键盘按键列表,我使用了带有文本的按钮列表。列表按行分隔。第一行的示例
public ButtonsCollection FirstRowButtonsDefault()
{
return new ButtonsCollection
{
new VirtualKbButton("~", "`"),
new VirtualKbButton("!", "1"),
new VirtualKbButton("@", "2"),
new VirtualKbButton("#", "3"),
...
};
}
用户可以使用自己的属性填充按钮列表。以下是添加按钮的代码
Font buttonFont = new Font("Tahoma", 8, FontStyle.Bold, GraphicsUnit.Point, 204);
VirtualKbButton btn = new VirtualKbButton();
btn.TopFont = buttonFont;
btn.Tag = "btn_Internet";
btn.TopText = "www";
btn.CanSendCommand = false;
virtualKeyboard1.FifthRowCustomButtons.Add(btn);
通过 SendKeys
方法,键盘组件模拟按键事件。使用此方法,每个字母都可以由当前具有焦点的任何控件处理。
SendKeys.Send("a"); //letter 'a' pressed
SendKeys.Send("~"); //symbol '~' pressed
要发送像 '{', '}', '+, '^', '%', '~', '(', ')' 这样的符号,必须使用花括号。
SendKeys.Send("{%}"); //symbol '%' pressed
SendKeys.Send("{+}"); //symbol '+' pressed
由于许多特殊键(Enter、Tab、Delete 等)没有对应的字母表示,它们具有特殊的助记名称。使用花括号可以模拟这些按键
SendKeys.Send("{ENTER}");
SendKeys.Send("{ESC}");
SHIFT、CTRL、ALT 键除外。它们有特殊的符号名称:“+”代表 Shift,“^”代表 Ctrl,“%”代表 Alt。这些标识符有助于创建不同的按键组合。
SendKeys.Send("^c"); // Ctrl + C
SendKeys.Send("^%s"); // Ctrl + Alt + S
SendKeys.Send("%{F4}"); // Alt + F4
目前,VirtualKeyBoard
可以处理以下按键组合
- Ctrl + Alt + Shift + 任意字母或任意功能命令 (F1 - F12)
- Ctrl + Alt + 任意字母或任意功能命令
- Ctrl + 任意字母或任意功能命令
- Alt + 任意字母或任意功能命令
TextBoxWithKeyboard
类使用 IMessageFilter 接口
来处理鼠标单击。它检查鼠标事件是否在包含 VirtualKeyBoard
控件的弹出窗体外部引发。
private static KeyboardFilter kf = null;
...
if (kf == null)
{
kf = new KeyboardFilter();
Application.AddMessageFilter(kf);
}
...
private class KeyboardFilter : IMessageFilter
{
public delegate void LeftButtonDown();
public event LeftButtonDown MouseDown;
public delegate void KeyPressUp(IntPtr target);
public event KeyPressUp KeyUp;
private const int WM_KEYUP = 0x101;
private const int WM_LBUTTONDOWN = 0x201;
bool IMessageFilter.PreFilterMessage(ref Message m)
{
switch (m.Msg)
{
// raises KeyUp() event
case WM_KEYUP:
if (KeyUp != null)
{
KeyUp(m.HWnd);
}
break;
// raises MouseDown() event
case WM_LBUTTONDOWN:
if (MouseDown != null)
{
MouseDown();
}
break;
}
return false;
}
}
TextBoxWithKeyboard
可以显示数字键盘或标准键盘,具体取决于控件的属性。
历史
- 2017 年 2 月 22 日:首次发布
- 2017 年 6 月 10 日:添加了应用自定义 XML 键盘布局的功能
- 2017 年 6 月 11 日:修复了 bug,在按下文字键时键盘发送了
null
值 - 2021 年 2 月 14 日:更改了应用自定义布局的功能,增加了更改每行按钮数量的可能性,以及显示/隐藏特殊按钮的功能
- 2021 年 2 月 16 日:修复了功能按钮行的绘图错误
- 2021 年 12 月 1 日:改进了 OnClick 速度