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

news2024/10/9 20:32:12

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 8

void 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 8

void 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.coloradmin.cn/o/2199926.html

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

相关文章

阿里出品2024版Java架构师面试指南,涵盖Java所有核心技能

最近很多粉丝朋友私信我说&#xff1a;熬过了去年的裁员潮却没熬过现在的内卷&#xff1b;打开Boss直拒一排已读不回&#xff0c;回的基本都是外包&#xff0c;薪资还给的不高&#xff0c;对技术水平要求也远超从前&#xff1b;感觉Java一个初中级岗位有上千人同时竞争&#xf…

面试必备:经典的 Shell 十三问!

1. 为何叫做shell? 我们知道计算机的运作不能离开硬件&#xff0c;但使用者却无法直接操作硬件&#xff0c;硬件的驱动只能通过一种称为操作系统(OS&#xff0c;Operating System)的软件来管控。linux严格来说只是一个操作系统&#xff0c;我们称之为内核(kernel)。 使用者没…

基于vue框架的大学生在线教育jp6jw(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;学生,教师,课程类型,课程信息,资料类型,课程资料,课程名称,选课信息,学生咨询,教师回复 开题报告内容 基于Vue框架的大学生在线教育平台开题报告 一、课题背景 随着互联网技术的飞速发展和全球教育需求的日益多元化&#xff0c;在线教…

单细胞转录组 —— STARsolo 原始数据处理

单细胞转录组 —— STARsolo 原始数据处理实战 前言 前面我们已经介绍了几种原始数据处理工具&#xff0c;最后再介绍一种多平台兼容的快速定量工具 —— STARsolo。 主要使用的还是 STAR 比对软件&#xff0c;只是增加了更多对单细胞数据的处理&#xff0c;不同平台数据的差…

2.5 Spring Boot整合Spring MVC框架

今天&#xff0c;我将向大家介绍如何在Spring Boot中整合Spring MVC框架&#xff0c;并展示如何创建和测试控制层&#xff08;Controller&#xff09;。 首先&#xff0c;让我们简要回顾一下Spring MVC。Spring MVC是一个基于Servlet的MVC框架&#xff0c;它简单、侵入性小&am…

安装Node.js环境,安装vue工具

一、安装Node.js 去官方网站自行安装自己所需求的安装包 这是下载的官方网站 下载 | Node.js 中文网 给I accept the terms in the License Agreement打上勾然后点击Next 把安装包放到自己所知道的位置,后面一直点Next即可 等待它安装好 然后winr打开命令提示符cmd 二、安装…

线稿如何快速上色?AI自动线稿上色教程分享

前言 在数字艺术的世界里&#xff0c;上色是一个既耗时又需要技巧的步骤。幸运的是&#xff0c;随着AI技术的发展&#xff0c;我们有了像千鹿AI这样的工具&#xff0c;它可以帮助艺术家和设计师自动完成线稿的上色工作。以下是使用千鹿AI进行自动线稿上色的详细教程。 准备工作…

vue 入门二

参考&#xff1a;丁丁的哔哩哔哩 11.组件基础 传递 props 1.父组件 <BlogPost title"My journey with Vue" />子组件<script setup> defineProps([title]) </script><template><h4>{{ title }}</h4> </template>2.prop…

【springboot9736】基于springboot+vue的逍遥大药房管理系统

作者主页&#xff1a;Java码库 主营内容&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 项目描述 伴随着全球信息化发展&#xff0c;行行业业都与计算机技…

10月9日

没看清x的范围

前端vue-配置请求拦截器

1.配置拦截器&#xff0c;记得20行的导出 2.响应拦截器&#xff0c;记得28行的导出 3.拦截器不止可以拦截&#xff0c;还可以添加内容

KaTeX.js渲染数学公式

什么是KaTeX.js ? KaTeX 是一个集成速度快且功能丰富的数学公式渲染库&#xff0c;专为 Web 设计。它由 Khan Academy 开发&#xff0c;提供接近印刷品质的数学公式展示&#xff0c;同时保持与浏览器的高效互动性。KaTeX 特点包括快速渲染速度、高质量的输出、独立运行、跨平…

IP-guard与Ping32功能对比:谁更适合你的企业?

在当今数字化时代&#xff0c;数据泄露已成为企业面临的一大挑战。为了保障信息安全&#xff0c;众多企业选择部署数据防泄漏&#xff08;DLP&#xff09;软件。IP-guard和Ping32作为市场上备受瞩目的两款产品&#xff0c;各自具有独特的功能和优势。那么&#xff0c;哪款软件更…

阿里云 CDN如何缓解ddos攻击

在网络安全日益重要的今天&#xff0c;DDoS攻击已成为企业面临的主要威胁之一。阿里云CDN&#xff08;内容分发网络&#xff09;以其强大的防护能力&#xff0c;成为抵御DDoS攻击的利器。九河云来和大家聊聊阿里云 CDN是如何缓解ddos攻击的吧。 首先&#xff0c;阿里云CDN通过…

CentOS7.9 下安装 Docker

第一步&#xff1a; sudo yum install -y yum-utils \ > device-mapper-persistent-data \ > lvm2 第二步&#xff1a;安装 sudo wget -O /etc/yum.repos.d/docker-ce.repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo sudo yum -y install…

旧衣回收小程序开发:开启线上旧衣回收新时代

近几年&#xff0c;旧衣回收这种新的理念在市场中兴起&#xff0c;并快速得到了发展&#xff0c;让大众闲置的衣物得到了归处&#xff0c;减少了资源浪费&#xff0c;深受大众的关注。 在科技的创新下&#xff0c;旧衣回收迎来了新的发展方式---旧衣回收小程序&#xff0c;以信…

Hugging face简要介绍

1.注册使用huggingface 2.在Datasets下可以查看数据集 3.在Models下可以查看模型&#xff0c;左侧是对模型的分类 4.官方文档查看https://huggingface.co/docs 5.主要模型&#xff1a; 自回归&#xff1a;GPT、Transformer-XL、XLNet 自编码&#xff1a;BERT、ALBERT、RoBERT…

JDK17安装教程

1.双击安装包 2.配置环境变量&#xff08;可选&#xff09;注意&#xff1a;JDK下载路径默认选择C盘 右键点击此电脑选择属性&#xff08;找到高级系统设置&#xff09; 点击环境变量 在系统变量中新建两个新的变量&#xff1a;CLASSPATH和JAVA_HOME CLASSPATH内容值为&…

10.9今日错题解析(软考)

目录 前言系统开发基础——耦合性I/O设备 前言 这是用来记录我备考软考设计师的错题的&#xff0c;今天知识点为耦合性、I/O设备&#xff0c;大部分错题摘自希赛中的题目&#xff0c;但相关解析是原创&#xff0c;有自己的思考&#xff0c;为了复习&#xff1a;&#xff09;&a…

嵌入式C语言自我修养:ARM体系结构与汇编语言

ARM体系结构 ⭐ 关联知识点&#xff1a;指令集 计算机的指令集一般可分为4种&#xff1a;复杂指令集(CISC)、精简指令集(RISC) 、显式并行指令集 (EPIC)和超长 指 令 字 指 令 集(VLIW)。嵌入式用的是RISC指令集&#xff0c;RISC指令集相对于CISC指令集&#xff0c;主要有以下…