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

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.xdnf.cn/news/1559293.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

PCL 3D-SIFT关键点检测(Z方向梯度约束

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.1.1 SIFT关键点检测 2.1.2 可视化函数 2.2完整代码 三、实现效果 PCL点云算法汇总及实战案例汇总的目录地址链接&#xff1a; PCL点云算法与项目实战案例汇总&#xff08;长期更新&#…

SQL优化 where谓词条件OR优化

1.测试表&#xff0c;及测试语句where条件中OR对应两个字段选择度很高 create table t618 as select * from dba_objects; select object_name from t618 where (object_id12043 or DATA_OBJECT_ID12043) and STATUSVALID; 2.没有索引情况下&#xff0c;全表扫描逻辑读…

C++核心编程和桌面应用开发 第九天(静态多态 动态多态 纯虚函数 抽象类 虚析构 纯虚析构 向上向下类型转换 重载重写重定义)

目录 1.1静态多态 1.2动态多态 1.2.1满足条件 1.2.2动态多态的使用条件 1.3纯虚函数和抽象类 1.3.1纯虚函数 1.3.2抽象类 1.4虚析构/纯虚析构 1.5向上向下类型转换 1.5.1向下类型转换 1.5.2向上类型转换 1.5.3多态中的类型转换 1.6重载重写重定义 1.6.1重载 1.6…

qwt实现码流柱状图多色柱体显示

qwt实现码流柱状图多色柱体显示 1. 前言2. qt实现柱状图3.qwt基础说明3.1 qwt安装与使用3.1.1 下载qwt源码3.1.2 编译3.1.3 安装3.1.4 使用3.2 QwtPlotBarChart类3.2.1画图步骤3.2.2 specialSymbol3.3.3 barTitle4 BsBarChart定制4.1 每个柱体可以显示不同的颜色4.2 每个柱体可…

MFC项目如何使用hiredis库连接redis

如何在windows平台使用c连接redis 1. 下载hiredis的vs工程文件2. 使用vs2022编译hiredis3.项目中调用4. 集群连接5. 简单的封装下 最近需要在windows PC终端读取redis数据。我这里使用hiredis连接redis. 工程是vs2022开发的。 1. 下载hiredis的vs工程文件 windows平台需要自己…

【C/C++】错题记录(七)

题目一 题目二 C在调用函数时&#xff0c;当实参和形参的数据类型不一致时&#xff0c;会发生数据类型转换&#xff01;将低精度转换为高精度时&#xff0c;由编译器隐式完成&#xff1b;将高精度转换为低精度时&#xff0c;必须用强制类型转换运算符&#xff1b; static_cast…

Redis:分布式 - 哨兵

Redis&#xff1a;分布式 - 哨兵 概念哨兵 Docker 搭建哨兵分布式选举流程 概念 Redis 的主从复制模式下&#xff0c;一旦主节点由于故障不能提供服务&#xff0c;需要人工进行主从切换&#xff0c;同时大量的客户端需要被通知切换到新的主节点上&#xff0c;对于上了一定规模…

浏览器动态移动的小球源码分享

浏览器动态移动的小球源码分享 <script>(function(a){var width100,height100,borderRadius100,circlefunction(){};circle.prototype{color:function(){let colour "#"Math.floor(Math.random()*255).toString(16)Math.floor(Math.random()*255).toString…

Linux块设备驱动实验

直接参考【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.81 本文仅作为个人笔记使用&#xff0c;方便进一步记录自己的实践总结。 前面我们都是在学习字符设备驱动&#xff0c;本章我们来学习一下块设备驱动框架&#xff0c;块设备驱动是Linux 三大驱动类型之一。块设备驱动要远…

Fiddler配合wireshark解密ssl

环境&#xff1a; win11&#xff08;wireshark&#xff09;--虚拟机win7&#xff08;Fiddler&#xff09;---虚拟机win7&#xff08;HTTPS站点&#xff09; 软件安装问题&#xff1a; 需要.net环境&#xff0c;NDP461-KB3102436-x86-x64-AllOS-ENU.exe。 安装fiddler后安装下…

vite项目打包md5报‘default‘ is not exported错误的解决方法

报错如下&#xff1a; 某一个包中用es方式引入md5模块&#xff0c;导致打包报错&#xff0c;经过一番探究测试&#xff0c;发现我的项目中用了“vite-plugin-require-transform”这个插件&#xff0c;是这个插件在做转换的时候报错了&#xff0c;如果你也是这个原因可按我的方式…

代码随想录day24:贪心part2

121. 买卖股票的最佳时机 class Solution {public int maxProfit(int[] prices) {int ans 0;int minPrice prices[0];for(int p : prices){ans Math.max(ans, p - minPrice);minPrice Math.min(p, minPrice);}return ans;} } 运用前缀和思想&#xff0c; 从左到右枚举卖…

Spring Boot教学资源库:构建微服务的基石

2 相关技术简介 2.1Java技术 Java是一种非常常用的编程语言&#xff0c;在全球编程语言排行版上总是前三。在方兴未艾的计算机技术发展历程中&#xff0c;Java的身影无处不在&#xff0c;并且拥有旺盛的生命力。Java的跨平台能力十分强大&#xff0c;只需一次编译&#xff0c;任…

dart-sass和node-sass的区别,使用dart-sass后可能会出现的问题

前言&#xff1a; 2020 年 10 月 27 日&#xff0c;Sass 官方团队正式宣布 Libsass 将弃用&#xff0c;以及基于它的 Node Sass 和 SassC&#xff0c;并且建议用户使用 Dart Sass。如果在 vue 脚手架搭建的项目中需要使用 sass&#xff0c;建议初始化时勾选 sass 配置&#xff…

前端优化之路:git commit 校验拦截

但是想要做到高效落地执行&#xff0c;就需要做些别的功课&#xff0c;先展示下成果图 需要了解git hooks&#xff0c;它是git的钩子&#xff0c;就像vue拥有自己的钩子一样。 官方文档&#xff1a;https://git-scm.com/docs/githooks 项目安装 husky&#xff0c;建议稳定版本…

patch函数前两个参数位

如我们多了解的&#xff0c;patch函数前两个参数位为 oldVnode 和 Vnode &#xff0c;分别代表旧节点和新节点&#xff0c;主要做了四个判断&#xff1a; patchVnode主要做了两个判断&#xff1a;

java中Math类和Random类的api

目录 Math 类 1&#xff09;abs(x) 2&#xff09;ceil(x) 3&#xff09;floor(x) 4&#xff09;round(x) 5&#xff09;max(x, y) 6&#xff09;min(x, y) 7&#xff09;sqrt(x) 8&#xff09;pow(x, y) 9&#xff09;random() 10&#xff09;sin(x), cos(x), tan(x) 11&#x…

购物网站毕业设计-电子电器商城管理系统SpringBootSSM框架开发

目录 1. 系统概述 1.1背景介绍 1.2 课题意义 1.3课题目标 2. 主要功能模块 2.1 前端用户模块 2.2 后端管理模块 2.3 功能图展示 3. 技术选型 3.1 VUE介绍 3.2 JAVA介绍 3.3 MySQL介绍 4. 系统设计 4‌.1数据库设计 5 详细设计 5.1 界面展示 设计一个电子电…

通过Keil5编译软件获取函数最深堆栈

文章目录 1.问题提出2.问题分析3.环境搭建4.解决方案5.经验总结6.疑问 1.问题提出 在编写新软件时&#xff0c;由于某功能需要使用RAM约24KB&#xff0c;而新模块的总RAM是96KB&#xff0c;该某功能一旦开启则需要占用四分之一的RAM空间。为了保证在开启某功能后&#xff0c;新…

WPF中的Window类

控件分类 在第一篇文章.Net Core和WPF介绍中的WPF的功能和特性部分根据功能性介绍了WPF的控件 名称。 在接下来的文章中&#xff0c;将会详细的介绍各个控件的概念及使用。 主要包括&#xff1a; 内容控件&#xff1a;Label、Button、CheckBox、ToggleButton、RadioButton、…