Win32 API 控制台鼠标操作、坐标获取与相关函数介绍

news/2024/10/10 8:52:49/

Win32 API 控制台鼠标操作、坐标获取与相关函数介绍

  • 一、前置介绍
    • 读取控制台输入缓冲区数据 ReadConsoleInput 函数
    • 控制台输入缓冲区中的输入事件 INPUT_RECORD 结构
    • 鼠标输入事件 MOUSE_EVENT_RECORD 结构
    • 更改输入模式 SetConsoleMode 函数
  • 二、鼠标坐标获取(以下代码环境为 VS2022 C语言)
    • 鼠标输入模式更改
      • 位段思想简单运用
        • 将所有开关打开
        • 检测是否打开开关
    • 鼠标坐标获取代码参考
  • 三、鼠标操作(以下代码环境为 VS2022 C语言)
  • 四、注意事项

一、前置介绍

使用鼠标操作前需要简单介绍 Win32 API 的一些函数。

读取控制台输入缓冲区数据 ReadConsoleInput 函数

从控制台输入缓冲区读取数据,并将其从缓冲区删除。

BOOL WINAPI ReadConsoleInput(_In_  HANDLE        hConsoleInput,			// 句柄_Out_ PINPUT_RECORD lpBuffer,					// 输出的信息_In_  DWORD         nLength,					// lpBuffer 参数指向的数组大小(以数组元素表示)_Out_ LPDWORD       lpNumberOfEventsRead		// 指向接收所读取输入记录数量的变量
);

详细信息请参考:ReadConsoleInput 函数

ReadConsoleInput 函数 用于读取操作,但是只能读取一种操作。例如读取的是鼠标操作,输入键盘操作时,接受鼠标操作的变量将置零。

  1. 第一个参数为输入句柄 关于句柄介绍可参考:GetStdHandle 函数

  2. 第二个参数为记录输入的信息 PINPUT_RECORD 结构体,下部分将会介绍。

  3. 后两个参数是 Windows 对内置类型封装的数据类型,这里不做介绍,有兴趣研究的读者我这里放上官方链接 Windows 数据类型

控制台输入缓冲区中的输入事件 INPUT_RECORD 结构

描述控制台输入缓冲区中的输入事件。 可以使用 ReadConsoleInput 或 PeekConsoleInput 函数从输入缓冲区读取这些记录,或使用 WriteConsoleInput 函数写入输入缓冲区。

typedef struct _INPUT_RECORD {WORD  EventType;							// 记录操作类型union {KEY_EVENT_RECORD          KeyEvent;		MOUSE_EVENT_RECORD        MouseEvent;	// 鼠标事件WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;MENU_EVENT_RECORD         MenuEvent;FOCUS_EVENT_RECORD        FocusEvent;} Event;
} INPUT_RECORD;

详细信息请参考:INPUT_RECORD 结构

INPUT_RECORD 结构体中记录操作使用了共用体(联合体),这也就是 ReadConsoleInput 函数只能读取一种操作的原因。

鼠标输入事件 MOUSE_EVENT_RECORD 结构

描述控制台 INPUT_RECORD 结构中的鼠标输入事件。

注意:
当控制台处于鼠标模式 (ENABLE_MOUSE_INPUT) 时,鼠标事件将放置在输入缓冲区中。

每当用户移动鼠标或者按下或释放鼠标按钮之一时,就会生成鼠标事件。 仅当控制台组具有键盘焦点且游标位于控制台窗口边框内时,鼠标事件才会放置在控制台的输入缓冲区中。

typedef struct _MOUSE_EVENT_RECORD {COORD dwMousePosition;	// 包含鼠标位置的 COORD 结构,以控制台屏幕缓冲区的字符单元坐标为单位。DWORD dwButtonState;		// 鼠标按钮的状态DWORD dwControlKeyState;	// 控制键的状态DWORD dwEventFlags;		// 鼠标事件的类型
} MOUSE_EVENT_RECORD;

详细信息请参考:MOUSE_EVENT_RECORD 结构

  1. MOUSE_EVENT_RECORD 结构体 记录了鼠标的位置 dwMousePosition,这个位置最小单位是控制台屏幕的一个字符距离,为 Windows 数据类型中的 COORD 类型。

  2. dwButtonState 记录了鼠标的按钮,如鼠标左键和右键等。

  3. dwControlKeyState 记录控制键的状态,这里忽略。

  4. dwEventFlags 记录鼠标事件类型,如 按下或释放了鼠标按钮、发生了鼠标位置更改、垂直鼠标滚轮已移动等等。

这里要注意,想要获取鼠标输入事件信息,必须要使用 SetConsoleMode 函数 更改输入模式。

更改输入模式 SetConsoleMode 函数

设置控制台输入缓冲区的输入模式或控制台屏幕缓冲区的输出模式。

BOOL WINAPI SetConsoleMode(_In_ HANDLE hConsoleHandle,	// 控制台输入缓冲区或控制台屏幕缓冲区的句柄_In_ DWORD  dwMode			// 要设置的输入或输出模式
);

详细信息请参考:SetConsoleMode 函数

SetConsoleMode 函数可以更改输入输出模式,这里只介绍鼠标输入模式。

二、鼠标坐标获取(以下代码环境为 VS2022 C语言)

鼠标输入模式更改

在 SetConsoleMode 中传入参数:

SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_EXTENDED_FLAGS | ENABLE_MOUSE_INPUT);
  1. ENABLE_MOUSE_INPUT 表示将鼠标信息放置在输入缓冲区中,由 ReadConsoleInput 获取,官方解释为:
    在这里插入图片描述
  2. ENABLE_EXTENDED_FLAGS 表示禁用鼠标选择和编辑文本,官方解释为:
    在这里插入图片描述
    注意,一定要禁用此模式也就是添加 ENABLE_EXTENDED_FLAGS,不然控制台鼠标坐标获取会异常,虽然我不知道原理是什么。

为什么要使用 ’ | ’ 在它俩中间,这实际上是借用了位段的思想,关于位段解释可参考这篇文章 (学习总结6)C语言结构体的内存对齐和位段实现。

位段思想简单运用

一个操作是开启状态还是关闭状态,可以用 bool 类型记录,使用 1 字节存储,但只利用了一比特位表示。如果有一堆相关操作,每个用一字节(8个比特位)表示就太浪费空间了,使用 ’ | ’ ’ & ’ 两个按位操作符可以节省空间:

例如,现在有四个开关,鼠标操作开关、键盘操作开关、颜色显示开关、声音开关,若使用 bool 类型会使用 4 字节,这里我们使用位段思想处理只要 1 字节空间,但开关的数值应该为 2 的整数次幕

将所有开关打开
#include <stdio.h>#define MOUSE_OPERATE 1
#define KEYBOARD_OPERATE 2
#define OPEN_COLOR 4
#define OPEN_SOUND 8void test2()
{unsigned char operate = 0;		// 1 字节空间operate |= MOUSE_OPERATE;		// 等价 operate = operate | MOUSE_OPERATE;printf("%d\n", operate);		// 打印数值operate |= KEYBOARD_OPERATE;printf("%d\n", operate);operate |= OPEN_COLOR;printf("%d\n", operate);operate |= OPEN_SOUND;printf("%d\n", operate);// 将所有开关打开也可以直接表示为://operate = MOUSE_OPERATE | KEYBOARD_OPERATE | OPEN_COLOR | OPEN_SOUND;
}int main()
{test2();return 0;
}

在这里插入图片描述

检测是否打开开关

同理于 &,只需要 开关变量 & 具体开关的值 就可以检查是否打开了。

#include <stdio.h>#define MOUSE_OPERATE 1
#define KEYBOARD_OPERATE 2
#define OPEN_COLOR 4
#define OPEN_SOUND 8void test1()
{unsigned char operate = 0;					// 1 字节operate = MOUSE_OPERATE | OPEN_SOUND;		// 打开 鼠标操作 和 声音// 按位与操作检查是否打开对应开关printf("是否开启了鼠标操作:%d\n", operate & MOUSE_OPERATE);printf("是否开启了键盘操作:%d\n", operate & KEYBOARD_OPERATE);printf("是否开启了颜色显示:%d\n", operate & OPEN_COLOR);printf("是否开启了声音开关:%d\n", operate & OPEN_SOUND);
}int main()
{//test2();test1();return 0;
}

在这里插入图片描述

鼠标坐标获取代码参考

知道方法后我们可以用变量记录鼠标坐标且打印在控制台上:

#include <stdio.h>
#include <stdbool.h>
#include <Windows.h>void getMouseOperate()			// 鼠标操作获取
{SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_EXTENDED_FLAGS | ENABLE_MOUSE_INPUT);
}void SetPos(short x, short y)	// 设置光标位置
{COORD pos = { x, y };SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}void HideCursor()				// 隐藏光标
{CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &CursorInfo);CursorInfo.bVisible = false; //隐藏控制台光标SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &CursorInfo);
}void test()
{getMouseOperate();HideCursor();INPUT_RECORD mouse;DWORD ret;while (true){ReadConsoleInput(GetStdHandle(STD_INPUT_HANDLE), &mouse, 1, &ret);int x = mouse.Event.MouseEvent.dwMousePosition.X;int y = mouse.Event.MouseEvent.dwMousePosition.Y;SetPos(0, 0);printf("                          ");SetPos(0, 0);printf("x = %d, y = %d", x, y);}
}int main()
{test();return 0;
}

关于 设置光标位置隐藏光标 函数来源请参考:Win32 API 光标隐藏定位和键盘读取等常用函数

三、鼠标操作(以下代码环境为 VS2022 C语言)

鼠标操作的开关借用了位段思想,我们想要读取鼠标操作就需要找到 相应开关的数值或宏定义,这里再次放上官方链接:MOUSE_EVENT_RECORD 结构

这里只介绍两个数值:

  1. FROM_LEFT_1ST_BUTTON_PRESSED 0x0001,最左侧的鼠标按钮,检查它是否打开了需要获取 dwButtonState 变量的值,进行 & 运算。
    在这里插入图片描述
  2. MOUSE_MOVED 0x0001,鼠标位置更改,介绍它是防止用户鼠标移动过快,在没有选择区域快速点击时移动到选择区域导致的误操作。
    在这里插入图片描述

获得了鼠标坐标 和 鼠标操作,就可以制作简单的鼠标操作界面了,这里放上一个简易菜单,仅供参考:

#include <stdio.h>
#include <stdbool.h>
#include <Windows.h>void getMouseOperate()			// 鼠标操作获取
{SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_EXTENDED_FLAGS | ENABLE_MOUSE_INPUT);
}void SetPos(short x, short y)	// 输出光标位置
{COORD pos = { x, y };SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}void HideCursor()				// 隐藏光标
{CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &CursorInfo);CursorInfo.bVisible = false; //隐藏控制台光标SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &CursorInfo);
}void menu1()
{getMouseOperate();HideCursor();INPUT_RECORD mouse;DWORD ret;int y = 0;int x = 0;int exitgame = 0;while (true){SetPos(0, 0);//读取输入事件ReadConsoleInput(GetStdHandle(STD_INPUT_HANDLE), &mouse, 1, &ret);DWORD mouseButton = mouse.Event.MouseEvent.dwButtonState;	// 鼠标按键记录DWORD mouseFlag = mouse.Event.MouseEvent.dwEventFlags;		// 鼠标事件记录//获取鼠标当前位置y = mouse.Event.MouseEvent.dwMousePosition.Y;x = mouse.Event.MouseEvent.dwMousePosition.X;char arr[3][13] = { "开始游戏", "调整画面", "退出游戏" };printf("*************************\n");if ((y == 1) && (4 <= x && x <= 20))						// 鼠标坐标在这个范围就打印{printf("****\033[41m     %-10s  \033[0m****\n", &arr[0][0]);	// \033改变颜色}else{printf("****   %-12s  ****\n", &arr[0][0]);}if ((y == 2) && (4 <= x && x <= 20)){printf("****\033[41m     %-10s  \033[0m****\n", &arr[1][0]);}else{printf("****   %-12s  ****\n", &arr[1][0]);}if ((y == 3) && (4 <= x && x <= 20)){printf("****\033[41m     %-10s  \033[0m****\n", &arr[2][0]);}else{printf("****   %-12s  ****\n", &arr[2][0]);}printf("*************************\n");if ((mouseButton & FROM_LEFT_1ST_BUTTON_PRESSED)	// 按位与检查左键按下&& !(mouseFlag & MOUSE_MOVED))					// 按位与检查鼠标未移动{if ((y == 1) && (4 <= x && x <= 20)){system("cls");					// 先清屏//game();getMouseOperate();}if ((y == 2) && (4 <= x && x <= 20)){//getMouseOperate();}if ((y == 3) && (4 <= x && x <= 20)){system("cls");					// 先清屏printf("%s\n", "退出游戏");exitgame = 1;}}Sleep(10);				// 刷新频率if (exitgame){break;}}
}int main()
{menu1();return 0;
}

这段代码源自:C语言两种鼠标操作与扫雷实例

四、注意事项

  1. 使用 system(“cls”) 清屏会导致鼠标操作与坐标获取失效,这个原因暂时还不清楚,使用 它 时只需再次获取鼠标操作即可。
  2. 非鼠标操作会让鼠标操作短暂失效,如此时使用键盘敲击(键盘操作),ReadConsoleInput 函数读取输入缓冲区数据,但不是鼠标数据,在 其 读取下一段数据会有停滞时间,且会随输入的键盘数据次数延长。
  3. 我个人写了一份 Windows C++ 控制台的简易菜单库小项目,方便处理鼠标操作或者键盘操作,有兴趣的读者可以看看:Windows C++控制台菜单库开发与源码展示

http://www.ppmy.cn/news/1536922.html

相关文章

Vue vben admin开源库中table组件tips

table如何自定义表头和自定义内容 自定义表头直接使用tittle&#xff0c;自定义内容是customRender {title: (<span><img src{alvchat_avatar} style"width:20px;height:20px;vertical-align:bottom"></img>{t(routes.alerts.columnsAIReview)}<…

基于STM32的简易交通灯proteus仿真设计(仿真+程序+设计报告+讲解视频)

基于STM32的简易交通灯proteus仿真设计(仿真程序设计报告讲解视频&#xff09; 仿真图proteus 8.9 程序编译器&#xff1a;keil 5 编程语言&#xff1a;C语言 设计编号&#xff1a;C0091 **1.**主要功能 功能说明&#xff1a; 以STM32单片机和数码管、LED灯设计简易交通…

fiddler抓包18-1_导出jmeter脚本(jmx文件)

课程大纲 方法1 ① 下载2个扩展文件&#xff0c;FiddlerExtensions.dll和FiddlerExtensions.pdb&#xff0c;到Fiddler根目录中的“ImportExport”下&#xff0c;重启Fiddler。 下载链接: https://pan.baidu.com/s/1qtLoaiTd-VfHFb3UIPoSZw?pwdtwcu提取码: twcu ② Fiddler导…

世界职业院校技能大赛-软件测试赛项模拟案例参考分享

智慧金融探索&#xff1a;软件测试的未来 一、项目总体思路 本项目“智慧金融探索&#xff1a;软件测试的未来”&#xff0c;旨在通过深入研究和实践&#xff0c;探索软件测试的新高度。不仅追求精益求精的技术水平&#xff0c;更致力于将传统测试方法与现代技术相结合&#x…

Spring Boot 开发详细案例:在线商品管理系统

目录: 项目概述开发环境与依赖配置项目结构设计数据库设计与配置Spring Boot 控制器与业务逻辑实现Spring Security 认证与权限管理前端与后端的交互总结1. 项目概述 在本案例中,我们将开发一个 在线商品管理系统。用户可以通过登录系统查看所有商品、添加新商品、更新商品信…

从零开始搭建一个node.js后端服务项目

一、下载node.js及配置环境 网上很多安装教程&#xff0c;此处就不再赘述了 版本信息 C:\Users\XXX>node -v v20.15.0C:\Users\XXX>npm -v 10.7.0 二、搭建node.js项目及安装express框架 在任意位置创建一个项目文件夹&#xff0c;此处项目文件夹名为test&#xff0…

SQL注入靶场sqli-labs less-4

sqli-labs靶场第三关less-4 1、确定注入点 http://192.168.128.3/sq/Less-4/?id1 http://192.168.128.3/sq/Less-4/?id2 有不同回显&#xff0c;判断可能存在注入&#xff0c; 2、判断注入类型 输入 http://192.168.128.3/sq/less-4/?id1 and 11 http://192.168.128.3/sq/l…

HTTP的请求头有哪些

HTTP请求头包含了很多重要的信息&#xff0c;它们可以分为几个主要类别。以下是常见的HTTP请求头和它们的功能&#xff1a; 1. 通用头部&#xff08;General Headers&#xff09; Cache-Control: 指示请求和响应遵循的缓存策略。 Connection: 控制当前的网络连接选项&#xf…