(十五)EGE鼠标消息

目录

  • 鼠标消息处理与事件检测
    • 鼠标消息检测示例
    • 鼠标消息
      • 1.鼠标消息的种类
      • 2. 鼠标消息结构体
    • 鼠标消息处理循环
      • 定义 mouse_msg变量
      • 鼠标消息的处理
      • 鼠标消息的判断
      • 为什么要用while 来处理鼠标消息
      • 使用while来处理会出现的问题
    • 鼠标的点击判断
      • 鼠标的点击判断
        • 判断鼠标点击的错误示例
        • 判断鼠标点击的正确方法
      • 鼠标其它消息判断
        • 鼠标位置
        • 鼠标按键按下松开状态
        • 鼠标滚轮
    • 鼠标实时位置的获取
    • 鼠标消息的初始化问题及消息的检测分析
      • (1) 未初始化的问题
      • (2) 放在while()中检测
      • (3) 每次循环前的状态初始化
      • (4) 正确的鼠标点击判断(状态初始化 + 检测每一条鼠标消息)
      • (5) 鼠标消息检测的总结
    • 鼠标消息的处理及状态的保存
    • 鼠标状态类(MouseState)
      • MouseState类使用示例
    • 设置鼠标是否显示
    • 清空鼠标消息缓存区
  • 鼠标操作实例
    • 格子点击
    • 鼠标按住拖动
    • 鼠标选择拖动

鼠标消息处理与事件检测

鼠标消息检测示例

  • 鼠标消息检测的大致结构如下:
  1. 设置状态位
  2. 状态位初始化
  3. 处理鼠标消息,根据鼠标消息修改状态位
  4. 根据状态位判断 鼠标是否进行了操作
  5. 重复步骤 2, 3 , 4
//设立标志位
bool flag;

for (; is_run(); delay_fps(60)) {
	//创建鼠标消息结构体变量,并初始化
	mouse_msg msg = { 0 };
	
	//标志位状态重置
	flag = false;
	
	while (mousemsg()) {
		msg = getmouse();
		
		//对消息进行检测,只进行简单操作
		if (鼠标进行了操作)
			flag = true;
	}

	//标志位判断并做处理
	if (flag) {
		//做绘图等操作
	}
}

鼠标消息

1.鼠标消息的种类

  鼠标正常放着时,是不会产生消息的。能让鼠标产生消息的,有以下几种:

  • 鼠标移动 (产生大量消息)
  • 按下、松开按键 (按下发送一条,松开发送一条)
  • 滑动滚轮 (滚动一格发送一条)

2. 鼠标消息结构体

  这几种鼠标消息,在EGE中并不区分开来发送,都是保存到一个鼠标消息结构体里,通过一些标志位来区分不同的消息,并且包含产生消息时鼠标的状态, 比如鼠标位置,鼠标左右键、中键有没有按下,滚轮是否滑动,向哪边滑动等。

鼠标消息结构体定义如下

typedef struct mouse_msg {
	int             x;
	int             y;
	mouse_msg_e     msg;
	unsigned int    flags;
	int             wheel;
	//成员函数
	bool is_left() { return (flags & mouse_flag_left) != 0; }
	bool is_right() { return (flags & mouse_flag_right) != 0; }
	bool is_mid() { return (flags & mouse_flag_mid) != 0; }
	
	bool is_down() { return msg == mouse_msg_down; }
	bool is_up() { return msg == mouse_msg_up; }
	bool is_move() { return msg == mouse_msg_move; }
	bool is_wheel() { return msg == mouse_msg_wheel; }
}mouse_msg;

参数说明

  • x, y
    鼠标的在窗口的坐标位置
  • wheel
    滚轮滚动值, 一般为120的倍数或约数, 前滚为正,后滚为负。
  • msg
    鼠标消息类型
    鼠标消息类型的一些枚举值()

mouse_msg_down, 鼠标按键按下消息
mouse_msg_up, 鼠标按键松开消息
mouse_msg_move,鼠标移动消息
mouse_msg_wheel, 鼠标滚轮消息

不建议直接访问,EGE已经有相应的函数来判断鼠标消息了

  • flags
    鼠标消息标志位,说明鼠标消息是左键、右键、中键滚轮不仅可以滚动,还可以往下按,往下按时就作为中键
typedef enum mouse_flag_e {
	mouse_flag_left     = 1,
	mouse_flag_right    = 2,
	mouse_flag_mid      = 4,
	mouse_flag_shift    = 0x100,
	mouse_flag_ctrl     = 0x200,
}mouse_flag_e;

不建议直接访问,EGE已经有相应的函数来判断鼠标消息了

鼠标消息结构体中的一些成员函数(C++部分)
用来判断鼠标消息类型和标志位的
下面几个是判断是鼠标哪个按键产生的消息(通过标志位判断)。

	bool is_left()  { return (flags & mouse_flag_left) != 0; }
	bool is_right() { return (flags & mouse_flag_right) != 0; }
	bool is_mid()   { return (flags & mouse_flag_mid) != 0; }

下面几个是用来判断鼠标的操作的,是按键按下,还是松开、移动、滚轮滚动

	bool is_down()  { return msg == mouse_msg_down; }
	bool is_up()    { return msg == mouse_msg_up; }
	bool is_move()  { return msg == mouse_msg_move; }
	bool is_wheel() { return msg == mouse_msg_wheel; }

比如
是否是鼠标左键按下的消息

if (mouseMsg.is_left() && mouseMsg.is_down())

鼠标消息处理循环

定义 mouse_msg变量

  • 一开始鼠标不动是没有消息产生的,而且鼠标进入EGE的图形窗口后才开始产生鼠标消息,离开后窗口后鼠标消息就不发送到EGE窗口。当然,如果你在窗口内按下按键不松开,把鼠标移出窗口,依然算EGE窗口的鼠标消息。
  • 先定义一个鼠标消息结构体变量
	mouse_msg msg;

获取鼠标消息之前,一定要先对mouse_msg 初始化, 否则可能会出现判断错误

mouse_msg msg;

for ( ; is_run(); delay_fps(60)) {
	//初始化
	msg = mouse_msg();
	
	//鼠标消息的处理
}

当然,将mouse_msg 结构体变量直接定义在帧循环中, 并给出初始化值,每次进入循环,mouse_msg 结构体变量都将会被初始化,这也是可以的

for ( ; is_run(); delay_fps(60)) {
	//每次循环都重新创建并初始化
	mouse_msg msg = {0};
	
	//鼠标消息的处理
}

鼠标消息的处理

  • mousemsg() 函数判断有没有鼠标消息,之后利用getmouse() 获取鼠标消息。
  • mousemsg() ,有鼠标消息则返回1,没有返回0.
  • getmouse(), 获取鼠标消息,返回鼠标消息结构体,没有鼠标消息就暂停程序,等待鼠标消息产生。

这里有两种处理方式,一种是 使用 if()

//如果有鼠标消息
if (mousemsg()) {
	//读取鼠标消息
	msg = getmouse();
}

另一种是使用 while()我们应该使用这种

//循环检测是否有鼠标消息
while (mousemsg()) {
	//有则读取
	msg = getmouse();
}

所以鼠标消息处理写起来就是

for ( ; is_run(); delay_fps(60)) {
	//每次循环都重新创建并初始化
	mouse_msg msg = {0};
	
	//鼠标消息的处理
	while (mousemsg()) {
		//有则读取
		msg = getmouse();
	}
	
	//使用鼠标消息
}

鼠标消息的判断

上面经过 msg = getmouse() ,已经获得了鼠标消息,下面可以使用这个结构体来获取信息。可以查看上面鼠标消息结构体的定义

  • 判断是哪个按键
    可以使用msg.is_left(), msg.is_right() , msg.is_mid() 等判断是哪个鼠标按键的消息。
  • 获取鼠标的位置(鼠标消息产生时)
    直接使用 msg.x, msg.y 即可,分别是x坐标和y坐标
    也可以使用 mousepos() 获取鼠标实时位置
  • 判断是否是鼠标移动的消息
    msg.is_move()
  • 判断是否是滚轮滚动的消息
    msg.is_wheel()
  • 获取滚轮滚动的方向
    msg.wheel, 正数为前滚,负数为后滚,0为没有滚动
    可以组合起来使用

是否是鼠标左键按下的消息

if (msg.is_left() && msg.is_down())

鼠标点击可以查看后面鼠标消息的判断

为什么要用while 来处理鼠标消息

  因为鼠标移动时,每一秒大约会产生 150条 左右的鼠标移动消息。

  1. if 的写法, 如果帧率不够时,会产生滞后
    比如,正常的是每秒处理60帧(这是因为画太快会加重cpu负担,也没必要,60帧已经能够有很好的视觉效果,所以循环一次后,会延时一段时间后再画一次),那么每秒就进入60次循环,只能获取并处理 60 条鼠标消息,没有处理的会堆积起来。那么移动1秒的鼠标,要两秒多才能处理完。
  2. while 的写法,能很快地将当前剩余的鼠标消息处理完成。
    因为cpu的速度非常快,如果按60帧的速率,鼠标每秒产生150条消息,那么每一帧会产生两三条鼠标消息,这时在while循环,这几条鼠标消息将被一次处理完,只保留最后一次鼠标消息,就不会产生滞后了,而且这种鼠标消息的快速产生,是鼠标移动时的情况,快速处理,可以跟得上鼠标的位置。

所以一般我们用 while() 的写法
下面来运行个示例,来看看两种写法的区别:
把下面一个处理鼠标消息的while , 改成 if ,看看鼠标消息处理滞后的情况
还有个 cleardevice() ,把这一行注释掉试试,看看清屏的作用

这是一个红色小圆跟随鼠标移动的示例,读取鼠标移动消息,获取鼠标所在位置,然后绘制。

#include 

int main()
{
	setinitmode(INIT_RENDERMANUAL, 100, 100);		//设置为手动模式, 设置窗口位置
	initgraph(640, 640);			//初始化窗口
	setcaption("EGE鼠标消息处理");	//设置窗口标题

	setbkcolor(WHITE);		//设置窗口背景为白色
	setcolor(RED);			//设置前景色为红色
	setfillcolor(RED);		//设置填充色为红色
	
	
	for (; is_run(); delay_fps(60)) {
		//定义个鼠标结构体变量, 并初始化
		mouse_msg msg = { 0 };

		//改下面这个 while, 改成 if 试试
		//获取鼠标消息
		while (mousemsg()) {
			msg = getmouse();
		}

		//鼠标移动则绘画
		if (msg.is_move()) {
			//清屏, 可以注释掉试试
			cleardevice();

			//画红色小圆
			fillellipse(msg.x, msg.y, 30, 30);
		}
	}
	
	closegraph();

	return 0;
}

使用while来处理会出现的问题

  鼠标移动时会产生大量的消息,所以在每一帧的鼠标消息处理循环中,如果鼠标正在移动,那么就会处理到多条鼠标消息,只保留最后一条。所以如果你的鼠标在点击时移动,点击的消息很有可能被移动的消息覆盖掉
  所以应该使用状态位,在while循环中,对每一条消息进行判断,设置标志位。处理完成后,再通过标志位来判断鼠标的事件。
  后面会有消息判断的示例。

鼠标的点击判断

鼠标的点击判断

  • 假设 有个 mouse_msg 类型的变量 msg , 里面保存有鼠标消息。
  • 判断是鼠标左键按下的消息
    鼠标左键按下后会发送一条按下的消息
if (msg.is_left() && msg.is_down()) {
	//鼠标左键处于按下状态
}
  • 判断是鼠标左键松开的消息
    鼠标左键松开时会发送一条鼠标左键松开的消息
if (msg.is_left() && msg.is_up()) {
	//鼠标左键处于按下状态
}
  • 中键,右键类似

判断鼠标点击的错误示例

下面这个鼠标点击判断,对鼠标消息进行处理后,在外面进行判断。要知道,鼠标移动时会产生大量的消息,所以在每一帧的鼠标消息处理循环中,如果鼠标正在移动,那么就会处理到多条鼠标消息,只保留最后一条。所以你的鼠标在点击时稍微移动,点击的消息很有可能被移动的消息覆盖掉,就判断不出来。

点击时移动试试,看看能不能正确判断点击

#include 

int main()
{
	initgraph(640, 480, INIT_RENDERMANUAL);			//初始化窗口
	setcaption("EGE鼠标点击错误示例");	//设置窗口标题

	setbkcolor(WHITE);		//设置窗口背景为白色
	setcolor(BLACK);		//设置前景色为黑色
	setfillcolor(EGERGB(0xff, 0x90, 0xff));	
	setfont(20, 0, "楷体");
	setfontbkcolor(WHITE);
	
	int clickCount = 0;
	for (; is_run(); delay_fps(60)) {
		//定义个鼠标结构体变量
		mouse_msg msg = { 0 };
		//获取鼠标消息
		while (mousemsg()) {
			msg = getmouse();
		}

		if (msg.is_left() && msg.is_down()) {
			clickCount++;
			fillellipse(msg.x, msg.y, 40, 40);
		}
			
		xyprintf(20, 20, "点击次数:%d", clickCount);
	}

	closegraph();

	return 0;
}

判断鼠标点击的正确方法

应该设立一个点击标志位,在鼠标消息处理循环检测点击事件,有点击就把标志位设为true。然后再在外面通过标志位检测。但是每次鼠标消息循环之前,都要把标志位设置 false

#include 

int main()
{
	initgraph(640, 480, INIT_RENDERMANUAL);			//初始化窗口

	setcaption("EGE鼠标点击正确示例");	//设置窗口标题

	setbkcolor(WHITE);		//设置窗口背景为白色
	setcolor(BLACK);		//设置前景色为黑色
	setfillcolor(EGERGB(0xff, 0x90, 0xff));
	setfont(20, 0, "楷体");
	setfontbkcolor(WHITE);

	int clickCount = 0;
	bool isClick;
	int xClick, yClick;
	for (; is_run(); delay_fps(60)) {
		//标志位设为false, 这是必要的步骤
		isClick = false;
		
		//获取鼠标消息
		while (mousemsg()) {
			mouse_msg msg = getmouse();

			//在消息处理循环中判断点击
			if (msg.is_left() && msg.is_down()) {
				isClick = true;
				xClick = msg.x;
				yClick = msg.y;
			}
		}

		//在这里通过标志位来判断
		if (isClick) {
			clickCount++;
			fillellipse(xClick, yClick, 40, 40);
		}	

		xyprintf(20, 20, "点击次数:%d", clickCount);
	}

	closegraph();

	return 0;
}

(十五)EGE鼠标消息_第1张图片

鼠标其它消息判断

鼠标位置

  • 获取鼠标产生消息时的位置
    位置的坐标(x, y) 就是(msg.x, msg.y), 直接使用即可

或者使用 mousepos() 获取鼠标实时位置

int x, y;
mousepos(&x, &y);

现在x, y就是鼠标所在的位置了

鼠标按键按下松开状态

可以使用 keystate() 来获取
鼠标左中右三个键的键码如下:

	key_mouse_l     = 0x01,		//左键
	key_mouse_r     = 0x02,		//右键
	key_mouse_m     = 0x04,		//中键

检测鼠标左键是否是按下状态

if (keystate(key_mouse_l)) {
}

鼠标滚轮

  • 滚轮滚动
    滚轮是有齿的,一格一格地转动,每转动一格发送一次消息。
  • 判断是否是滚轮滚动的消息
if (msg.is_wheel())
  • 判断滚轮滚动方向
//先判断是否是滚轮滚动的消息
if (msg.is_wheel()) {
	if (msg.wheel > 0) {
		//滚轮前滚
	}
	else {
		//滚轮后滚
	}
}
  • 判断是否是鼠标移动的消息
if (msg.is_move()) {
	//鼠标产生了移动消息
	//可以通过 msg.x, msg.y获取鼠标移动消息产生时的位置
}

shift键 和 ctrl
  这两个键盘上的辅助键的标志位 只在鼠标移动或者滚轮滚动有效
  如果鼠标在移动或滚轮滚动的同时有按住这两个辅助键,标志位就有效

  • 判断移动或滚轮滚动的同时是否按下 shift 键
if (msg.flags == mouse_flag_shift) {
	//移动或滚轮滚动的同时按下了 shift 键
}
  • 判断移动的同时是否按下 ctrl 键
if (msg.flags == mouse_flag_ctrl) {
	//移动或滚轮滚动的同时按下了 ctrl 键
}

下面做个鼠标消息判断的示例:
请注意这里的结构体变量没有进行初始化

  • 没有进行初始化是为了方便显示鼠标的消息信息,否则每一个循环,初始化后,上一次的消息将会丢失,消息显示一闪而过,无法保留。
  • 这里显示的是单独一条鼠标消息的内容。所以没有用标志位。

#include 

int main()
{
	setinitmode(INIT_RENDERMANUAL, 100, 100);		//设置为普通窗口, 设置窗口位置
	initgraph(640, 480);			//初始化窗口
	setcaption("EGE鼠标消息获取");	//设置窗口标题

	setbkcolor(WHITE);
	setcolor(BLACK);
	setfont(18, 0, "黑体");

	mouse_msg msg = { 0 };
	for (; is_run(); delay_fps(60))
	{
		//获取鼠标消息,这个函数会等待,等待到有消息为止
		//类似地,有和kbhit功能相近的函数MouseHit,用于检测有没有鼠标消息
		while (mousemsg())
		{
			msg = getmouse();
		}

		//格式化输出为字符串,用于后面输出
		//msg和flag常数请参考文档或者mouse_msg_e, mouse_flag_e的声明

		cleardevice();
		xyprintf(0, 20, "鼠标消息位置:  x   = %4d     y = %4d",
			msg.x, msg.y);
		xyprintf(0, 40, "消息类型:move  = %d   down  = %d    up  = %d",
			(int)msg.is_move(),
			(int)msg.is_down(),
			(int)msg.is_up());
		xyprintf(0, 60, "按键:    left  = %d    mid  = %d  right = %d",
			(int)msg.is_left(),
			(int)msg.is_mid(),
			(int)msg.is_right());
		xyprintf(0, 80, "滚轮:    wheel = %d        wheel rotate = %d",
			(int)msg.is_wheel(),
			msg.wheel);
		xyprintf(0, 100, "辅助键:   shift = %d    ctrl = %d",
			msg.flags == mouse_flag_shift,
			msg.flags == mouse_flag_ctrl);
	}

	getch();

	closegraph();

	return 0;
}

鼠标实时位置的获取

  获取鼠标的当前位置,可以用 mousepos(), 这个不依赖于鼠标消息,不产生鼠标消息也能获取鼠标当前位置

  • 所以想要获取鼠标当前的位置,用这个函数。
  • 鼠标消息中的鼠标位置消息产生时的鼠标位置,如果消息处理不及时,那就和当前的位置可能差很大。
    函数声明如下:
int mousepos(int *x, int *y);

传入的是x, 和y的地址,调用后,会修改x和y,x和y就保存了鼠标的位置。
使用示例:

int x, y;
mousepos(&x, &y);

但是如果鼠标位置是用来作点击判断,最好还是用鼠标点击时的 msg.x, msg.y, 因为如果点击后快速移动鼠标,但帧率较小时,鼠标位置可能和点击位置相差极大。


鼠标消息的初始化问题及消息的检测分析

(1) 未初始化的问题

请注意上面我们把结构体的变量定义在了哪个位置??

  • 是定义在了帧绘制循环的外面, 每次循环没有进行初始化
mouse_msg msg = { 0 };

for (; is_run(); delay_fps(60)){
	while (mousemsg()) {
		msg = getmouse();
	}
	
}

这样我们每次循环,鼠标消息结构体还是保留上次的状态所以如果没有鼠标消息那么下一次检测的就会是之前的消息所以这样会有什么问题??

这涉及到鼠标消息初始化的问题, 下面我们做个区别的示例

  • 下面这个是没有进行初始化的
    鼠标不动,点击一下试试。点击之后移动又如何
    把初始化部分 取消注释,看看情况发生了什么变化
#include 

int main()
{
	initgraph(300, 300);
	setbkcolor(WHITE);
	setcolor(0xFF0000);

	int leftClickCount = 0;
	mouse_msg msg = { 0 };

	for (; is_run(); delay_fps(60)) {
		//初始化
		//msg = mouse_msg();
		
		while (mousemsg()) {
			msg = getmouse();	
		}

		if (msg.is_left() && msg.is_down())
			leftClickCount++;

		xyprintf(20, 50, "鼠标左键点击次数:%3d", leftClickCount);
	}

	getch();

	closegraph();
	return 0;
}

这个小示例中,左键点击一次, 计数就会增很多,如果你按住不放,就会一直增.因为上面鼠标左键点击的次数是根据消息是否是左键而且是按下的消息来计算的。按住不放后,只产生一条按下的消息,每次循环,检测的msg保存的依然是之前的消息。所以就会一直计数。然后做松开或者移动等其它动作时,消息改变,就停止计数。
  所以做状态检测时,需要保证每次循环开始,对要检测的状态进行初始化。

(2) 放在while()中检测

那我们可以放在while循环中,没有鼠标消息就不会执行检测。这样能保证即使我们没有给mouse_msg变量初始化,但我们检测的仍是最新的消息。

while (mousemsg()) {
	msg = getmouse();	
	
	if (msg.is_left() && msg.is_down())
		leftClickCount++;
}

再来运行一下试试

#include 

int main()
{
	initgraph(300, 300);
	setbkcolor(WHITE);
	setcolor(0xFF0000);

	int leftClickCount = 0;
	mouse_msg msg = { 0 };

	for (; is_run(); delay_fps(60)) {
		while (mousemsg()) {
			msg = getmouse();
			
			//鼠标左键点击计数
			if (msg.is_left() && msg.is_down())
				leftClickCount++;

		}
	
		xyprintf(20, 50, "鼠标左键点击次数:%3d", leftClickCount);
	}

	getch();

	closegraph();
	return 0;
}
  • 可以发现已经没问题了,点击计数正常。
  • 但是如果我们是要在判断是鼠标点击后绘图,还有其他一些复杂的操作呢?这样就不可能放在while循环中了,这些操作可能会很耗时,也消耗CPU性能, 就可能会出现卡顿,窗口闪烁等。
  • 所以这样放在 while() 循环中,对每一条鼠标消息进行处理的方法只适合一些简单的操作

(3) 每次循环前的状态初始化

所以我们 可以把mouse_msg 变量定义在帧绘制循环中 ,这样 每次进行鼠标消息处理之前都会被初始化 那么没有鼠标消息的时候,因为已经被初始化了,所以判断毫无问题。

  • 重要的是要进行状态初始化, 而不是定义在帧绘制循环中,定义在外面也行,需要在每次循环时对我们的状态进行一次状态初始化。
  • 来运行一下试试
#include 

int main()
{
	initgraph(300, 300);
	setbkcolor(WHITE);
	setcolor(0xFF0000);

	int leftClickCount = 0;

	for (; is_run(); delay_fps(60)) {
		//放在了帧绘制循环中
		mouse_msg msg = { 0 };

		while (mousemsg()) {
			msg = getmouse();
		}

		//鼠标左键点击计数
		if (msg.is_left() && msg.is_down())
			leftClickCount++;

		xyprintf(20, 50, "鼠标左键点击次数:%3d", leftClickCount);
	}

	getch();

	closegraph();
	return 0;
}

可以看到,鼠标不动时点击是毫无问题的,如果鼠标边移动边点击,那么很大可能是不会计数的
为什么?因为while()循环只保留最后一次的消息,你把检测消息放在了while()循环外,而鼠标移动时是会发出大量的消息的,那么很有可能会忽略掉鼠标移动的消息

(4) 正确的鼠标点击判断(状态初始化 + 检测每一条鼠标消息)

所以正确的应该是,在帧绘制循环的开始处放个点击的标志位,自动会进行一次初始化,你定义在帧循环之外也行,重要的是每次进入循环都要进行一次状态重置,.然后在while()循环对每一条鼠标消息进行判断。如果是点击的鼠标消息,设置点击的标志有效,这样鼠标无论怎么移动,都是计数正确的

鼠标点击计数的正确示例如下:

  • 我们使用状态位作鼠标左键点击的记录,不需要每次都初始化mouse_msg, 初始化这个标志位就行了,因为当有消息时才会进行鼠标消息检测,所以能够保证检测的msg都是最新的值,而且每一条都进行了检测,不漏过每一条鼠标消息。复杂的绘图放在了帧循环中,控制一定的帧率。
int main()
{
	initgraph(300, 300);
	setbkcolor(WHITE);
	setcolor(0xFF0000);

	int leftClickCount = 0;
	
	mouse_msg msg = { 0 };

	for (; is_run(); delay_fps(60)) {
		//对状态位进行初始化(变量定在循环体内,每次进入循环都会初始化)
		bool isLeftClick = false;
		
		while (mousemsg()) {
			msg = getmouse();

			//鼠标左键点击判断
			if (msg.is_left() && msg.is_down()) {
				//改变状态位
				isLeftClick = true;
			}
		}
		
		//对状态位进行检测
		if (isLeftClick) {
			//鼠标点击处理(绘图等等,这里只进行简单的计数)
			leftClickCount++;
		}

		xyprintf(20, 50, "鼠标左键点击次数:%3d", leftClickCount);
	}

	getch();

	closegraph();
	return 0;
}

(5) 鼠标消息检测的总结

while() 循环获取鼠标消息,循环退出后,会只保留最后一条鼠标消息,其它鼠标消息丢失。如果你想要检测每一条鼠标消息,请放在while()中。

判断哪些状态位需要每次进入帧绘制循环都要进行初始化,哪些不用

  • 对于那些只跟最新的消息有关的状态位就要初始化,如是否鼠标点击,是否移动
  • 对于跟以前的消息有关的状态位,就不需要,如,鼠标按键现在是弹起还是按下,这跟之前鼠标是否进行了按下弹起有关。并不是说这次循环鼠标没有按下的消息,鼠标就没有按下。鼠标产生一条按下的消息后,它按下的状态是一直保持的,直到鼠标松开。

检查鼠标是否点击,应该放在while()中检测。

  • 如果在检测到鼠标点击后要做复杂的操作,那么请在while()循环之前设立一个点击标志位并且状态重置,在while()循环中检测到后,使标志位有效。在while()循环外再由标志位判断出鼠标是否点击,然后再做其它处理。
  • 耗时,耗性能的操作不能放在while()循环,如果需要检测一些状态,可以设置标志位,在while()中进行检测,while() 循环退出后,再根据状态位进行处理。
  • 鼠标消息检测的大致结构如下:
mouse_msg msg = { 0 };

//设立标志位
bool flag;

for (; is_run(); delay_fps(60)) {
	//标志位状态重置
	 flag = false;

	while (mousemsg()) {
		msg = getmouse();
		
		//对消息进行检测,只进行简单操作
		if (鼠标进行了操作)
			flag = true;
	}

	//标志位判断并做处理
	if (flag) {
		//做绘图等操作
	}
}

鼠标消息的处理及状态的保存

  • 鼠标消息只是表示之前某一时刻鼠标的动作,却没有当前的鼠标状态信息,为此需要对消息进行处理,通过消息判断出 目前鼠标的状态 并保存。
  • 我们可以设置一个标志位一开始设置成鼠标按键弹起的状态, 当检测到鼠标点击的消息,就把它修改成按下的状态, 当检测到鼠标弹起的消息,就把它修改成弹起的状态
  • 需要注意的是,这种判断鼠标的状态需要处理每一条鼠标消息, 用 while()的方法获取鼠标消息, 退出循环后,只保留最后一条鼠标消息,所以可能会忽略掉其它的鼠标消息。这样有可能得到的状态是不正确的,因为如果被忽略的消息汇中有按下的消息,被忽略掉了,而你没检测,那你的状态记录就是错的。
  • 所以我们要检测每一条消息, 要把检测的部分放在while循环中
mouse_msg msg = { 0 };
bool leftDown = false;

for (; is_run(); delay_fps(60)) {
	while (mousemsg()) {
		msg = getmouse();

		//放在while循环中
		if (msg.is_left()) {
			if (msg.is_down())
				leftDown = true;
			else if (msg.is_up())
				leftDown = false;
		}
	}

	if (leftDown) {
		如果左键是按下状态
	}
}
  • 如果你要分别检测左中右三个键,那么就要设置三个状态标志位,为了代码简便,我们可以用大小为3的标志位数组来表示,这样在添加更多的标志位时可以只用一个循环就可以检测完毕,而不用一个一个地复制,修改名称。
  • 值得注意的是我们在if (key[i]) 的语句中的后面加了个break这是因为鼠标消息一次只能是来自一个按键,确定后就不需要检测剩余的按键了。
mouse_msg msg = { 0 };
//0, 1, 2分别表示鼠标左中右键
bool down[3] = { false };

for (; is_run(); delay_fps(60)) {

	while (mousemsg()) {
		msg = getmouse();

		//按键状态监测
		bool key[3] = { msg.is_left(), msg.is_mid(), msg.is_right() };
		for (int i = 0; i < 3; i++) {
			//判断来自哪个键
			if (key[i]) {
				//状态检测
				if (msg.is_down())
					down[i] = true;
				else if (msg.is_up())
					down[i] = false;

				break;
			}
		}	
	}

	if (down[0]) {
		//如果左键是按下状态;
	}
}

判断鼠标按键点击的代码

#include 

int main()
{
	initgraph(300, 300);
	setbkcolor(WHITE);
	setcolor(0xFF0000);

	mouse_msg msg = { 0 };
	const char* keyname[3] = { "左键", "中键","右键" };
	//0, 1, 2分别表示鼠标左中右键
	bool click[3] = { false };
	int clickCount[3] = { 0 };

	for (; is_run(); delay_fps(60)) {
		for (int i = 0; i < 3; i++)
			click[i] = false;
		
		//鼠标消息检测
		while (mousemsg()) {
			msg = getmouse();

			//按键状态监测
			bool key[3] = { msg.is_left(), msg.is_mid(), msg.is_right() };
			for (int i = 0; i < 3; i++) {
				//判断来自哪个键
				if (key[i]) {
					//状态检测
					if (msg.is_down())
						click[i] = true;
					
					//知道是来自一个键后,其它键就不用判断了
					break;
				}
			}
		}

		for (int i = 0; i < 3; i++) {
			if (click[i])
				clickCount[i]++;

			xyprintf(20, i * 30, "鼠标%s点击数:%3d", keyname[i], clickCount[i]);
		}
	}

	getch();

	closegraph();
	return 0;
}

(十五)EGE鼠标消息_第2张图片

鼠标状态类(MouseState)

  为了能更好地进行鼠标处理,我为此定义一个鼠标状态类 ,用以保存鼠标的按键信息,判断现在鼠标按键是按下还是松开状态。还可以检测鼠标单击,双击。因为鼠标的位置已经有 mousepos() 函数可以实时获取位置了,滚轮只在触发时有效,所以不用保存。
  鼠标状态类可以直接用,保存在工程中,以后可以用来进行鼠标消息处理
需要进行一下两个步骤:

  1. 新建一个 MouseState.h 头文件,把下面这部分复制粘贴,保存
#pragma once

#ifndef MOUSE_STATE_H_
#define MOUSE_STATE_H_

//鼠标按键状态
enum mouse_key_state {
	MOUSE_UP = 0,			//按键松开
	MOUSE_DOWN = 1,			//按键按下
};

//鼠标状态类
//可以保存鼠标按键状态,获取单击,双击事件
//需要在每一帧处理鼠标消息事件之前调用 resetState(), 进行按键状态重置
//需要对每一个鼠标消息使用 handleMouseMsg(mousemsg) 进行处理

class MouseState
{
private:
	//鼠标按键的状态(MOUSE_DOWN 或 MOUSE_UP)
	mouse_key_state curMouseState[3];

	int xClick[3], yClick[3];
	bool clickEvent[3];				//鼠标点击事件
	bool releaseEvent[3];			//鼠标释放事件

	bool isPressMsg[3];				//是鼠标点击消息
	bool isReleaseMsg[3];			//是鼠标释放消息 

	//鼠标双击(0, 1, 2分别是左中右)
	bool doubleClickEvent[3];		//鼠标双击事件
	double interval;			//双击触发的两次点击最大时间间隔
	double clickTimes[3];	//记录点击的时间,用于判断双击

public:
	MouseState();

	//设置双击触发的两次点击最大时间间隔(单位:秒)
	void setIntervalTime(double time_seconds);

	//获取双击时间间隔
	double getINtervalTime() { return interval; }

	//重置鼠标按键状态,必须在每一帧中,处理鼠标消息之前调用
	void resetState();

	//鼠标消息处理函数
	void handleMouseMsg(mouse_msg msg);

	//鼠标是否有单击事件产生
	bool hasLeftClick() { return clickEvent[0]; }
	bool hasMidClick() { return clickEvent[1]; }
	bool hasRightClick() { return clickEvent[2]; }

	//鼠标是否有按键松开事件产生
	bool hasLeftRelease() { return releaseEvent[0]; }
	bool hasMidRelease() { return releaseEvent[1]; }
	bool hasRightRelease() { return releaseEvent[2]; }

	//鼠标是否有双击事件产生
	bool hasLeftDoubleClick() { return doubleClickEvent[0]; }
	bool hasMidDoubleClick() { return doubleClickEvent[1]; }
	bool hasRightDoubleClick() { return doubleClickEvent[2]; }

	//是否是鼠标按键按下的消息,只针对一条鼠标消息
	bool isLeftPressMsg() { return isPressMsg[0]; }
	bool isMidPressMsg() { return isPressMsg[1]; }
	bool isRightPressMsg() { return isPressMsg[2]; }

	//是否是鼠标按键松开的消息,只针对一条鼠标消息
	bool isLeftReleaseMsg() { return isReleaseMsg[0]; }
	bool isMidtReleaseMsg() { return isReleaseMsg[1]; }
	bool isRightReleaseMsg() { return isReleaseMsg[2]; }

	//获取内部按键状态数组,方便循环检测
	const bool* getKeysPress() { return isPressMsg; }
	const bool* getKeysRelease() { return isReleaseMsg; }
	const bool* getKeysClick() { return clickEvent; }
	const bool* getKeysDoubleClick() { return doubleClickEvent; }
	const mouse_key_state* getKeysState() { return curMouseState; }

	//判断鼠标现在是否处于按下状态
	bool isLeftDown() { return curMouseState[0] == MOUSE_DOWN; }
	bool isMidDown() { return curMouseState[1] == MOUSE_DOWN; }
	bool isRightDown() { return curMouseState[2] == MOUSE_DOWN; }

	//获取鼠标单击点击位置,需要在鼠标单击事件发生后获取才有效
	void getLeftClickPos(int* x, int* y);
	void getMidClickPos(int* x, int* y);
	void getRightClickPos(int* x, int* y);

private:
	//检测鼠标双击
	void checkDoubleClick(int keyType);
};
#endif //! MOUSE_STATE_H_
  1. 新建一个 MouseState.cpp 源文件,把下面这部分代码复制粘贴,保存
#define SHOW_CONSOLE
#include 
#include "MouseState.h"

MouseState::MouseState()
{
	interval = 0.4f;
	
	for (int i = 0; i < 3; i++) {
		curMouseState[i] = MOUSE_UP;
		clickEvent[i] = false;
		releaseEvent[i] = false;

		doubleClickEvent[i] = false;
		
		xClick[i] = yClick[i] = 0;

		//初始赋一个较小的负值
		clickTimes[i] = -1000;

		isPressMsg[i] = isReleaseMsg[i] = false;
	}
}

//设置双击触发的两次点击最大时间间隔(单位:秒)
void MouseState::setIntervalTime(double time_seconds)
{
	interval = (time_seconds > 0) ? time_seconds : 0;
}

//重置鼠标按键状态,必须在每一帧中,处理鼠标消息之前调用
void MouseState::resetState()
{
	for (int i = 0; i < 3; i++) {

		clickEvent[i] = false;
		releaseEvent[i] = false;

		doubleClickEvent[i] = false;

		isPressMsg[i] = isReleaseMsg[i] = false;
	}
}


//鼠标消息处理函数
void MouseState::handleMouseMsg(mouse_msg msg)
{
	bool isKey[3] = { msg.is_left(), msg.is_mid(), msg.is_right()};
	
	for (int i = 0; i < 3; i++) {
		isPressMsg[i] = false;
		isReleaseMsg[i] = false;
	}
	//点击事件检查
	for (int i = 0; i < 3; i++) {
		if (isKey[i]) {
			//鼠标点击
			if (msg.is_down()) {
				isPressMsg[i] = true;
				isReleaseMsg[i] = false;
				curMouseState[i] = MOUSE_DOWN;
				clickEvent[i] = true;
				xClick[i] = msg.x;
				yClick[i] = msg.y;
			}
			//鼠标松开
			else if (msg.is_up()) {
				isPressMsg[i] = false;
				isReleaseMsg[i] = true;
				curMouseState[i] = MOUSE_UP;
			}

			checkDoubleClick(i);

			//鼠标消息只能属于一个键,其它键不用再检测
			break;
		}
	}
}

//检测鼠标双击事件
void MouseState::checkDoubleClick(int keyType) {
	double curTime = fclock();
	/*鼠标双击事件检查*/
	if (clickEvent[keyType]) {
		if (curTime - clickTimes[keyType] > interval) {
			clickTimes[keyType] = curTime;
		}
		else {
			doubleClickEvent[keyType] = true;
			clickTimes[keyType] = 0;
		}
	}							
}

//获取鼠标点击位置
void MouseState::getLeftClickPos(int* x, int* y)
{
	*x = xClick[0];
	*y = yClick[0];
}
void MouseState::getMidClickPos(int* x, int* y)
{
	*x = xClick[1];
	*y = yClick[1];
}
void MouseState::getRightClickPos(int* x, int* y)
{
	*x = xClick[2];
	*y = yClick[2];
}

使用流程

  1. 在帧循环外创建一个 MouseState 类对象, 一个mouse_msg 类对象
  2. 在鼠标消息处理前,调用 MouseState 对象的 resetState();
  3. 进行鼠标消息处理循环,对每一条鼠标消息,调用 handleMouseMsg( ) 进行处理
  4. 如果需要检测每一条鼠标消息类型的,在鼠标消息处理循环中进行。
  5. 鼠标单击双击事件,在鼠标消息处理循环外判断 hasXXXClick()
  6. 在用hasXXXClick() 判断由鼠标点击后,可以用 getXXXClickPos() 获取鼠标点击时的位置
MouseState mouseState;
mouse_msg mouseMsg;

for ( ; is_run(); delay_fps(60)) {
	mouseState.resetState();
	
	while (mousemsg()) {
		mouseMsg = getmouse();
		mouseState.handleMouseMsg(mouseMsg);
		
		//这里用来处理对每一条鼠标消息判断的
		//当前的鼠标消息是否是左键按下的消息
		if (mouseState.isLeftPressMsg()) {
		}
	}
	
	//用来处理鼠标点击事件,或者鼠标状态判断

	//如果有鼠标左键点击
	if (mouseState.hasLeftClick()) {
	}

	//如果左键是按下状态
	if (mouseState.isLeftDown()) {
	}
}

MouseState类使用示例

#include 
#include "MouseState.h"

int main()
{
	setinitmode(0, 100, 100);		//设置为普通窗口, 设置窗口位置
	setrendermode(RENDER_MANUAL);	//设置为手动模式
	initgraph(700, 300);			//初始化窗口
	setcaption("EGE鼠标消息获取");	//设置窗口标题

	setbkcolor(WHITE);
	setcolor(BLACK);
	setfont(18, 0, "黑体");


	MouseState mouseState;
	mouse_msg msg = { 0 };

	//设置双击需要两次点击时间间隔不超过多少秒才会触发
	mouseState.setIntervalTime(0.4f);

	//点击次数计数,0, 1, 2分别是左中右
	int clickCount[3] = { 0 }, doubleClickCount[3] = { 0 };

	//鼠标按下松开次数计数
	int pressCount[3] = { 0 }, releaseCount[3] = { 0 };

	//鼠标消息数计数
	int msgCount = 0;
	int xClickPos[3] = { 0 }, yClickPos[3] = { 0 };
	for (; is_run(); delay_fps(60))
	{
		//这个是mouseState正确处理鼠标点击消息的必要调用,在每一帧未处理鼠标消息之前调用。
		mouseState.resetState();

		//检测并处理鼠标消息
		while (mousemsg())
		{
			msg = getmouse();

			msgCount++;

			mouseState.handleMouseMsg(msg);

			//鼠标按键松开按下消息是对某一条个鼠标消息来说的,所以放在鼠标消息处理的循环中
			//获取状态数组,方便检测3个按键状态
			const bool* isPress = mouseState.getKeysPress();
			const bool* isRelease = mouseState.getKeysRelease();

			for (int i = 0; i < 3; i++) {
				if (isPress[i])
					pressCount[i]++;
				if (isRelease[i])
					releaseCount[i]++;
			}

		}

		//计算点击次数,点击事件 在 下次resetState()之前都有效,所以放在鼠标消息处理完成之后
		//在下次resetState之前都可以查询是否有点击事件产生
		//可以用 hasLeftClick(), hasMidClick(), hasRightClick() 来检测单击事件
		//为了方便循环输出,这里直接获取状态数组
		const bool* keysClick = mouseState.getKeysClick();
		const bool* keysDoubleClick = mouseState.getKeysDoubleClick();

		for (int i = 0; i < 3; i++) {
			if (keysClick[i]) {
				clickCount[i]++;
				switch (i) {
				case 0:	mouseState.getLeftClickPos(&xClickPos[0], &yClickPos[0]);  break;
				case 1:	mouseState.getMidClickPos(&xClickPos[1], &yClickPos[1]);   break;
				case 2:	mouseState.getRightClickPos(&xClickPos[2], &yClickPos[2]); break;
				}
			}

			if (keysDoubleClick[i])
				doubleClickCount[i]++;
		}

		//格式化输出为字符串,用于后面输出
		//msg和flag常数请参考文档或者mouse_msg_e, mouse_flag_e的声明

		int xMouse, yMouse;
		mousepos(&xMouse, &yMouse);		//获取鼠标实时位置

		cleardevice();
		xyprintf(0, 20, "鼠标位置:  x   = %4d     y = %4d",
			xMouse, yMouse);
		xyprintf(0, 40, "消息类型:move  = %d   down  = %d    up  = %d",
			(int)msg.is_move(),
			(int)msg.is_down(),
			(int)msg.is_up());
		xyprintf(0, 60, "按键:    left  = %d    mid  = %d  right = %d",
			(int)msg.is_left(),
			(int)msg.is_mid(),
			(int)msg.is_right());
		xyprintf(0, 80, "滚轮:    wheel = %d        wheel rotate = %d",
			(int)msg.is_wheel(),
			msg.wheel);
		xyprintf(0, 100, "辅助键:   shift = %d    ctrl = %d",
			msg.flags == mouse_flag_shift,
			msg.flags == mouse_flag_ctrl);

		const char* keyNames[3] = { "左", "中", "右" };
		const mouse_key_state* keysState = mouseState.getKeysState();

		for (int i = 0; i < 3; i++) {
			xyprintf(0, 120 + i * 20, "鼠标%s键状态: %s, 按下次数:%3d, 松开次数:%3d, 单击次数:%3d, 双击次数:%3d",
				keyNames[i],
				(keysState[i] == MOUSE_DOWN) ? "按下" : "松开",
				pressCount[i], releaseCount[i],
				clickCount[i], doubleClickCount[i]);
		}

		xyprintf(0, 180, "鼠标消息数总计:%d", msgCount);
		xyprintf(0, 220, "点击位置:左[%d, %d], 中[%d, %d], 右[%d, %d]", 
			xClickPos[0], yClickPos[0], xClickPos[1], yClickPos[1], xClickPos[2], yClickPos[2]);
	}

	getch();

	closegraph();

	return 0;
}

(十五)EGE鼠标消息_第3张图片

设置鼠标是否显示

  • 有时有不显示鼠标的需要,比如看视频,这时候就不能让鼠标出现了。
  • 函数声明如下:
int showmouse(int bShow);

bShow为布尔类型,1或0, 1为显示, 0为不显示,可以用true 和 false代替

  • 不显示鼠标
showmouse(false);
  • 显示鼠标
showmouse(true);

清空鼠标消息缓存区

当你觉得缓存区中的鼠标消息已经无用时,可以把缓存区中的鼠标消息清空。

flushmouse();
  • 使用后,鼠标消息缓存区中的鼠标消息将会被清空。
    例如,前面红色小圆跟随鼠标移动的示例,因为帧率不够,使用if 处理鼠标消息时会出现红色小圆滞后的现象, 那么这时可以使用flushmouse(), 将剩余的鼠标消息清空,这样就不用处理滞后的消息,小球就能跟上鼠标了。

鼠标操作实例

格子点击

  假设有个宽高数为N * M 的格子方块,每个格子宽高分别为 GRID_WIDTH, GRID_HEIGHT
如果方格区域左上角为 (BLOCK_LEFT, BLOCK_TOP),知道方格区域宽高后,可以直接根据鼠标位置算出鼠标点击的格子坐标,而不用一个一个格子地判断鼠标点击位置是否在范围内

  • 格子之间无间隔
    如果格子之间无间隔,鼠标点击位置为 (xClick, yClick),这里假设方块区域左上角为 (0,0),那么计算点击的格子公式为:
int xGrid = xClick / GRID_WIDTH;
int yGrid = yClick / GRID_HEIGHT;

这里(xGrid, yGrid)就是格子的坐标
左上角的格子是(0, 0), 右下角的格子是(N - 1, M - 1)。

  • 格子之间有间隔
    如果格子之间有间隔,假设间隔宽度为DEVIDE_WIDTH, 高度为DEVIDE_HEIGHT
    那么把格子和间隔先算作一个大格子,计算点击在哪个大格子,然后在计算是否在实际格子中。
int xGrid = xClick / (GRID_WIDTH + DIVIDE_WIDTH);
int yGird = yClick / (GRID_HEIGHT + DIVIDE_HEIGHT);

//计算点击坐标在对应大格子中的位置
int x = xClick - xGrid * (GRID_WIDTH + DIVIDE_WIDTH);
int y = yClick - yGrid * (GRID_HEIGHT + DIVIDE_HEIGHT);

然后根据x, y 来判断是否点击到实际格子

下面是示例程序:通过点击,使方格颜色翻转。

#include 

//方块参数
const int N = 4, M = 8;
const int GRID_WIDTH = 150, GRID_HEIGHT = 75;
const int BLOCK_LEFT = 10, BLOCK_TOP = 60;
const int BLOCK_WIDTH = N * GRID_WIDTH, BLOCK_HEIGHT = M * GRID_HEIGHT;

//窗口大小
const int SCR_WIDTH = BLOCK_LEFT * 2 + BLOCK_WIDTH;
const int SCR_HEIGHT = BLOCK_TOP + BLOCK_HEIGHT + 10;

//颜色定义
const color_t DARK_GRAY  = EGERGB(0x33, 0x33, 0x33);
const color_t LIGHT_GRAY = EGERGB(0xaa, 0xaa, 0xaa);
const color_t BG_COL = WHITE;

int grid[M][N] = { 0 };

void checkBlockClick(int x, int y, int* xGrid, int* yGrid);	//方块点击检测
void drawAllGrid();							//绘制所有格子
void drawGrid(int xGrid, int yGrid);		//绘制格子
void flipGrid(int xGrid, int yGrid);		//翻转格子

int main()
{
	initgraph(SCR_WIDTH, SCR_HEIGHT, INIT_RENDERMANUAL);
	setbkcolor(BG_COL);
	setcaption("点击方格翻转");
	setfont(28, 0, "楷体");
	setbkmode(TRANSPARENT);

	drawAllGrid();

	bool click_flag;
	int xClick, yClick;
	
	for (; is_run(); delay_fps(60)) {
		//点击标志位清零
		click_flag = false;

		//处理鼠标消息
		while (mousemsg()) {
			mouse_msg msg = getmouse();

			//左键按下
			if (msg.is_left() && msg.is_down()) {
				//标志位置位,记录点击位置
				click_flag = true;
				xClick = msg.x;
				yClick = msg.y;
			}
		}

		//检测到点击
		if (click_flag) {
			//信息输出区域清屏
			setfillcolor(BG_COL);
			bar(0, 0, SCR_WIDTH, BLOCK_TOP);

			//输出鼠标点击坐标
			setcolor(BLACK);
			xyprintf(0, 0, "鼠标点击:[%d, %d]", xClick, yClick);

			//计算鼠标点击格子坐标
			int xGrid, yGrid;
			checkBlockClick(xClick, yClick, &xGrid, &yGrid);

			//点击在格子区域
			if (xGrid != -1 || yGrid != -1) {
				setcolor(BLACK);
				xyprintf(0, 30, "翻转格子:(%d, %d)\n", xGrid, yGrid);

				//翻转对应格子
				flipGrid(xGrid, yGrid);
			}
		}
	}

	closegraph();
	return 0;
}

//绘制格子
void drawGrid(int xGrid, int yGrid)
{	
	//计算格子所在区域
	int left = BLOCK_LEFT + xGrid * GRID_WIDTH, top = BLOCK_TOP + yGrid * GRID_HEIGHT;
	int right = left + GRID_WIDTH - 1, bottom = top + GRID_HEIGHT - 1;

	setfillcolor((grid[yGrid][xGrid] == 0) ? DARK_GRAY : LIGHT_GRAY);
	bar(left, top, right, bottom);


	//绘制间隔线
	setcolor(WHITE);
	if (xGrid != N - 1)
		line(right, top, right, bottom);
	if (yGrid != M - 1)
		line(left, bottom, right, bottom);
}

//翻转格子
void flipGrid(int xGrid, int yGrid)
{
	if (0 <= xGrid && xGrid < N && 0 <= yGrid && yGrid < M) {
		grid[yGrid][xGrid] = !grid[yGrid][xGrid];
		drawGrid(xGrid, yGrid);
	}
}

//绘制所有格子
void drawAllGrid()
{
	for (int i = 0; i < M; i++) {
		for (int j = 0; j < N; j++) {
			drawGrid(j, i);
		}
	}
}

void checkBlockClick(int x, int y, int* xGrid, int* yGrid)
{
	//判断点击位置是否在方块区域
	if (BLOCK_LEFT <= x && x < BLOCK_LEFT + BLOCK_WIDTH
		&& BLOCK_TOP <= y && y < BLOCK_TOP + BLOCK_HEIGHT) {
		//计算相对于方块区域左上角的坐标,方便计算
		x -= BLOCK_LEFT;
		y -= BLOCK_TOP;

		//计算出点击的格子
		*xGrid = x / GRID_WIDTH;
		*yGrid = y / GRID_HEIGHT;
	}
	else {
		*xGrid = *yGrid = -1;
	}
}

(十五)EGE鼠标消息_第4张图片

鼠标按住拖动

  • 按住拖动即鼠标按下后选中物体,然后按住不放移动鼠标,改变物体参数,鼠标按键松开后确定修改
    比如,电脑的音量调节滑动条。
  • 和下面的鼠标选择拖动操作的区别是,确认修改的方式,一个是在按键松开时,一个是在再次点击时,只要在对应时刻将动作标志位复位即可,即主要区别在于动作标志位复位代码放置的位置不同
  • 主要是根据基点和鼠标当前位置确定改变的参数。
  • 基点多是鼠标点击的位置,可以在移动后改变基点为当前位置,物体参数修改用增量的方式,物体参数 += (由当前位置与基点决定的偏移量)
  • 也可以记录物体初始参数,移动后不改变基点物体参数 = 初始参数 + (由当前位置与基点决定的偏移量)
#include 
#include 

struct Circle
{
	int x, y;
	double radius;
	int edge;
};

void draw(const Circle& c)
{
	setfillcolor(EGEARGB(0xFF, 0xa0, 0x00, 0xa0));
	ege_fillellipse(c.x - c.radius - c.edge, c.y - c.radius - c.edge, 2 * (c.radius + c.edge), 2 * (c.radius  + c.edge));
	setfillcolor(EGEARGB(0xFF, 0xFF, 0x33, 0xFF));
	ege_fillellipse(c.x - c.radius, c.y - c.radius, c.radius * 2, c.radius * 2);
}

int main()
{
	initgraph(640, 480, INIT_RENDERMANUAL);
	setbkcolor(WHITE);
	delay_ms(0);
	setcolor(BLACK);
	setfont(20, 0, "楷体");
	setbkmode(TRANSPARENT);
	//开启抗锯齿
	ege_enable_aa(true);

	const int N = 3;
	Circle circles[N] = {
		{100, 240, 80.0, 10 },
		{300, 240, 80.0, 10},
		{500, 240, 80.0, 10}
	};
	int xBase, yBase;				//记录鼠标左键点击的位置
	int checkid = -1;
	bool move_flag = false, zoom_flag = false;
	bool redraw = true;

	for (; is_run(); delay_fps(60)) {	
		while (mousemsg()) {
			mouse_msg msg = getmouse();

			//鼠标左键点击位置记录			
			if (msg.is_move() ) {
				if (checkid != -1) {
					//移动
					if (move_flag) {
						//根据基点和当前位置移动物体
						circles[checkid].x += msg.x - xBase;
						circles[checkid].y += msg.y - yBase;
					}
					//缩放
					else {
						//根据基点和当前位置分别到圆心的距离,计算缩放的大小
						int x1 = msg.x - circles[checkid].x, y1 = msg.y - circles[checkid].y;
						int x2 = xBase - circles[checkid].x, y2 = yBase - circles[checkid].y;
						double dr = sqrt(x1 * x1 + y1 * y1) - sqrt(x2 * x2 + y2 * y2);
						circles[checkid].radius += dr;
						//缩放限制
						if (circles[checkid].radius < circles[checkid].edge)
							circles[checkid].radius = circles[checkid].edge;
					}

					//操作后,改变基点
					xBase = msg.x;
					yBase = msg.y;
					redraw = true;
				}
			}
			else if (msg.is_left()) {
				if (msg.is_down()) {
					//设置基点为点击位置
					xBase = msg.x;
					yBase = msg.y;

					//对每个物体进行检测,
					for (int i = 0; i < N; i++) {
						int dist = (xBase - circles[i].x) * (xBase - circles[i].x) + (yBase - circles[i].y) * (yBase - circles[i].y);

						//移动选中(内圆)
						if (dist < circles[i].radius * circles[i].radius) {
							move_flag = true;
							checkid = i;
							break;
						}
						//缩放选中(外环)
						else if (dist < (circles[i].radius + circles[i].edge) * (circles[i].radius + circles[i].edge)) {
							zoom_flag = true;
							checkid = i;
							break;
						}
					}
				}
				else {
					//动作标志位复位
					checkid = -1;
					move_flag = zoom_flag = false;
				}
			}
		}

		//空格键重置
		while (kbmsg()) {
			key_msg msg = getkey();
			if (msg.key == key_space && msg.msg == key_msg_down) {
				for (int i = 0; i < N; i++) {
					circles[i].x = 100 + i * 200;
					circles[i].y = 240;
					circles[i].radius = 80.0;
				}
				zoom_flag = move_flag = false;
				redraw = true;
			}
		}

		//重绘
		if (redraw) {
			redraw = false;
			cleardevice();
			for (int i = N -1 ; i >= 0; i--)
				draw(circles[i]);
			setcolor(BLACK);
			outtextxy(300, 0, "拖动内圆移动,拖动外环调整大小");
			outtextxy(300, 22, "按空格键重置位置及大小");
		}
	}

	closegraph();

	return 0;
}

(十五)EGE鼠标消息_第5张图片

鼠标选择拖动

选择拖动是首次点击选中物体,然后鼠标移动改变物体参数,再次点击则确定修改

  • 这种主要是有个选中的标志位,下面则为checkid, 为-1代表未选中,不为-1代表选中。当检测到点击消息时,检测是否选中,点击选中后,根据点击的位置确定动作。如内部则拖动,边框则调整大小。
  • 当检测到移动消息时,如果有选中物体,则根据动作改变物体参数。
  • 添加后重绘标志位 redraw, 避免无操作时的重复绘制。

#include 
#include 

struct Circle
{
	int x, y;
	double radius;
	int edge;
};

void draw(const Circle& c)
{
	setfillcolor(EGEARGB(0xFF, 0xa0, 0x00, 0xa0));
	ege_fillellipse(c.x - c.radius - c.edge, c.y - c.radius - c.edge, 2 * (c.radius + c.edge), 2 * (c.radius  + c.edge));
	setfillcolor(EGEARGB(0xFF, 0xFF, 0x33, 0xFF));
	ege_fillellipse(c.x - c.radius, c.y - c.radius, c.radius * 2, c.radius * 2);
}

int main()
{
	initgraph(640, 480, INIT_RENDERMANUAL);
	setbkcolor(WHITE);
	delay_ms(0);
	setcolor(BLACK);
	setfont(20, 0, "楷体");
	setbkmode(TRANSPARENT);
	//开启抗锯齿
	ege_enable_aa(true);

	const int N = 3;
	Circle circles[N] = {
		{100, 240, 80.0, 10 },
		{300, 240, 80.0, 10},
		{500, 240, 80.0, 10}
	};
	int xBase, yBase;				//记录鼠标左键点击的位置
	int checkid = -1;
	bool move_flag = false, zoom_flag = false;
	bool redraw = true;

	for (; is_run(); delay_fps(60)) {	
		while (mousemsg()) {
			mouse_msg msg = getmouse();

			//鼠标左键点击位置记录			
			if (msg.is_move() ) {
				if (checkid != -1) {
					//移动
					if (move_flag) {
						//根据基点和当前位置移动物体
						circles[checkid].x += msg.x - xBase;
						circles[checkid].y += msg.y - yBase;
					}
					//缩放
					else {
						//根据基点和当前位置分别到圆心的距离,计算缩放的大小
						int x1 = msg.x - circles[checkid].x, y1 = msg.y - circles[checkid].y;
						int x2 = xBase - circles[checkid].x, y2 = yBase - circles[checkid].y;
						double dr = sqrt(x1 * x1 + y1 * y1) - sqrt(x2 * x2 + y2 * y2);
						circles[checkid].radius += dr;
						//缩放限制
						if (circles[checkid].radius < circles[checkid].edge)
							circles[checkid].radius = circles[checkid].edge;
					}

					//操作后,改变基点
					xBase = msg.x;
					yBase = msg.y;
					redraw = true;
				}
			}
			else if (msg.is_left()) {
				if (msg.is_down()) {
					if (checkid == -1) {
						//设置基点为点击位置
						xBase = msg.x;
						yBase = msg.y;

						//对每个物体进行检测,
						for (int i = 0; i < N; i++) {
							int dist = (xBase - circles[i].x) * (xBase - circles[i].x) + (yBase - circles[i].y) * (yBase - circles[i].y);

							//移动选中(内圆)
							if (dist < circles[i].radius * circles[i].radius) {
								move_flag = true;
								checkid = i;
								break;
							}
							//缩放选中(外环)
							else if (dist < (circles[i].radius + circles[i].edge) * (circles[i].radius + circles[i].edge)) {
								zoom_flag = true;
								checkid = i;
								break;
							}
						}
					}
					else {
						//动作标志位复位
						checkid = -1;
						move_flag = zoom_flag = false;
					}
				}
			}
		}

		//空格键重置
		while (kbmsg()) {
			key_msg msg = getkey();
			if (msg.key == key_space && msg.msg == key_msg_down) {
				for (int i = 0; i < N; i++) {
					circles[i].x = 100 + i * 200;
					circles[i].y = 240;
					circles[i].radius = 80.0;
				}
				zoom_flag = move_flag = false;
				redraw = true;
			}
		}

		//重绘
		if (redraw) {
			redraw = false;
			cleardevice();
			for (int i = N -1 ; i >= 0; i--)
				draw(circles[i]);
			setcolor(BLACK);
			outtextxy(300, 0, "点击内圆移动,点击外环改变大小");
			outtextxy(300, 22, "再次点击左键确认");
			outtextxy(300, 44, "按空格键重置位置及大小");
		}
	}

	closegraph();

	return 0;
}

(十五)EGE鼠标消息_第6张图片

你可能感兴趣的:(EGE)