65.9K
CodeProject 正在变化。 阅读更多。
Home

触摸屏 GDI+ 绘制键盘

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (25投票s)

2017年2月22日

CPOL

5分钟阅读

viewsIcon

38661

downloadIcon

5370

自定义绘制的键盘用户控件和带键盘弹出的文本框。

引言

最近,我有一个项目需要一个专业的虚拟键盘控件,用于我的 WinForm 项目。我想要一个适合我应用程序界面的控件。在线上,我找到了很多键盘,但其中许多都有明显的缺点,例如无法自定义、设置不方便、速度慢、无法将按键发送到非活动窗口。因此,我决定自己制作一个屏幕键盘,它具有许多自定义操作选项。在此过程中,我不仅实现了一个键盘,还实现了一个带有键盘选择器的 TextBox

背景

VirtualKeyBoard 继承自 System.Windows.Forms.Control,为了扩展其功能,您需要熟悉 GDI+ 编程。该控件充当模拟器,可以将文本输入到当前聚焦的输入字段中。它允许用户更改键盘设计,还可以操作各种属性,例如不同元素的颜色、按钮的字体,并且您还可以隐藏/显示不同的按钮区域。

TextBoxWithKeyboard 是扩展的标准 System.Windows.Forms.TextBox 控件。该组件会在 TextBox 下方显示实现的虚拟键盘,并在用户单击键盘以外的区域时将其隐藏。

Using the Code

要使用此处提供的 VirtualKeyboard,您只需下载提供的 VirtualKeyboard.DLL 并将其包含在您的 Visual Studio 项目中。如果您想使用 TextBoxWithKeyboard 控件,则必须将 VirtualKeyboard.dllTextBoxKeyboard.dll 添加到您的应用程序中,或者您可以将 TextBoxKeyboard.csproj 项目附加到您的解决方案。此外,如果您想更改弹出键盘的视觉设计,您需要手动修改它,可以在 OnScreenKeyboardForm.cs 窗体上找到该键盘。

默认情况下,虚拟键盘会将消息发送到当前活动窗口,但当您用手指(在触摸屏上)或鼠标单击另一个应用程序(记事本、Word 等)时,被单击的应用程序将成为活动窗口。如果您想阻止这种情况,可以通过“SPY++”来检查“OSK.exe”窗口的样式。该窗口具有扩展样式 WS_EX_NOACTIVATEWS_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}");

SHIFTCTRLALT 键除外。它们有特殊的符号名称:“+”代表 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 速度
© . All rights reserved.