EGE基础:鼠标消息篇

48 篇文章 891 订阅
订阅专栏
14 篇文章 1 订阅
订阅专栏

EGE专栏:EGE专栏

目录

  • 一、鼠标
    • 1. 鼠标按键
    • 2. 鼠标动作产生鼠标消息
  • 二、鼠标消息
    • 1. 鼠标消息所包含的信息
    • 2. 鼠标消息的读取
    • 3. 鼠标操作的判断
      • 3.1 鼠标消息结构体 mouse_msg
        • 从鼠标消息中提取信息
      • 3.2 鼠标按键消息的判断
        • 示例:根据鼠标按键消息识别鼠标按键状态
      • 3.3 鼠标移动消息的判断
        • 示例:绘制跟随鼠标的圆
      • 3.4 鼠标滚轮消息的判断
        • 示例:滚轮滚动
  • 三、鼠标事件
    • 1. 鼠标消息处理循环
      • 1.1 处理不及时所带来的消息滞后
      • 1.2 消息处理循环中如何对消息进行处理
        • 1.2.1 不应在消息处理循环外进行检测
        • 1.2.2 在消息处理循环内对每一条消息进行检测
    • 2. 获取鼠标指针位置
      • 2.1 通过鼠标消息获取鼠标指针位置
      • 2.2 win API 获取鼠标指针实时位置
      • 2.3 鼠标指针初始位置问题
      • 2.4 mousepos() 函数
    • 3. 鼠标的点击判断
      • 3.1 鼠标按下时触发点击
        • 示例:鼠标按键按下时绘制圆
      • 3.2 鼠标按键抬起时触发点击
        • 示例:鼠标点击按钮
    • 4. 鼠标按键状态
      • 4.1 读取鼠标消息记录按键状态
      • 4.2 keystate()函数
      • 示例:根据鼠标按键消息识别鼠标按键状态
    • 5. 鼠标拖拽

一、鼠标

1. 鼠标按键

  鼠标一般都有左键右键滚轮,通常滚轮除了可以前后滚动之外,还可以被作为按键使用,称为 中键,不过中键很少使用,远不如左键和右键使用频繁。鼠标上的DPI键用于调节鼠标的灵敏度,对应到系统上可以改变指针的移动速度。有些鼠标还会有两个 侧键,一般是作为浏览网页、应用时的前进和后退使用。

  有些鼠标可以进行宏编程,改变鼠标按键按下时电脑接收到的动作信息,从而改变按键功能。

在这里插入图片描述

2. 鼠标动作产生鼠标消息

  鼠标移动、点击,滚轮滚动等动作都会产生鼠标消息,由鼠标内部MCU向电脑发送数据,发送数据的频率和鼠标的回报率有关,普通鼠标一般是每秒向电脑发送100多次数据,回报率越高,延迟越低。而信息发送给系统,经过处理后,系统再将消息转发到对应的窗口,交由窗口对应的程序进行处理。
  那么在EGE中,操作鼠标时窗口都会接收到哪些消息呢?

在线鼠标按键测试

  

鼠标动作涉及按键发送消息数描述
按键按下左键、中键和右键1按键按下只会发送一条鼠标消息,长按也不会再次发送
按键抬起左键、中键和右键1按键抬起时会发送一条鼠标消息
鼠标移动N发送速度一般为每秒百条以上,鼠标芯片轮询,检测到鼠标移动足够距离会发送一条鼠标移动消息,具体精度由DPI和系统设置
滚轮前滚滚轮1滚轮每滚动一小格发送一条消息
滚轮后滚滚轮1滚轮每滚动一小格发送一条消息

二、鼠标消息

1. 鼠标消息所包含的信息

  在EGE中,鼠标消息被统一封装到 mouse_msg 结构体中。我们可以通过结构体中成员值来区分出是消息的类型:是属于按键按下抬起、鼠标移动还是滚轮滚动消息。结构体中还包含触发此次消息的按键,鼠标的位置信息等。同时,还会附加一个信息:触发鼠标消息时,相应的辅助键是否被按下。辅助键和鼠标状态组合,可以产生更多的操作。

在这里插入图片描述

2. 鼠标消息的读取

  系统发送给窗口的鼠标消息,会被EGE处理后保存到自己的消息队列中,用户可以使用 getmouse() 函数从消息队列中读取鼠标消息。鼠标消息的类型为 mouse_msg

  需要注意的是,如果队列中没有鼠标消息,那么getmouse() 函数会一直等待直到获取到鼠标消息再返回。

在这里插入图片描述

  当消息队列为空时, getmouse() 会一直等待,直到有鼠标消息产生,这就有一个很大的问题,如果用户没有动鼠标,那么程序就会一直在 getmouse() 里面循环,无法执行后面的代码。
  所以 应该在读取鼠标消息之前,先用 mousemsg() 函数判断消息队列里有没有鼠标消息,如果没有,就直接跳过读取环节,进行其它的操作。如果消息队列里有鼠标鼠标消息,则说明用户使用鼠标进行了操作,这时就可以将鼠标消息队列中的消息一一取出进行处理。

//当消息队列中有鼠标时,读取鼠标消息并进行处理
while (mousemsg()) {
	//使用 getmouse()读取鼠标消息
	mouse_msg mouseMsg = getmouse();

	//这里对读取到的鼠标消息进行处理
}

  注意了,这里使用了while 循环,而不是使用 if ,这是因为鼠标消息产生的速度是很快的。如果使用if ,那么每次只能处理一条消息,比鼠标消息产生的速度慢上不少,鼠标1秒内产生的消息,程序要两三秒才能处理完,用户会感觉得到明显的鼠标操作延迟。并且这样会导致鼠标消息堆积,队列满后新产生的消息因无法存储而丢失。

  读取到鼠标消息后,就可以从消息中判断出用户进行的鼠标操作,程序就可以对这些操作进行响应。

3. 鼠标操作的判断

  getmouse() 返回的鼠标消息被保存在 mouse_msg 结构体中,现在就要从 mouse_msg 得到鼠标操作的相关信息。

3.1 鼠标消息结构体 mouse_msg

  mouse_msg 定义在 <ege.h> 头文件中,

//鼠标消息结构体
typedef struct mouse_msg {
	int             x;		// 鼠标指针在窗口中的位置x
	int             y;		// 鼠标指针在窗口中的位置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;

  以下是一些成员用到的枚举

// 按键对应位
typedef enum mouse_flag_e {
	mouse_flag_left     = 0x01,
	mouse_flag_right    = 0x02,
	mouse_flag_mid      = 0x04,
	mouse_flag_shift    = 0x100,
	mouse_flag_ctrl     = 0x200,
}mouse_flag_e;

// 消息类型枚举
typedef enum mouse_msg_e {
	mouse_msg_down      = 0x10,
	mouse_msg_up        = 0x20,
	mouse_msg_move      = 0x40,
	mouse_msg_wheel     = 0x80,
}mouse_msg_e;

  mouse_msg 结构体中保存的内容如下:

成员变量保存的内容
说明
x, y鼠标指针位置鼠标指针在窗口坐标系中的坐标
msg消息类型类型有按键按下、按键抬起、鼠标移动和滚轮滚动四种
flags触发消息的鼠标按键以及辅助键状态消息最多只能由鼠标左键、中键和右键三键中的其中一个触发,或者没有触发按键。同时保存产生消息时辅助键有没有按下
wheel滚轮滚动值当类型是滚轮消息时,可以通过此值来判断鼠标滚动方向,前滚为正,后滚为负

  为了方便使用,结构体增加了成员函数,用于识别消息类型和触发按键。当获取到鼠标消息后,就可以调用成员函数对消息进行判断,同时获取鼠标指针位置等信息,辅助键状态等信息。
  下面是判别是否是鼠标左键抬起的消息,

mouse_msg msg = getmouse();

// 鼠标左键抬起
if (msg.is_left() && msg.is_up()) {
	
	//获取鼠标位置
	int x = msg.x;
	int y = msg.y;
	
	//检查辅助键是否按下
	bool shiftKeyIsDown = (msg.flags & mouse_flag_shift) != 0;
	bool ctrlKeyIsDown  = (msg.flags & mouse_flag_ctrl ) != 0;
}
从鼠标消息中提取信息

  下面的例程是不断从队列中读取鼠标消息,并从记录的最后一条鼠标消息中提取出相关信息显示在窗口上.

在这里插入图片描述

#include <graphics.h>

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

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

	//记录读取到的最后一条鼠标消息
	mouse_msg msgRecord = { 0 };
	bool redraw = true;
	int leftMargin = 80;
	for (; is_run(); delay_fps(60))
	{
		//检查是否有鼠标消息,没有则跳过(避免程序阻塞)
		//有则处理每一条鼠标消息
		while (mousemsg())
		{
			//getmouse 获取鼠标消息
			msgRecord = getmouse();
			redraw = true;
		}

		//msg和flag常数请参考文档或者mouse_msg_e, mouse_flag_e的声明
		if (redraw) {
			redraw = false;
			cleardevice();
			xyprintf(leftMargin, 20, "鼠标位置:  x   = %4d     y = %4d",
				msgRecord.x, msgRecord.y);
			xyprintf(leftMargin, 40, "消息类型:move  = %d   down  = %d    up  = %d  wheel = %d",
				msgRecord.is_move(),
				msgRecord.is_down(),
				msgRecord.is_up(),
				msgRecord.is_wheel());
			xyprintf(leftMargin, 60, "按键:    left  = %d    mid  = %d  right = %d",
				msgRecord.is_left(),
				msgRecord.is_mid(),
				msgRecord.is_right());
			xyprintf(leftMargin, 80, "滚轮值:  wheel rotate = %d",
				msgRecord.wheel);
			xyprintf(leftMargin, 100, "辅助键:   shift = %d    ctrl = %d",
				(msgRecord.flags & mouse_flag_shift) != 0,
				(msgRecord.flags & mouse_flag_ctrl) != 0);
		}
	}

	closegraph();

	return 0;
}

3.2 鼠标按键消息的判断

  鼠标的按键消息一共有按键按下按键抬起两种类型的消息。
在这里插入图片描述
  对于一个mouse_msg类型的鼠标消息 msg,如果是鼠标按键消息,那么 is_down()和 is_up() 这两个判断按键是按下还是抬起的函数会有其中一个返回 true,所以可以使用或来判断是否是按键消息。

//是否是按键消息
if (msg.is_down() || msg.is_up()) {
}

  同时,还可以使用 is_left() is_mid()is_rigth() 函数确认具体触发按键消息的按键,和前面判断按键是按下还是抬起的函数组合起来就能识别出是哪个按键进行的操作。
  例如: msg.is_down() && msg.is_left()判断的是鼠标左键按下的动作。

while (mousemsg()) {
	mouse_msg msg = getmouse();
	if (msg.is_left() && msg.is_down())
		// 鼠标左键按下
	}
}
鼠标动作is_left()is_mid()is_right()
is_down()左键按下中键按下右键按下
is_up()左键抬起中键抬起右键抬起
示例:根据鼠标按键消息识别鼠标按键状态

  通过读取鼠标消息,得到鼠标按键状态,然后根据鼠标按键状态在上方或在下方绘制圆。

在这里插入图片描述

#include <graphics.h>

#define MOUSE_KEY_NUM    3

// 枚举:鼠标按键索引
typedef enum KeyIndex
{
	KeyIndex_LEFT  = 0,  // 鼠标左键
	KeyIndex_MID   = 1,  // 鼠标中键
	KeyIndex_RIGHT = 2,  // 
} KeyIndex;

// 枚举:鼠标按键状态
typedef enum KeyState
{
	KeyState_UP   = 0,
	KeyState_DOWN = 1,
} KeyState;

int main()
{
	const int winWidth = 640, winHeight = winWidth * 3 / 4;

	initgraph(winWidth, winHeight, INIT_RENDERMANUAL);
	ege_enable_aa(true);
	setbkcolor(WHITE);

	bool redraw = true;
	int leftMargin = 80;

	//颜色表
	color_t colorTable[MOUSE_KEY_NUM] = { EGERGB(0x30, 0xC9, 0XF7), EGERGB(0xFC, 0xF3, 0x84), EGERGB(0xA8, 0xE3, 0xA9), };

	//记录按键状态
	KeyState mouseKeyState[MOUSE_KEY_NUM];

	for (int i = 0; i < MOUSE_KEY_NUM; i++) {
		mouseKeyState[i] = KeyState_UP;
	}

	setlinewidth(2);
	setcolor(BLACK);

	for (; is_run(); delay_fps(60)) {
	
		// 处理所有鼠标消息
		while (mousemsg()) {
			mouse_msg msg = getmouse();

			// 当鼠标消息是按键消息时进行处理
			if (msg.is_down() || msg.is_up()) {
				//确定是按键按下还是按键抬起
				KeyState keyState = msg.is_down() ? KeyState_DOWN : KeyState_UP;

				//根据触发的按键,改变对应对应按键的状态
				if (msg.is_left())
					mouseKeyState[KeyIndex_LEFT] = keyState;
				else if (msg.is_mid())
					mouseKeyState[KeyIndex_MID] = keyState;
				else
					mouseKeyState[KeyIndex_RIGHT] = keyState;
				redraw = true;
			}
		}

		// 绘图
		if (redraw) {
			cleardevice();

			for (int i = 0; i < MOUSE_KEY_NUM; i++) {
				//计算圆心位置及半径
				const float radius = winHeight * 2 / 11;
				const int cx = (3 + i * 5) * winWidth / 16;
				const int cy = (mouseKeyState[i] == KeyState_UP) ? (3 * winHeight / 11) : (8 * winHeight / 11);

				setfillcolor(colorTable[i]);
				ege_fillellipse(cx - radius, cy - radius, 2 * radius, 2 * radius);
				ege_ellipse(cx - radius, cy - radius, 2 * radius, 2 * radius);
			}
		}
	}

	closegraph();

	return 0;
}

3.3 鼠标移动消息的判断

  鼠标消息的类型如果是移动消息,那么成员函数 is_move() 将返回 true,由此可以辨别出鼠标移动消息并进行处理。

  鼠标的移动消息产生的速度比鼠标的其它消息快得多,移动时速度一般会达到每秒百条以上,所以可以判断是否是移动消息。

mouse_msg msg = getmouse();

if (msg.is_move()) {
	//鼠标移动消息
}

  利用 is_move() 函数判断是否是鼠标移动消息,主要是在处理鼠标拖拽(鼠标按键按住不放,然后移动鼠标)操作的时候使用。

  ① 鼠标按键按下时记录按下时的位置。
  ② 检测到鼠标移动消息时,如果鼠标按键处于按下状态,那么此时就是在进行鼠标拖拽操作,可以根据鼠标指针在按键按下时的位置和当前位置做相应的操作。

  每个鼠标消息产生时都会带有鼠标指针当前的位置,如果仅仅是想获取鼠标指针的位置,并不需要判断消息类型是否是移动消息,直接读取即可,如下所示:

int mousePosX = -1, mousePosY = -1;

while (mousemsg()) {
	mouse_msg msg = getmouse();
	
	//记录鼠标位置
	mousePosX = msg.x;
	mousePosY = msg.y;
}
示例:绘制跟随鼠标的圆

  示例为处理鼠标消息时记录下最后一次产生消息时的鼠标位置,然后在这个位置上绘制一个圆。

  由于在EGE20.08及之前的版本中,如果鼠标没有产生消息,那么获取的鼠标初始位置错误,因此可以使用 win API 中的 GetCursorPos() 和 ScreenToClient() 函数获取初始鼠标在窗口中的位置。

在这里插入图片描述

#include <graphics.h>
#include <Windows.h>

int main()
{
	timeBeginPeriod(1);
	const int winWidth = 640, winHeight = 480;
	initgraph(winWidth, winHeight, INIT_RENDERMANUAL);
	ege_enable_aa(true);
	setbkcolor(WHITE);

	POINT mousePos;

	//获取鼠标初始位置
	GetCursorPos(&mousePos);
	ScreenToClient(getHWnd(), &mousePos);

	setlinewidth(2);
	setcolor(BLACK);
	setfillcolor( EGERGB(0x30, 0xC9, 0XF7));
	const int radius = 64;
	bool redraw = true;

	for (; is_run(); delay_jfps(120)) {
		while (mousemsg()) {
			mouse_msg msg = getmouse();
			// 读取鼠标指针位置
			mousePos.x = msg.x;
			mousePos.y = msg.y;

			redraw = true;
		}

		if (redraw) {
			redraw = false;

			cleardevice();
			ege_fillellipse(mousePos.x - radius, mousePos.y - radius, 2 * radius, 2 * radius);
			ege_ellipse(mousePos.x - radius, mousePos.y - radius, 2 * radius, 2 * radius);
		}
	}

	timeEndPeriod(1);
	closegraph();

	return 0;
}

3.4 鼠标滚轮消息的判断

  滚动滚轮时,可以感觉得到滚轮是一格一格地转动的,并非平滑连续。滚轮每转动一小格产生一次脉冲信号,触发一次滚轮消息。
在这里插入图片描述
  滚轮消息可以使用成员函数 is_wheel() 判断,如果是滚轮消息,可以通过成员 wheel 的值(一般是120的倍数或约数)来判断滚轮滚动的方向,如果是值大于0,鼠标向前滚动,值小于0则是鼠标向后滚动。

while (mousemsg()) {
	mouse_msg msg = getmouse();
	
	//鼠标滚轮消息处理
	if (msg.is_wheel()) {
		if (msg.wheel > 0) {
			// 滚轮前滚
		}else {
			// 滚轮后滚
		}
	}
}
示例:滚轮滚动

  在示例中,程序会读取鼠标的滚轮消息,并根据滚轮滚动方向不断调节目标位置,之后小球会绕着圈旋转至目标位置。
在这里插入图片描述

#include <graphics.h>
#include <math.h>

struct Point
{
	float x;
	float y;
};

struct Transition
{
	double cur;
	double end;
};

struct TrackCircle
{
	Point trackCenter;	// 轨道中心
	float trackRadius;	// 轨道半径
	float circleRadius;	// 圆半径
	float radian;		// 弧度
};


void transform(Transition* transition);
void updateTargetPos(Transition* transition, double targetPosOffset);
void drawTrackCicle(const TrackCircle* trackCircle);

int main()
{
	const int winWidth = 640, winHeight = 480;
	initgraph(winWidth, winHeight, INIT_RENDERMANUAL);
	ege_enable_aa(true);
	setbkcolor(WHITE);

	setlinewidth(2);
	setcolor(BLACK);
	setfillcolor( EGEARGB(0xE0, 0x30, 0xC9, 0XF7));

	const double trackMaxPos = 12.0;

	Transition transition = { 0.0, 0.0 };

	TrackCircle trakcCircle =
	{
		{winWidth/2.0f, winHeight/2.0f},
		156.0f,
		64.0f,
		0.0f,
	};

	for (; is_run(); delay_jfps(60)) {
		int targetPosOffset = 0;

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

			//处理鼠标滚轮消息,根据滚动值累计位置差值
			if (msg.is_wheel()) {
				targetPosOffset += (msg.wheel > 0) ? 1 : -1;
			}
		}

		// 更新目标位置
		if (targetPosOffset != 0) {
			updateTargetPos(&transition, targetPosOffset);
		}

		transform(&transition);

		trakcCircle.radian = (float)(2.0 * PI * transition.cur / trackMaxPos);

		cleardevice();
		drawTrackCicle(&trakcCircle);
	}
	
	closegraph();
	return 0;
}

void updateTargetPos(Transition* transition, double targetPosOffset)
{
	transition->end += targetPosOffset;
}

void transform(Transition* transition)
{
	const double MIN_FORWARD_DISTANCE = 1.0 / 32.0;

	// 按照固定比例缩短当前位置与目标位置的距离
	if (transition->cur != transition->end) {
		double forwardDistance = 1.0 / 32.0 * (transition->end - transition->cur);

		// 控制最低前进距离,避免前进过慢
		if (fabs(forwardDistance) < MIN_FORWARD_DISTANCE) {
			forwardDistance = (forwardDistance >= 0) ? MIN_FORWARD_DISTANCE : -MIN_FORWARD_DISTANCE;
		}

		// 位置修正,避免越过目标位置
		if (forwardDistance / (transition->end - transition->cur) > (1.0 - 1E-3))
			transition->cur = transition->end;
		else
			transition->cur = transition->cur + forwardDistance;
	}
}

void drawTrackCicle(const TrackCircle* trackCircle)
{
	const Point* trackCenter = &trackCircle->trackCenter;
	const Point circleCenter = {
		trackCenter->x + trackCircle->trackRadius * cosf(trackCircle->radian),
		trackCenter->y + trackCircle->trackRadius * sinf(trackCircle->radian)
	};

	float left = trackCenter->x - trackCircle->trackRadius;
	float top  = trackCenter->y - trackCircle->trackRadius;
	float width = 2 * trackCircle->trackRadius;

	ege_ellipse(left, top, width, width);

	left = circleCenter.x - trackCircle->circleRadius;
	top  = circleCenter.y - trackCircle->circleRadius;
	width = 2 * trackCircle->circleRadius;

	ege_fillellipse(left, top, width, width);
	ege_ellipse(left, top, width, width);
}

三、鼠标事件

1. 鼠标消息处理循环

  从上面的示例可以看出,处理鼠标消息的方式先用 mousemsg() 判断消息队列中有没有鼠标消息,如果有就使用 getmouse() 从队列中读取消息,直至队列中的鼠标消息都读取完毕。

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

  为什么要使用循环一次性将队列中的鼠标消息处理完毕呢?因为当你检测到时队列中存在鼠标消息时,那已经是之前移动鼠标、点击鼠标按键产生的,如果这次不处理完毕,这些消息又会被遗留下来,等待下次处理,这样消息就存在滞后性,用户会感觉到明显的延迟。鼠标消息可能会一直快速产生,如果每次只处理一条,处理的速度跟不上消息产生的速度,那么消息就会堆积得越来越多,所以每次检测到时,及时处理完消息列队中停留的鼠标消息。

1.1 处理不及时所带来的消息滞后

  接下来看看实际情况如何,将上面的示例:跟随鼠标的圆while 循环 改为 if,每次只处理一条鼠标消息。运行示例,可以看到,不断移动鼠标,最后鼠标指针和圆偏离得越来越远,圆无法及时跟随鼠标指针。
  因为帧循环设定的是60FPS,比鼠标消息产生的速度要慢一些,产生的消息总是要过一会才处理到,但这时鼠标指针已经移动到另一个位置了。

  那可不可以通过提高帧率来提高处理速度呢,比如从60FPS提升到120FPS?可以,但是帧率过高会导致其它操作重复进行,耗费CPU资源,并且鼠标消息产生的速度是可以调节的,有些好一点的鼠标每秒能发送1000条消息,这样120FPS仍然无法满足要求,要提高到多少帧率就难以确定,并不是一个好的办法。

在这里插入图片描述

#include <graphics.h>
#include <Windows.h>

int main()
{
	const int winWidth = 640, winHeight = 480;
	initgraph(winWidth, winHeight, INIT_RENDERMANUAL);
	ege_enable_aa(true);
	setbkcolor(WHITE);

	POINT mousePos;

	//获取鼠标初始位置
	GetCursorPos(&mousePos);
	ScreenToClient(getHWnd(), &mousePos);

	setlinewidth(2);
	setcolor(BLACK);
	setfillcolor(EGERGB(0x30, 0xC9, 0XF7));
	const int radius = 64;
	bool redraw = true;

	for (; is_run(); delay_jfps(120)) {
		while (mousemsg()) {
			mouse_msg msg = getmouse();
			// 读取鼠标位置
			mousePos.x = msg.x;
			mousePos.y = msg.y;

			redraw = true;
		}

		if (redraw) {
			redraw = false;

			cleardevice();
			ege_fillellipse(mousePos.x - radius, mousePos.y - radius, 2 * radius, 2 * radius);
			ege_ellipse(mousePos.x - radius, mousePos.y - radius, 2 * radius, 2 * radius);
		}
	}

	closegraph();

	return 0;
}

1.2 消息处理循环中如何对消息进行处理

1.2.1 不应在消息处理循环外进行检测

  我们每次从消息队列中读取鼠标消息时,队列中存放的消息数量可能会有0 至数条不等的消息,这就意味着 不能 按下述代码先读取完所有消息,再在循环外进行检测:

mouse_msg msg = {0};

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

// 错误做法:在循环外检测鼠标消息
if (msg.is_left() && msg.is_down()) {

}

  因为当队列中有多条消息时,循环对同一个变量 msg 赋值,会将之前的值覆盖,只保存最后一条鼠标消息,这样就会有鼠标消息被漏掉。

  比如在鼠标移动时按下鼠标按键,这时消息队列中可能会有多条鼠标消息,因为移动消息发送的速度比较快,所以很可能在最后面的是移动消息,这样按键消息就会被漏掉。

1.2.2 在消息处理循环内对每一条消息进行检测

  我们需要在消息处理循环内对每一条消息进行检测,这样才不会遗漏掉消息。
  当然,因为鼠标移动消息产生的速度过快,可能达每条上千条,因此尽量避免检测到鼠标移动消息时进行复杂绘图等耗时操作,只记录处理数据,循环结束后再统一进行绘图操作。而其他如按下、抬起、滚轮之类的消息一般只有每秒几条,可以加入绘图操作,也可以只记录修改数据,等到后面统一进行绘图。

  鼠标消息中大多数是移动消息,在判断消息类型时,可以优先判断是否是鼠标移动消息,这样可以提高效率。

while (mousemsg())
{
	mouse_msg msg = getmouse();
	if (msg.is_move()) {
		//移动消息
	} else if (msg.is_down())) {
		//按键按下消息
	} else if (msg.is_up()) {
		//按键抬起消息
	} else  {
		//滚轮消息
	}
}

2. 获取鼠标指针位置

2.1 通过鼠标消息获取鼠标指针位置

  鼠标指针位置可以通过读取鼠标消息结构体 mouse_msg中的成员x, y来获取,里面记录的坐标是鼠标消息产生时,鼠标指针在窗口绘图区域中的坐标。

  需要注意的是,当鼠标指针移动到窗口外边缘的位置时,此时程序中所读取到的鼠标指针坐标是会出现超出创建窗口时所设定的大小范围的情况,如出现负值、超出最大值等。这是因为窗口外边缘实际上有个阴影边框,在这里产生的鼠标消息也能被窗口接收到。
  如果在程序中需要根据坐标进行计算,需要先检查坐标值的有效性。

在这里插入图片描述

while (mousemsg()) {
	mouse_msg msg = getmouse();
	int x = msg.x;
	int y = msg.y;
}

  那么有个问题,在没有接收到鼠标消息时,怎么确定鼠标的位置?如果创建窗口后不操作鼠标,窗口是接收不到鼠标消息,此时无法通过鼠标消息得到鼠标指针的位置,这个可以通过winAPI解决

2.2 win API 获取鼠标指针实时位置

  win API 中的 GetCursorPos() 函数可以得到当前鼠标指针在屏幕上的位置,而ScreenToClient() 函数可以将屏幕坐标转换为窗口客户区域坐标,这样就得到了鼠标指针的实时位置。

#include <Windows.h>

//获取鼠标初始位置
POINT mousePos;
GetCursorPos(&mousePos);
ScreenToClient(getHWnd(), &mousePos);

2.3 鼠标指针初始位置问题

  EGE20.08及之前的版本中,在窗口没有接收到鼠标消息之前,EGE的接口是读取不到准确的鼠标指针位置的。可以使用 win API 中的 GetCursorPos() 和 ScreenToClient() 函数读取鼠标指针的实时位置,作为位置初始值。

  读取到初始位置后,后续应该使用EGE的鼠标消息来获取鼠标指针位置。由于消息处理的滞后性,等到你进行处理时,距离鼠标消息产生可能已经过去了十几毫秒的时间。如果鼠标在这个过程中不断移动,鼠标指针位置可能已经有了极大的变化。这时候你获取的鼠标实时位置就就与消息产生时的位置不一样,从而造成误判。

2.4 mousepos() 函数

  mousepos()函数可以获取到EGE窗口所接收到的最后一次鼠标消息时鼠标指针的位置。两个参数都是 int* 型指针,用于返回鼠标指针的位置。

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

  因为 mousepos()函数是根据窗口接收到的鼠标消息来确定鼠标指针的位置的,这就意味着如果鼠标指针超出窗口范围,返回的位置将是错误的。
  同时,在EGE20.08以及之前的版本,如果窗口创建后鼠标没有动作,那么 mousepos()将会因没有接收到鼠标消息而无法确定鼠标指针的位置,返回一个错误值,在新版本中已完成修复。


3. 鼠标的点击判断

  鼠标按键有左键、中键和右键三个,按键消息只有两种类型:按下和抬起。这就有两种触发点击事件的方式,一种是鼠标按键按下时触发,另一个种是鼠标按键抬起时触发。

鼠标点击事件触发方式条件适用场景
按键按下时触发检测到鼠标消息类型为 按键按下快速响应
按键抬起时触发检测到鼠标消息类型为 按键抬起延迟响应,用于点击按钮、鼠标拖拽、框选等

  因为鼠标按键只会在按下抬起时各发送一条鼠标消息,长按并不会再次发送,所以检测到有按键按下抬起的消息时就可以直接判定为点击。

3.1 鼠标按下时触发点击

  当检测到鼠标消息类型是按键按下时判定为鼠标点击。

  鼠标按下时触发点击可以达到快速响应的效果。当检测到有按键按下时立刻触发点击,把按下时的鼠标指针位置作为点击位置,这时再由按下的按键进行相应的操作即可。

while (mousemsg()) {
	mouse_msg msg = getmouse();
	//以按键按下作为触发鼠标点击条件
	if (msg.is_down()) {
		// 用is_left(), is_mid() 和is_right()判断按键
		// msg.x, msg.y 是按键按下时鼠标的位置
	}
}
示例:鼠标按键按下时绘制圆

  检测到鼠标按键按下后,在指针点击处绘制一个圆,圆的颜色由按下的按键决定。
在这里插入图片描述

#include <graphics.h>

int main()
{
	const int winWidth = 640, winHeight = winWidth * 3 / 4;

	initgraph(winWidth, winHeight, INIT_RENDERMANUAL);
	ege_enable_aa(true);
	setbkcolor(WHITE);

	const float radius = 48.0f;

	setlinewidth(2);
	setcolor(BLACK);

	for (; is_run(); delay_fps(60)) {
		// 处理所有鼠标消息
		while (mousemsg()) {
			mouse_msg msg = getmouse();

			// 当鼠标消息是按键按下时进行处理
			if (msg.is_down()) {
				// 根据触发按键设置不同的颜色
				color_t circleColor;
				if (msg.is_left())
					circleColor = EGERGB(0x30, 0xC9, 0XF7);
				else if (msg.is_right())
					circleColor = EGERGB(0xA8, 0xE3, 0xA9);
				else
					circleColor = EGERGB(0xFC, 0xF3, 0x84);

				// 以鼠标点击处为圆心绘制圆
				setfillcolor(circleColor);
				ege_fillellipse(msg.x - radius, msg.y - radius, 2 * radius, 2 * radius);
				ege_ellipse(msg.x - radius, msg.y - radius, 2 * radius, 2 * radius);
			}
		}
	}

	closegraph();

	return 0;
}

3.2 鼠标按键抬起时触发点击

  检测到鼠标消息类型为按键抬起时判定为鼠标点击。

  当把鼠标按键抬起作为鼠标点击的触发条件时,鼠标按下和抬起并非是独立的,一般是把按键按下时指针的位置作为点击位置,只是点击操作会延迟到按键抬起时才触发。
  在鼠标按键按下时,就可以根据按下的位置对按钮等进行检测,如果有需要,可以记录下此时的位置,。等到按键抬起时,根据之前的检测结果执行相应的点击操作。
在这里插入图片描述

// 记录
int xLeftPress = -1, yLeftClick = -1;

while (mousemsg())
{
	mouse_msg msg = getmouse();
	
	// 检测鼠标左键点击操作
	if (msg.is_left()) {
		if (msg.is_down()) {
			// 按下:记录左键按下位置
			xClick = msg.x;
			yClick = msg.y;
		} else {
			// 抬起:执行点击操作	
		}
	}
}
示例:鼠标点击按钮

  鼠标点击按钮,按钮会改变颜色样式。
在这里插入图片描述

#include <graphics.h>

typedef struct ColorStyle
{
	color_t normal;
	color_t press;
} ColorStyle;

typedef struct Button
{
	float x;
	float y;
	float radius;
	int styleIndex;
	bool press;
} Button;

// 改变按钮样式
void changeButtonStyle(Button* button);

// 获取颜色样式
const ColorStyle* getColorStyle(int index);

// 绘制按钮
void drawButton(const Button* button);

// 判断点击位置是否在按钮区域
bool isPressOnButton(const Button* button, int x, int y);

#define COLOR_STYLE_NUM  3
const ColorStyle colorStyleTable[COLOR_STYLE_NUM] = {
	{EGEACOLOR(0xFF, 0xA0D8EF), EGEACOLOR(0xFF, 0x2CA9E0)},
	{EGEACOLOR(0xFF, 0xFCF384), EGEACOLOR(0xFF, 0xF9CA31)},
	{EGEACOLOR(0xFF, 0xEBC4DB), EGEACOLOR(0xFF, 0xCC7EB0)},
};

int main()
{
	const int winWidth = 640, winHeight = winWidth * 3 / 4;

	initgraph(winWidth, winHeight, INIT_RENDERMANUAL);
	ege_enable_aa(true);
	setbkcolor(WHITE);

	Button button = {winWidth / 2.0f, winHeight / 2.0f, 64.0f, 0, false};

	int xLeftPress = -1, yLeftPress = -1;
	bool flag_pressButton = false;

	for (; is_run(); delay_fps(60)) {
		// 处理所有鼠标消息
		while (mousemsg()) {
			mouse_msg msg = getmouse();

			// 处理鼠标左键点击
			if (msg.is_left()) {
				if (msg.is_down()) {
					xLeftPress = msg.x;
					yLeftPress = msg.y;

					// 如果鼠标在按钮区域按下,设置鼠标为按下状态,标记鼠标按下按钮
					if (isPressOnButton(&button, xLeftPress, yLeftPress)) {
						flag_pressButton = true;
						button.press = true;
					}
				} else {
					//鼠标按下按钮,抬起时触发点击事件
					if (flag_pressButton) {
						flag_pressButton = false;

						// 恢复按钮为非按下状态,并改变按钮样式
						button.press = false;
						changeButtonStyle(&button);
					}
				}
			}
		}
		cleardevice();
		drawButton(&button);
	}

	closegraph();
	return 0;
}

// 改变按钮样式
void changeButtonStyle(Button* button)
{
	// 切换到样式表中下一个样式
	button->styleIndex++;
	if (button->styleIndex >= COLOR_STYLE_NUM) {
		button->styleIndex = 0;
	}
}

// 获取颜色样式
const ColorStyle* getColorStyle(int index)
{
	return &colorStyleTable[index];
}

bool isPressOnButton(const Button* button, int x, int y)
{
	float dx = x - button->x;
	float dy = y - button->y;
	return (dx * dx + dy * dy) <= (button->radius * button->radius);
}

void drawButton(const Button* button)
{
	// 获取按钮当前的颜色样式
	const ColorStyle* colorStyle = getColorStyle(button->styleIndex);

	// 根据按钮是否被按下设置颜色
	if (button->press)
		setfillcolor(colorStyle->press);
	else
		setfillcolor(colorStyle->normal);

	setlinewidth(2);
	setcolor(BLACK);

	ege_fillellipse(button->x - button->radius,button->y - button->radius, 2 * button->radius, 2 * button->radius);
	ege_ellipse(button->x - button->radius,button->y - button->radius, 2 * button->radius, 2 * button->radius);
}

4. 鼠标按键状态

  鼠标按键有按下松开两种状态,按键状态切换时,会发送相应的鼠标消息。

状态切换发送鼠标消息
松开 → \rightarrow 按下按键按下(mouse_msg_down)
按下 → \rightarrow 松开按键抬起(mouse_msg_up)

4.1 读取鼠标消息记录按键状态

  因此,我们可以通过读取鼠标消息来判断鼠标按键的状态:当接收到一条按键按下的鼠标消息时,标记对应按键的状态为按下;当接收到一条按键抬起的鼠标消息时,标记对应按键状态为松开。

// 鼠标左键是否被按下
bool leftKeyIsPressed = false;

while (mousemsg()) {
	mouse_msg msg = getmouse()
	
	if (msg.is_left()) {
		leftKeyIsPressed = msg.is_down();
	}
}

4.2 keystate()函数

  EGE中还有个 keystate() 函数可以获取按键当前的状态,如果按键是按下的,则返回 1,否则返回 0。如果只想知道当前按键的状态而不想知道鼠标做按下按键、移动、松开按键等操作的顺序,那么就可以使用 keystate()。如果交互的功能与鼠标操作的顺序有关(鼠标拖拽等),那就只能使用鼠标消息来进行处理。

  鼠标左中右三个键的键码如下:

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

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

if (keystate(key_mouse_l)) {
	// 鼠标左键为按下状态
}

示例:根据鼠标按键消息识别鼠标按键状态

  通过读取鼠标消息,得到鼠标按键状态,然后根据鼠标按键状态在上方或在下方绘制圆。

在这里插入图片描述

#include <graphics.h>

#define MOUSE_KEY_NUM    3

// 枚举:鼠标按键索引
typedef enum KeyIndex
{
	KeyIndex_LEFT  = 0,  // 鼠标左键
	KeyIndex_MID   = 1,  // 鼠标中键
	KeyIndex_RIGHT = 2,  // 
} KeyIndex;

// 枚举:鼠标按键状态
typedef enum KeyState
{
	KeyState_UP   = 0,
	KeyState_DOWN = 1,
} KeyState;

int main()
{
	const int winWidth = 640, winHeight = winWidth * 3 / 4;

	initgraph(winWidth, winHeight, INIT_RENDERMANUAL);
	ege_enable_aa(true);
	setbkcolor(WHITE);

	bool redraw = true;
	int leftMargin = 80;

	//颜色表
	color_t colorTable[MOUSE_KEY_NUM] = { EGERGB(0x30, 0xC9, 0XF7), EGERGB(0xFC, 0xF3, 0x84), EGERGB(0xA8, 0xE3, 0xA9), };

	//记录按键状态
	KeyState mouseKeyState[MOUSE_KEY_NUM];

	for (int i = 0; i < MOUSE_KEY_NUM; i++) {
		mouseKeyState[i] = KeyState_UP;
	}

	setlinewidth(2);
	setcolor(BLACK);

	for (; is_run(); delay_fps(60)) {
	
		// 处理所有鼠标消息
		while (mousemsg()) {
			mouse_msg msg = getmouse();

			// 当鼠标消息是按键消息时进行处理
			if (msg.is_down() || msg.is_up()) {
				//确定是按键按下还是按键抬起
				KeyState keyState = msg.is_down() ? KeyState_DOWN : KeyState_UP;

				//根据触发的按键,改变对应对应按键的状态
				if (msg.is_left())
					mouseKeyState[KeyIndex_LEFT] = keyState;
				else if (msg.is_mid())
					mouseKeyState[KeyIndex_MID] = keyState;
				else
					mouseKeyState[KeyIndex_RIGHT] = keyState;
				redraw = true;
			}
		}

		// 绘图
		if (redraw) {
			cleardevice();

			for (int i = 0; i < MOUSE_KEY_NUM; i++) {
				//计算圆心位置及半径
				const float radius = winHeight * 2 / 11;
				const int cx = (3 + i * 5) * winWidth / 16;
				const int cy = (mouseKeyState[i] == KeyState_UP) ? (3 * winHeight / 11) : (8 * winHeight / 11);

				setfillcolor(colorTable[i]);
				ege_fillellipse(cx - radius, cy - radius, 2 * radius, 2 * radius);
				ege_ellipse(cx - radius, cy - radius, 2 * radius, 2 * radius);
			}
		}
	}

	closegraph();

	return 0;
}

5. 鼠标拖拽

  鼠标拖曳即鼠标按键按住不放,然后移动鼠标。

  鼠标的拖拽功能,大致处理如下:
  ① 当鼠标左键按下时,根据鼠标指针位置确定选中的目标,并记录选中的目标和按下的位置。
  ② 鼠标移动时,如果此时按键处于按下状态,那么对目标进行拖曳判断,如果目标可以被拖拽,就根据鼠标指针当前位置和之前按下时的位置执行相应的操作。

  如果总体拖拽的功能数量较少,也可以在按键按下时就确定目标和相应的功能,在鼠标拖拽时就可以对目标进行相应的拖拽处理。
在这里插入图片描述

#include <graphics.h>
#include <math.h>
#include <ShellScalingApi.h>

struct Circle
{
	int x, y;
	float radius;
	int borderWidth;
};

enum Action
{
	Action_NONE = 0,
	Action_MOVE,
	Action_SCALE,
};

bool PointIsInCircle(const Circle* circle, int x, int y, bool containBorder);
double distance(int a, int b);

void moveCircle(Circle* circle, int x, int y);
void initAllCircle(Circle circleArray[], int num);
void setCircleRadius(Circle* circle, int radius);
void drawCircle(const Circle* circle);

int main()
{
	SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
	initgraph(640, 480, INIT_RENDERMANUAL);
	setbkcolor(WHITE);
	setcolor(BLACK);
	setfont(20, 0, "楷体");
	setbkmode(TRANSPARENT);
	ege_enable_aa(true);

	const int CIRCLE_NUM = 3;
	Circle circleArray[CIRCLE_NUM];
	initAllCircle(circleArray, CIRCLE_NUM);

	int xPressPos = -1, yPressPos = -1;
	int circleIndex = -1;

	Circle initialCircle = { 0 };

	Action action = Action_NONE;
	bool redraw = true;

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

			//鼠标左键点击位置记录
			if (msg.is_move() ) {
				if (action == Action_MOVE) {
					//根据按下位置到当前位置的偏移量计算目标位置
					int xTarget = initialCircle.x + msg.x - xPressPos;
					int yTarget = initialCircle.y + msg.y - yPressPos;

					moveCircle(&circleArray[circleIndex], xTarget, yTarget);
				}
				else if (action == Action_SCALE) {
					//根据基点和当前位置分别到圆心的距离,计算圆半径
					int xCircle = circleArray[circleIndex].x;
					int yCircle = circleArray[circleIndex].y;
					float radiusDiff = distance(msg.x - xCircle, msg.y - yCircle) - distance(xPressPos - xCircle, yPressPos - yCircle);
					float radius = initialCircle.radius + radiusDiff;

					setCircleRadius(&circleArray[circleIndex], radius);
				}
				redraw = true;
			}
			else if (msg.is_left()) {
				if (msg.is_down()) {
					//设置基点为点击位置
					xPressPos = msg.x;
					yPressPos = msg.y;

					action = Action_NONE;

					//对每个物体进行检测,
					for (int i = 0; i < CIRCLE_NUM; i++) {
						if (PointIsInCircle(&circleArray[i], xPressPos, yPressPos, true)) {
							// 根据左键按下位置是在圆内还是边框,设置拖拽
							if (PointIsInCircle(&circleArray[i], xPressPos, yPressPos, false))
								action = Action_MOVE;
							else
								action = Action_SCALE;

							initialCircle = circleArray[i];
							circleIndex = i;
							break;
						}
					}
				}
				else {
					// 动作复位
					action = Action_NONE;
				}
			}
		}

		//空格键重置
		while (kbmsg()) {
			key_msg msg = getkey();
			if ((msg.key == key_space) && (msg.msg == key_msg_up)) {
				initAllCircle(circleArray, CIRCLE_NUM);
				action = Action_NONE;
				redraw = true;
			}
		}

		//重绘
		if (redraw) {
			redraw = false;
			cleardevice();

			for (int i = CIRCLE_NUM - 1 ; i >= 0; i--)
				drawCircle(&circleArray[i]);

			setcolor(BLACK);
			outtextxy(300, 0, "拖动内圆移动,拖动外环调整大小");
			outtextxy(300, 22, "按空格键重置位置及大小");
		}
	}

	closegraph();

	return 0;
}

bool PointIsInCircle(const Circle* circle , int x, int y, bool containBorder)
{
	double dx = x - circle->x;
	double dy = y - circle->y;
	double radius = circle->radius - circle->borderWidth / 2.0f;

	if (containBorder)
		radius += circle->borderWidth;

	return dx * dx + dy * dy <= radius * radius;
}

double distance(int a, int b)
{
	return sqrt(a * a + b * b);
}

void moveCircle(Circle* circle, int x, int y)
{
	circle->x = x;
	circle->y = y;
}

void initAllCircle(Circle circleArray[], int num)
{
	for (int i = 0; i < num; i++) {
		circleArray[i].x = 100 + i * 200;
		circleArray[i].y = 240;
		circleArray[i].radius = 80.0;
		circleArray[i].borderWidth = 10;
	}
}

void setCircleRadius(Circle* circle, float radius)
{
	if (radius < circle->borderWidth)
		radius = circle->borderWidth;

	circle->radius = radius;
}

void drawCircle(const Circle* circle)
{
	float x = circle->x - circle->radius, y = circle->y - circle->radius;
	float width = circle->radius * 2.0f, height = width;

	//绘制填充圆
	setfillcolor(EGEARGB(0xFF, 0xFF, 0x33, 0xFF));
	ege_fillellipse(x, y, width, height);

	//绘制边框
	setlinewidth(circle->borderWidth);
	setcolor(EGEARGB(0xFF, 0xA0, 0x00, 0xA0));
	ege_ellipse(x, y, width, height);
}

EGE专栏: EGE专栏

windows消息 鼠标消息、定时器消息
单于的学习历程
11-19 1809
windows消息定时器消息
EGE绘图之五 按钮(上)
m0_67403143的博客
03-03 301
EGE专栏:EGE专栏 上一EGE绘图之四 Gif动图播放 下一EGE绘图之五 按钮(下) EGE绘图之(五) 按钮(上) 文章最后修改时间:2021年7月7日22:24:34 目录 一、按钮功能 1. 普通按钮 2. 可选按钮 2.1 单选按钮 (Radio Button) 2.2 复选框 (Check Box) 3. 多功能按钮 二、按钮样式 三、 按钮点击 1. 按钮点击的确定 2.按钮的点击判定 2.1 矩形区域点击判定 2.1.1 矩形区域的表示 2.1
用代码实现鼠标运行状态的VC++示例
03-17
内容索引:VC/C++源码,系统相关,鼠标  用代码实现鼠标运行状态的VC++示例,也就是说,在点击鼠标的时候一般有三种状态,正常状态:显示鼠标的指针;运行状态:显示繁忙的图标;选择或输入状态鼠标变成光标。本代码是在不通过点击鼠标的情况下,模拟出这三种状态,其实程序没有什么用途,只是为了熟悉鼠标的有关操作。
(七)EGE基础绘图
01-06
目录基础绘图画笔设置设置当前绘画颜色设置填充颜色设置当前绘画线型设置当前绘画线宽绘图位操作模式绘图目标图形的绘制(1) 普通绘图函数普通绘图函数带有锯齿普通绘图函数使用的是RGB颜色绘图函数像素点多个像素点线圆(线框, 内部无填充):填充椭圆(内部带颜色)填充圆更多普通绘图函数请参考官网文档如何画圆?内部有颜色的那种(2) 区域颜色填充 基础绘图 画笔设置 设置当前绘画颜色 void setcolor(color_t color, PIMAGE pimg = NULL); 设置填充颜色 void setfillcolor(color_t color, PIMAGE pimg = NULL);
C C++最全鼠标事件、键盘事件,你听过嘛?,推荐
最新发布
2401_84973144的博客
05-15 852
📜个人简介⭐️🙋‍♂️🍑🍅🌸🚀好久不见,甚是想念!大家好!,如果你早已知晓,那就考考你,看看下面的内容你还记得多少。
C++代码实现Windows系统下发送鼠标移动和单击的程序
05-25
c++代码实现系统层发送鼠标移动和单击消息,可实现模拟人工点按某个按钮的功能。基于此代码可进行功能扩充,例如录制多个按钮位置实现组合操作的自动化执行。
获取各种鼠标信息的方法
weixin_30768661的博客
06-27 288
主要内容: 鼠标双击的时间间隔 鼠标光标的闪烁频率 鼠标的按键数目 鼠标等待时的光标 鼠标在窗体上的的位置 记录鼠标的行为 截取系统信息判断鼠标的单击键 1.鼠标的双击时间间隔 实现效果: 主要利用API函数GetDoubleClickTime(用于判断连续两次鼠标单击之间会被处理成双击事件的间隔时间) 声明语法如下: [DllImport("user32...
css 鼠标点击的几种状态
jinchenga的博客
03-09 1228
【代码】css 鼠标点击的几种状态
windows 鼠标消息触发及处理流程
wugang0825的专栏
12-06 2114
windows 鼠标消息触发及处理流程(自己的理解,仅做参考) 假设用户鼠标单击Overlap类型窗口的菜单,解释鼠标消息的生成及传递过程 1)鼠标驱动程序向windows发送中断,缓存数据中包含鼠标事件类型,和鼠标事件发生的屏幕坐标;(由于对驱动不了解,为了更好的解释下面的步骤,暂时这么理解) 2)windows的explorer进程接收到事件,将根据鼠标的坐标得到坐标位置上Z轴靠前的窗口
windows系统对鼠标点击事件的处理过程
down_login的专栏
03-12 7317
Windows用这个消息来做什么? “HITTEST”就是“命中测试”的意思,WM_NCHITTEST消息用来获取鼠标当前命中的位置。 WM_NCHITTEST的消息响应函数会根据鼠标当前的坐标来判断鼠标命中了窗口的哪个部位,消息响应函数的返回值指出了部位,例如它可能会返回HTCAPTION,或者HTCLIENT等。(其返回值有很多,请查阅MSDN)。 为了便于理解,我先描述一下Windows
计算机网络(七)
qq_40599849的博客
03-21 2938
一、HTTP协议 HTTP(HyperText Transfer Protocol,超⽂本传输协 )的协议。 HTTP是⽆连接, ⽆状态, ⼯作在应⽤层的协议。 ⽆连接理解为: http协议本身是没有维护连接信息的, http的数据会交给⽹络协议栈传输层的TCP协议, ⽽TCP是⾯向连接的。 ⼤家注意区别。 ⽆状态: HTTP 协议⾃身不对请求和响应之间的通信状态进⾏保存。也就是说在 HTTP 这个级别,协议对于发送过的请求或响应都不做持久化处理。 HTTP是可靠传输的,虽然HTTP无连接,自身不会维护连
鼠标事件的Windows消息
01-08
鼠标事件的Windows消息,以文本形式打开,仅供参考,如有不对请指出
实现鼠标点击图形填充
06-25
将屏幕上的鼠标选取点作为多边形顶点进行填充 计算机图形学作业之一
C++EGE: Ballon Ball 动画
02-19
1、泡泡会跟着你的鼠标指针跑 2、按下空格键可以暂停 3、按下SC退出程序 ******************部分代码展示****************** #ifndef EGE_H #define EGE_H #if defined(_MSC_VER) && (_MSC_VER >= 1200) #pragma...
EGE小游戏:字母游戏
07-02
玩法:从上空掉落苹果字母,如果键盘上按下的字母与掉落的字母一致,则该苹果消失,在任意上方位置出现新的苹果字母。
C++EGE:烟花女友表白代码龙年快乐
02-19
**********使用说明书*********** 1、先解压。 2、代码文件:烟花\Code\ 3、主程序代码:main.cpp ...void EGEAPI ege_set_transform(ege_transform_matrix* const pmatrix, PIMAGE pimg = NULL); ege_point EGEAPI
迷宫 C++ ege 图片 鼠标检测 键盘控制
09-06
简单的游戏文档,其中资源文件可以根据用户自己电脑文件绝对路径进行修改
使用mouse_msg结构体实现简单图形的橡皮条技术
qq_43606914的博客
04-23 4032
mouse_msg结构体:用于保存鼠标消息 有关其具体声明和成员介绍及其他键盘鼠标输入函数可参阅 https://xege.org/manual/api/input/index.htm 橡皮条技术(以直线为例): 选择第一个线段端点 光标移动时从初始化拉出一线段 线段随光标移动直到选定第二个端点 下面是运用橡皮条技术绘制直线的代码: #include <graphics.h> ...
windows编程之鼠标消息总结
eskimoer的专栏
04-13 7362
1 确定鼠标是否存在:fMouse = GetSystemMetrics (SM_MOUSEPRESENT) ; 2 获取鼠标上按键数目:cButtons = GetSystemMetrics (SM_CMOUSEBUTTONS) ; 3 鼠标消息: 可以分为显示区域消息,非显示区域消息,总共21个鼠标消息,其中11个消息和显示区域无关。 WM_MOUSEMOVE --鼠标移过窗口的显示区
devc++ EGE 鼠标信息
12-09
根据提供的引用内容,可以了解到在使用EGE库进行编程时,可以通过以下代码获取鼠标信息: ```c++ while(mousemsg()) { msg=getmouse(); } ``` 其中,`mousemsg()`函数用于持续监测鼠标点击事件,当鼠标被点击时,`getmouse()`函数会获取鼠标的信息并将其存储在`msg`变量中。通过这种方式,我们可以获取鼠标的位置、点击状态等信息,从而实现一些与鼠标相关的操作。 另外,如果想要在Dev-C++中使用EGE库,可以按照以下步骤进行操作: 1. 在EGE官网中下载小熊猫Dev-C++。 2. 打开Dev-C++,依次点击菜单中的“文件”->“新建”->“新建项目”。 3. 在弹出的对话框中,选择“多媒体”->“Graphics.h”作为项目类型,并设置项目名称和保存路径。 4. 在代码中引入`graphics.h`头文件,并使用`initgraph`函数初始化绘图环境,即可开始使用EGE库进行编程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
写文章

热门文章

  • EGE 安装与配置 78774
  • (一)EGE介绍 47101
  • EGE基础:图像操作篇 29720
  • EGE基础:输入篇 22801
  • 算法篇:逆序对 19843

分类专栏

  • 编程语言
  • C++
  • Python 3篇
  • Java
  • EGE 48篇
  • EGE 入门教程 9篇
  • EGE 分类教程 14篇
  • EGE 示例程序 5篇
  • EGE 编程开发 6篇
  • 绘图软件
  • Matplotlib 1篇
  • GeoGebra 1篇
  • WaveDrom 3篇
  • 算法 12篇
  • 数据结构与算法分析 2篇
  • 算法竞赛 2篇
  • 离线渲染和实时渲染 2篇

最新评论

  • EGE示例程序——花火闪烁的夜晚

    依稀_yixy: 先配置 ege 图形库,这不是标准库,需要自己配置

  • EGE示例程序——花火闪烁的夜晚

    shockworld: 博主,请问为什么color_t color这一步总是报错?求解

  • EGE示例程序——2048

    依稀_yixy: 可以,但是你需要先配置 EGE 图形库

  • EGE示例程序——2048

    陈7嘎嘎厉害: 你好,这个代码不能用在devc++吗

  • EGE基础:输入篇

    τáο心幻_べ: #error: include "sys_edit.h" must after include "ege.h" or "graphics.h" 我这个是什么情况,我已经提前调用了graphics.h图形库了

大家在看

  • codeArts Snap:辅助你的编程神器 583
  • 【漏洞复现】海康威视 综合安防管理平台 session接口 远程代码执行漏洞
  • 密码学及其应用——为什么选择接近的质数因子对RSA加密算法不安全? 391
  • 数据挖掘的基本介绍以及Python、pandas的基本应用 162
  • C#中深拷贝和浅拷贝的介绍与用法 652

最新文章

  • Easy Graphics Engine on GitHub
  • EGE专栏内容导航
  • (二)算法分析
2024年1篇
2022年16篇
2021年12篇
2020年39篇
2019年9篇

目录

目录

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

依稀_yixy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或 充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值

聚圣源8月8号店铺免费起名的姓名测试打分装饰公司起名字傅 起名子免费娃娃起名字news.sina.com.cn俞恩傅廷远免费阅读小说无敌推销员powerbuilder下载加里奥出装737求人帮忙说公司注册名称怎么起园林企业起名大全集女性的胸单机游戏免费下载网站蛇的成语韫色过浓未删减百度云广字辈取名起名大全男我是你额上熏黑的矿灯100022邮编网起名免费起名打分吉林移动免费黑色皮肤代码雷姓起名拜伦·戴维斯全国免费起名紧扣的星星动漫权财开店起名风险淀粉肠小王子日销售额涨超10倍罗斯否认插足凯特王妃婚姻让美丽中国“从细节出发”清明节放假3天调休1天男孩疑遭霸凌 家长讨说法被踢出群国产伟哥去年销售近13亿网友建议重庆地铁不准乘客携带菜筐雅江山火三名扑火人员牺牲系谣言代拍被何赛飞拿着魔杖追着打月嫂回应掌掴婴儿是在赶虫子山西高速一大巴发生事故 已致13死高中生被打伤下体休学 邯郸通报李梦为奥运任务婉拒WNBA邀请19岁小伙救下5人后溺亡 多方发声王树国3次鞠躬告别西交大师生单亲妈妈陷入热恋 14岁儿子报警315晚会后胖东来又人满为患了倪萍分享减重40斤方法王楚钦登顶三项第一今日春分两大学生合买彩票中奖一人不认账张家界的山上“长”满了韩国人?周杰伦一审败诉网易房客欠租失踪 房东直发愁男子持台球杆殴打2名女店员被抓男子被猫抓伤后确诊“猫抓病”“重生之我在北大当嫡校长”槽头肉企业被曝光前生意红火男孩8年未见母亲被告知被遗忘恒大被罚41.75亿到底怎么缴网友洛杉矶偶遇贾玲杨倩无缘巴黎奥运张立群任西安交通大学校长黑马情侣提车了西双版纳热带植物园回应蜉蝣大爆发妈妈回应孩子在校撞护栏坠楼考生莫言也上北大硕士复试名单了韩国首次吊销离岗医生执照奥巴马现身唐宁街 黑色着装引猜测沈阳一轿车冲入人行道致3死2伤阿根廷将发行1万与2万面值的纸币外国人感慨凌晨的中国很安全男子被流浪猫绊倒 投喂者赔24万手机成瘾是影响睡眠质量重要因素春分“立蛋”成功率更高?胖东来员工每周单休无小长假“开封王婆”爆火:促成四五十对专家建议不必谈骨泥色变浙江一高校内汽车冲撞行人 多人受伤许家印被限制高消费

聚圣源 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化