一、项目背景详细介绍

在现代 Windows 桌面应用程序中,软键盘(On-Screen Keyboard) 是一种非常常见的输入辅助工具。
它的作用是:当用户无法或不方便使用物理键盘时,通过点击屏幕上的虚拟按键来输入字符。

软键盘广泛用于:

  • 触摸屏终端(如自助终端机、ATM、工业触控面板);

  • 医疗、零售等无键盘设备;

  • 安全输入场景(如银行登录密码输入、虚拟终端登录);

  • 智能家居控制界面。

在 VC++ 应用程序中,我们可以使用 Win32 API + MFC/SDK 界面 实现一个完整的软键盘组件。
本项目以 Win32 SDK + GDI绘图 + 窗口消息机制 为基础,创建一个可点击的软键盘窗口,模拟物理键盘输入。

项目目标包括:

  • 实现可视化键盘界面;

  • 支持鼠标点击模拟按键;

  • 支持大小写切换;

  • 支持数字、符号;

  • 将输入结果发送至目标窗口(如文本框)。


二、项目需求详细介绍

2.1 功能性需求

  1. 程序启动后显示一个软键盘窗口;

  2. 界面上包含字母键、数字键、符号键;

  3. 点击按键时,将字符发送到焦点窗口;

  4. 支持 Shift(切换大小写);

  5. 支持 Backspace、Enter 等控制键;

  6. 界面美观,按钮布局规则;

  7. 支持最小化、关闭等窗口操作。


三、相关技术详细介绍

3.1 Win32 GUI 编程基础

在 Win32 SDK 下,一个 GUI 程序主要由以下部分组成:

  • WinMain():程序入口;

  • WNDCLASS / WNDCLASSEX:注册窗口类;

  • CreateWindow():创建窗口;

  • WndProc():窗口过程函数,用于处理消息(鼠标点击、重绘、键盘输入等)。

软键盘程序的核心逻辑就是:

  1. 创建一个主窗口;

  2. 绘制所有“按键”;

  3. 在鼠标点击时检测按键区域;

  4. 模拟键盘输入消息发送给目标窗口。


3.2 GDI 图形绘制技术

我们将使用 GDI(Graphics Device Interface) 绘制每个按键:

  • Rectangle():绘制按键矩形;

  • DrawText():绘制文字;

  • InvalidateRect():触发重绘。

每个按键的数据结构将包含:


RECT rect; // 按钮区域 std::wstring text; // 按钮文字


3.3 模拟按键输入技术

当用户点击软键盘上的按钮时,程序需要将该字符“输入”到当前聚焦的窗口中。
我们使用 Windows 提供的消息函数:


PostMessage(hwndTarget, WM_CHAR, 字符代码, 0);

或者对于控制键(如 Backspace、Enter):


PostMessage(hwndTarget, WM_KEYDOWN, VK_BACK, 0); PostMessage(hwndTarget, WM_KEYUP, VK_BACK, 0);

获取目标窗口句柄的方式:


HWND hwndTarget = GetForegroundWindow(); // 当前聚焦窗口


3.4 按钮状态与点击检测

当用户单击鼠标时,系统会发送 WM_LBUTTONDOWN 消息。
我们通过坐标检测判断点击的是哪个按钮:


POINT pt = { LOWORD(lParam), HIWORD(lParam) }; for (auto &btn : buttons) { if (PtInRect(&btn.rect, pt)) { OnButtonClick(btn.text); } }


3.5 大小写切换(Shift 实现)

通过设置一个全局布尔变量:


bool isUpperCase = false;

每次点击 Shift 按钮时切换状态,并更新所有按键文字(如 'a'→'A')。


四、实现思路详细介绍

  1. 创建主窗口:注册窗口类并创建软键盘窗口;

  2. 初始化按键数据:在内存中定义所有按钮的区域与文字;

  3. 绘制界面:在 WM_PAINT 中使用 GDI 绘制矩形与文字;

  4. 响应鼠标点击:在 WM_LBUTTONDOWN 消息中检测被点击的按键;

  5. 发送输入消息:通过 PostMessage 模拟键盘输入;

  6. 处理特殊键:Shift、Backspace、Enter 等;

  7. 刷新显示:点击 Shift 后重绘整个界面。


五、完整实现代码

/******************************************************
 * 文件: SoftKeyboard.cpp
 * 功能: VC++ 实现软键盘(Win32 API)
 ******************************************************/
#include <windows.h>
#include <string>
#include <vector>

struct KeyButton {
    RECT rect;          // 按钮区域
    std::wstring text;  // 按钮文字
};

std::vector<KeyButton> g_buttons; // 全局按钮列表
bool g_isUpperCase = false;       // 是否大写
HWND g_hwndMain = NULL;           // 主窗口句柄

/******************************************************
 * 函数: CreateKeyboardLayout
 * 功能: 创建键盘布局,定义每个按键的矩形区域与文字
 ******************************************************/
void CreateKeyboardLayout() {
    g_buttons.clear();
    int startX = 20, startY = 40;
    int keyWidth = 40, keyHeight = 40;
    int gap = 5;

    std::wstring row1 = L"1234567890";
    std::wstring row2 = L"qwertyuiop";
    std::wstring row3 = L"asdfghjkl";
    std::wstring row4 = L"zxcvbnm";

    // 第一行(数字)
    for (int i = 0; i < (int)row1.size(); i++) {
        RECT r = { startX + i * (keyWidth + gap), startY,
                   startX + i * (keyWidth + gap) + keyWidth, startY + keyHeight };
        g_buttons.push_back({ r, std::wstring(1, row1[i]) });
    }

    // 第二行(字母)
    startY += keyHeight + gap;
    for (int i = 0; i < (int)row2.size(); i++) {
        RECT r = { startX + i * (keyWidth + gap) + 20, startY,
                   startX + i * (keyWidth + gap) + 20 + keyWidth, startY + keyHeight };
        g_buttons.push_back({ r, std::wstring(1, row2[i]) });
    }

    // 第三行
    startY += keyHeight + gap;
    for (int i = 0; i < (int)row3.size(); i++) {
        RECT r = { startX + i * (keyWidth + gap) + 40, startY,
                   startX + i * (keyWidth + gap) + 40 + keyWidth, startY + keyHeight };
        g_buttons.push_back({ r, std::wstring(1, row3[i]) });
    }

    // 第四行
    startY += keyHeight + gap;
    for (int i = 0; i < (int)row4.size(); i++) {
        RECT r = { startX + i * (keyWidth + gap) + 60, startY,
                   startX + i * (keyWidth + gap) + 60 + keyWidth, startY + keyHeight };
        g_buttons.push_back({ r, std::wstring(1, row4[i]) });
    }

    // 特殊键
    RECT shiftRect = { 20, startY, 100, startY + keyHeight };
    g_buttons.push_back({ shiftRect, L"Shift" });

    RECT backRect = { 480, 40, 560, 80 };
    g_buttons.push_back({ backRect, L"Back" });

    RECT enterRect = { 480, startY, 560, startY + keyHeight };
    g_buttons.push_back({ enterRect, L"Enter" });
}

/******************************************************
 * 函数: DrawKeyboard
 * 功能: 绘制键盘按键
 ******************************************************/
void DrawKeyboard(HDC hdc) {
    for (auto &btn : g_buttons) {
        Rectangle(hdc, btn.rect.left, btn.rect.top, btn.rect.right, btn.rect.bottom);
        std::wstring text = btn.text;
        if (text.size() == 1 && g_isUpperCase)
            text[0] = towupper(text[0]);

        DrawText(hdc, text.c_str(), -1, (LPRECT)&btn.rect,
                 DT_CENTER | DT_VCENTER | DT_SINGLELINE);
    }
}

/******************************************************
 * 函数: SendCharToForeground
 * 功能: 将字符发送到当前焦点窗口
 ******************************************************/
void SendCharToForeground(wchar_t ch) {
    HWND hwndTarget = GetForegroundWindow();
    if (hwndTarget && hwndTarget != g_hwndMain) {
        PostMessage(hwndTarget, WM_CHAR, ch, 0);
    }
}

/******************************************************
 * 函数: OnButtonClick
 * 功能: 按钮点击事件处理
 ******************************************************/
void OnButtonClick(const std::wstring &text) {
    if (text == L"Shift") {
        g_isUpperCase = !g_isUpperCase;
        InvalidateRect(g_hwndMain, NULL, TRUE);
    } else if (text == L"Back") {
        HWND hwndTarget = GetForegroundWindow();
        if (hwndTarget && hwndTarget != g_hwndMain) {
            PostMessage(hwndTarget, WM_KEYDOWN, VK_BACK, 0);
            PostMessage(hwndTarget, WM_KEYUP, VK_BACK, 0);
        }
    } else if (text == L"Enter") {
        HWND hwndTarget = GetForegroundWindow();
        if (hwndTarget && hwndTarget != g_hwndMain) {
            PostMessage(hwndTarget, WM_KEYDOWN, VK_RETURN, 0);
            PostMessage(hwndTarget, WM_KEYUP, VK_RETURN, 0);
        }
    } else if (text.size() == 1) {
        wchar_t ch = g_isUpperCase ? towupper(text[0]) : text[0];
        SendCharToForeground(ch);
    }
}

/******************************************************
 * 函数: WndProc
 * 功能: 主窗口消息处理函数
 ******************************************************/
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
    case WM_CREATE:
        CreateKeyboardLayout();
        break;
    case WM_PAINT: {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);
        DrawKeyboard(hdc);
        EndPaint(hwnd, &ps);
        break;
    }
    case WM_LBUTTONDOWN: {
        POINT pt = { LOWORD(lParam), HIWORD(lParam) };
        for (auto &btn : g_buttons) {
            if (PtInRect(&btn.rect, pt)) {
                OnButtonClick(btn.text);
                break;
            }
        }
        break;
    }
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

/******************************************************
 * 函数: WinMain
 * 功能: 程序入口
 ******************************************************/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) {
    const wchar_t CLASS_NAME[] = L"SoftKeyboardWindow";
    WNDCLASS wc = {};
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = CLASS_NAME;
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

    RegisterClass(&wc);

    g_hwndMain = CreateWindowEx(
        0, CLASS_NAME, L"虚拟软键盘", WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 640, 320,
        NULL, NULL, hInstance, NULL);

    ShowWindow(g_hwndMain, nCmdShow);
    UpdateWindow(g_hwndMain);

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return 0;
}

六、代码详细解读

  1. CreateKeyboardLayout():
    定义所有按键的矩形区域与文字内容,构建软键盘布局。

  2. DrawKeyboard():
    使用 GDI 绘制所有按键矩形与文字,自动根据大小写状态更新。

  3. SendCharToForeground():
    获取当前焦点窗口句柄并向其发送字符输入消息。

  4. OnButtonClick():
    处理按钮逻辑,包括 Shift 切换、Backspace 删除、Enter 输入与普通字符输入。

  5. WndProc():
    处理窗口消息,包括绘制、点击、退出等。

  6. WinMain():
    程序入口,注册窗口类并创建窗口,启动消息循环。


七、项目详细总结

本项目演示了如何在 VC++ 环境下使用 Win32 API 实现一个功能完整的软键盘系统。
主要技术点包括:

  1. 窗口消息机制:响应鼠标点击;

  2. GDI 绘图技术:动态绘制界面;

  3. 输入模拟:通过 PostMessage 实现虚拟输入;

  4. 状态控制:实现大小写切换;

  5. 组件化设计:可独立嵌入到其他系统中。

该实现逻辑清晰、结构紧凑,适用于教学、项目演示或嵌入式设备开发。


八、项目常见问题及解答

Q1:点击软键盘时文字没有输入到目标窗口?
A1:确认当前输入焦点在可输入窗口上(例如文本框)。软键盘窗口本身不应获得焦点。

Q2:为什么按键显示错位?
A2:检查 DPI 设置或调整按键间距参数。

Q3:能否支持中文输入?
A3:可以,但需增加输入法或拼音逻辑模块,这属于扩展功能。

Q4:如何让软键盘保持在最前?
A4:使用 SetWindowPos(hwnd, HWND_TOPMOST, ...)。

Q5:是否可做为 DLL 嵌入其他程序?
A5:可以,将 CreateKeyboardLayout() 与绘制逻辑封装为类,导出 API 调用。


九、扩展方向与性能优化

  1. 增加符号键与功能键布局(Tab、Ctrl、Alt);

  2. 支持触摸输入(WM_TOUCH),用于触屏设备;

  3. 增加透明背景与圆角键绘制(GDI+);

  4. 加入动画反馈(按键高亮效果);

  5. 增加输入日志与统计模块;

  6. 多语言键盘布局(中/英/日等);

  7. 模块化封装为可重用控件(DLL/COM);

  8. 优化绘图性能:使用双缓冲防止闪烁。

更多推荐