windows SDK编程 --- 消息之键盘消息(4)

发布于:2024-04-28 ⋅ 阅读:(26) ⋅ 点赞:(0)

前置知识

一、 键盘消息

在Windows操作系统中,键盘消息是用来通知应用程序有关键盘输入事件的一种机制。当用户在键盘上进行操作,比如按键或释放键时,Windows会生成相应的消息并发送给处理这些输入的应用程序。这些消息对于开发图形用户界面(GUI)应用程序至关重要,因为它们允许应用程序响应用户的输入。

下面是几种主要的键盘消息及其用途的详细说明:

  1. WM_KEYDOWN
描述:
当用户按下一个键盘键(并未完全释放)时发送此消息给拥有焦点的窗口。
参数:
wParam:虚拟键码。
lParam:提供了有关按键的更多信息,如重复次数、扫描码、ALT键是否被按下等。
lParam 参数的详细位分布:
0-15位:重复计数(Repeat count)。这表示自从用户开始按下键以来,由于按键保持不放,相同的消息被自动重复发送的次数。这个计数不是累积的,每次消息发送时都是独立的计数。
16-23位:扫描码(Scan code)。这是键盘硬件产生的代码,用于识别物理键。
24位:扩展键标志(Extended-key flag)。如果是扩展键(如功能键、方向键等),该位为125-28位:保留,未使用。
29位:上下文代码。对于 WM_KEYDOWN 消息,这个值始终为030位:上一个键状态(Previous key state)。如果在发送此消息之前键是未按下的,则此位为1;如果键已经是按下状态,则此位为031位:转换状态(Transition state)。对于 WM_KEYDOWN,该位总是为0;对于 WM_KEYUP,该位为1
  1. WM_KEYUP
描述:
当用户释放一个键盘键时发送此消息。
参数:
wParam:虚拟键码。
lParam:同样包含键的重复次数、扫描码、是否有ALT键等信息。
  1. WM_CHAR
描述:
用于处理字符输入,当按下能产生字符的键时,通过 TranslateMessage 函数生成。例如,按下 A 键时,如果启用了大写锁定或者同时按住了Shift键,可能会发送 WM_CHAR 消息并携带字符 ‘A’。
参数:
wParam:字符的ASCII或Unicode值。
lParam:与 WM_KEYDOWN 和 WM_KEYUP 相同,提供了关于按键事件的更多细节。
  1. WM_SYSKEYDOWN 和 WM_SYSKEYUP
描述:
这些消息类似于 WM_KEYDOWN 和 WM_KEYUP,但它们用于处理系统键(如Alt和F10)。
参数:
wParam:虚拟键码。
lParam:包含与普通键盘消息相同的额外信息。

二、什么是虚拟键码

虚拟键码是Windows操作系统定义的一组常数,用于表示键盘上的每一个按键。这些键码可以让开发者在编程时准确地识别用户按下或释放的具体键,无论键盘布局和硬件如何。虚拟键码在处理键盘输入提供了一个硬件无关的方法来识别键盘动作。简而言之,虚拟键码就是对键盘按键的一种编程上的抽象表示。wParam 参数会包含被按下或释放键的虚拟键码。

VK_A:字母 'A' 键(值为 0x41)
VK_0:数字 '0' 键(值为 0x30)
VK_ESCAPE:Esc键(值为 0x1B)
VK_RETURN:回车键(值为 0x0D)
VK_LEFT:左方向键(值为 0x25)
VK_RIGHT:右方向键(值为 0x27)
VK_UP:上方向键(值为 0x26)
VK_DOWN:下方向键(值为 0x28

三、什么是扫描码?扫描码和虚拟键码的关系?

在Windows编程中,处理键盘事件如 WM_KEYDOWN 时,可以通过消息的 lParam 获取扫描码,而 wParam 提供虚拟键码。这允许程序同时利用键的物理位置(扫描码)和逻辑功能(虚拟键码)来响应用户的键盘操作。

键盘扫描码(Scan Code)

键盘扫描码是键盘硬件为每次按键生成的原始代码,表示按下的是键盘上的哪一个物理键。这些代码通常是硬件级的,与操作系统和键盘布局无关。扫描码的主要作用是在最底层—即硬件和操作系统的界面—提供键位信息。

  • 特点:扫描码直接来自键盘,不受键盘语言布局的影响。
  • 用途:用于在低级别(如驱动程序或操作系统核心)处理键盘输入。

虚拟键码(Virtual-Key Code)

虚拟键码是操作系统级的抽象,它将扫描码转换为一个标准化的代码,这反映了按键的“功能”,而不仅仅是位置。例如,无论在哪种键盘布局中,字母“A”的虚拟键码都是相同的,即使其物理位置可能不同。

  • 特点:虚拟键码依赖于键盘布局,因为它反映了按键的逻辑功能(如字符、命令等)。
  • 用途:用于编写应用程序时处理更高级的输入(如文字输入、控制命令等)。

两者的区别

  • 来源:扫描码来自键盘硬件;虚拟键码由操作系统提供。
  • 依赖性:扫描码与键盘布局无关,与物理键位置直接相关;虚拟键码依赖于键盘布局,反映键的逻辑功能。
  • 用途:扫描码通常在较低的系统级别(如操作系统或驱动程序)使用,用于识别物理键位;虚拟键码在更高级的应用程序中使用,用于处理具体的键盘功能,如文本输入或用户界面控制。

三、快捷键Shortcut Keys)和热键(Hotkeys)有什么区别

快捷键(Shortcut Keys)

  • 快捷键通常是指在特定应用程序内部使用的键盘组合,用于执行应用程序的特定功能。这些快捷键只在该应用程序具有焦点时有效。例如,许多应用程序都使用 Ctrl + S 作为保存操作的快捷键。
  • 上下文依赖:快捷键的作用依赖于当前应用程序的状态和哪个窗口或控件有焦点。
  • 实现方式:在应用程序的消息处理逻辑中捕捉和处理特定的 WM_KEYDOWN 或 WM_KEYUP 消息来实现快捷键功能。

热键(Hotkeys)

  • 热键是系统级的键盘快捷键,它们在整个操作系统中都是有效的,无论当前哪个应用程序有焦点。热键通常用于操作系统功能或某些特定应用程序的功能,这些功能需要从任何地方快速访问。
  • 全局作用域:热键无论在哪个应用程序中都能被触发
  • 注册使用:通过使用Windows API的 RegisterHotKey 函数来设置。这个函数允许你定义一个热键和一个与之关联的消息,当热键被按下时,这个消息将被发送到指定的窗口处理。

示例代码

#include <windows.h>



//5.消息处理
LRESULT CALLBACK WndProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam
)
{
    // 根据消息类型进行分支处理

    switch (msg)
    {
    case WM_CREATE:
    {
        //注册热键
        RegisterHotKey(hwnd, 5566, MOD_CONTROL, VK_F1);

        return 0;
    }

    //键盘消息
    case WM_KEYDOWN:
    {
        //获得键盘状态
        BYTE KeyState[256];
        if (GetKeyboardState(KeyState)==0)
        {
            return TRUE; 
        }
        //键盘扫描码
        BYTE ScanCode = (int)lParam >> 16 && 0xff;
        WORD ch;
        //将键盘扫描码转换成Ascii码
        ToAscii(wParam, ScanCode, KeyState, &ch, 0);
        
        char buffer[100];
        wsprintf(buffer, "你按下了:%c\n", ch);
        OutputDebugString(buffer);

        return 0;
    }

    case WM_CHAR:
    {
        char buffer[100];
        wsprintf(buffer, "你按下了:%c\n", wParam);
        OutputDebugString(buffer);

        return 0;
    }

    case WM_KEYUP:
    {

        return 0;
    }


    //热键消息
    case WM_HOTKEY:
    {
        MessageBox(NULL, "HotKey!", "WM_HOTKEY", MB_OK);
        return 0;
    }



    case WM_CLOSE:
    {
        //询问是否关闭窗口
        int id = MessageBox(NULL, "你确定关闭窗口吗", "WM_CLOSE", MB_YESNO);
        if (id == IDYES)
        {
            DestroyWindow(hwnd);
        }

        return 0;
    }

    case WM_DESTROY:
    {
        //注销热键
        UnregisterHotKey(hwnd,5566);
        PostQuitMessage(0); 
        return 0;
    }


    }


    return DefWindowProc(hwnd, msg, wParam, lParam);


}

int WINAPI WinMain(
    HINSTANCE hInstance,     // 当前实例的句柄
    HINSTANCE hPrevInstance, // 前一个实例的句柄,现在总是为 NULL
    LPSTR lpCmdLine,         // 命令行参数的字符串
    int nCmdShow             // 指示程序窗口应如何被显示
)
{
    //1.注册窗口
    char MyWindowClassName[] = "MyWindowClass";         //窗口类名
    WNDCLASSEX wc = { 0 };
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_VREDRAW | CS_HREDRAW;     //
    wc.lpfnWndProc = WndProc;    //窗口过程函数(窗口回调函数->处理消息)
    wc.hInstance = hInstance;
    wc.hIcon = NULL;             //图标
    wc.hCursor = NULL;          //光标
    wc.hbrBackground = CreateSolidBrush(RGB(0, 255, 0));    //窗口背景颜色刷子
    wc.lpszMenuName = NULL;     //菜单名称
    wc.lpszClassName = MyWindowClassName;    //窗口类名


    if (RegisterClassEx(&wc) == 0)
    {
        return 0;
    }




    //2.创建窗口
    char MyWindowName[] = "MyWindowClass";         //窗口名称

    HWND hwnd = CreateWindowEx(
        0,
        MyWindowClassName,
        MyWindowName,
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        NULL,
        NULL,
        hInstance,
        NULL
    );
    if (hwnd == NULL)
    {
        return 0;
    }



    //3.显示更新窗口

    ShowWindow(hwnd, SW_SHOWNORMAL);
    UpdateWindow(hwnd);

    //4.消息循环(消息队列)
    MSG msg = {};
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return 0;
}




网站公告

今日签到

点亮在社区的每一天
去签到