栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

c/c++游戏编程之Easyx图形库基础(三)

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

c/c++游戏编程之Easyx图形库基础(三)

c/c++游戏编程之Easyx图形库基础(一)
c/c++游戏编程之Easyx图形库基础(二)
c/c++游戏编程之Easyx图形库基础(三)

文章目录
    • 新建button头文件和源文件
    • 创建命名空间MyUI
    • 禁用拷贝构造函数和赋值拷贝运算符
    • 定义Draw函数
    • 制作一张简单的按钮图片
    • 绘制按钮,验证返回值
    • 更加细节的做法

新建button头文件和源文件

阅读本节内容建议掌握的前置知识:多文件编程。
这节内容我们介绍如何利用easyx来实现一个Button(按钮)类。

如图所示,在上一节的项目中分别新建头文件button.h,源文件button.cpp。

button.h:

//button.h

#pragma once //#pragma once是一个比较常用的C/C++预处理指令,只要在头文件的最开始加入这条预处理指令,使头文件不会被重复包含。

#include 

class IMAGE; //声明IMAGE类
struct ExMessage; //声明MOUSEMSG结构体

namespace MyUI {
	
	enum class BUTTON_MSG : short {
		NOMSG = 0, //鼠标与按钮无关联
		MOUSE_IN, //鼠标位于按钮内
		MOUSE_LDOWN, //鼠标左键按下
		MOUSE_LUP, //鼠标左键抬起
		MOUSE_RDOWN, //鼠标右键按下
		MOUSE_RUP, //鼠标右键抬起
	};
	
	//Button描述结构体
	struct BUTTON_DESC {
		LPCTSTR pImgFile; //图片路径
		int posx; //图片初始x坐标
		int posy; //图片初始y坐标
		unsigned int width; //图片宽度
		unsigned int height; //图片高度
		unsigned int transparency; //图片透明度
	};

	class Button {
	public:

		Button(const BUTTON_DESC& _btnDesc);
		Button(const Button& _btn) = delete; //禁用默认拷贝构造函数
		Button& operator = (const Button& _btn) = delete; //禁用默认赋值拷贝运算符
		~Button();

	protected:

		IMAGE* pImg_; //指向图像对象的指针
		BUTTON_DESC btnDesc_;

	};
}


#pragma once 的作用相当于:

#ifndef BUTTON_H_
#define BUTTON_H_

#endif

两者的作用都是防止头文件被重复包含。

创建命名空间MyUI

使用namespace关键字将我们自定义的控件类放在命名空间MyUI里。
(Button这个类名可能会与其他库定义的Button冲突,可以选择不这样做,这里主要是为了让不懂的同学学习命名空间的用法)

禁用拷贝构造函数和赋值拷贝运算符

因为我们有数据成员 IMAGE* pImg_ ,我们将用它指向一块动态内存。所以需要先禁用Button类的默认拷贝构造函数和默认拷贝赋值运算符,防止隐式的拷贝操作导致内存管理出现问题。

button.cpp:

//button.cpp

#include "button.h"
#include 

namespace MyUI {
	Button::Button(const BUTTON_DESC& _btnDesc) : btnDesc_(_btnDesc) {
		pImg_ = new IMAGE;
		loadimage(pImg_, _btnDesc.pImgFile, _btnDesc.width, _btnDesc.height);
	}

	Button::~Button() {
		delete pImg_;
	}
}

我们在构造函数用参数初始化列表初始化btnDesc_,并为pImg_分配动态内存,在析构函数里释放它。

定义Draw函数

好了,现在的Button空空如也,我们需要定义一个将按钮绘制出来的成员函数。还记得上一节的透明贴图函数吗?我们只需要将它改一改,变成Button类的成员函数即可。
(如果你要封装更多的控件,可以将此段贴图代码做成一个接口,放在公共文件里,供实现控件绘图方法调用)。

//在button.h的声明
BUTTON_MSG Draw(const ExMessage& _msMsg);

//在button.cpp的定义
BUTTON_MSG Button::Draw(const ExMessage& _msMsg) {
		HDC imgDC = GetImageHDC(pImg_); //获取图像设备上下文句柄

		BUTTON_MSG rt = BUTTON_MSG::NOMSG;

		//通过坐标值判断鼠标是否在按钮内
		if (_msMsg.x >= btnDesc_.posx && _msMsg.x <= (btnDesc_.posx + btnDesc_.width) &&
			_msMsg.y >= btnDesc_.posy && _msMsg.y <= (btnDesc_.posy + btnDesc_.height)) 
		{
			if (WM_LBUTTONDOWN == _msMsg.message) {
				rt = BUTTON_MSG::MOUSE_LDOWN;
			} 
			else if (WM_LBUTTONUP == _msMsg.message) {
				rt = BUTTON_MSG::MOUSE_LUP;
			}
			else {
				rt = BUTTON_MSG::MOUSE_IN;
			}
		}

		// 结构体的第三个成员表示额外的透明度,0 表示全透明,255 表示不透明。
		BLENDFUNCTION bf = { AC_SRC_OVER, 0, btnDesc_.transparency, AC_SRC_ALPHA };

		// 使用AlphaBlend函数实现半透明贴图
		AlphaBlend(
			GetImageHDC(NULL), //设备上下文句柄
			btnDesc_.posx, //绘制的x坐标
			btnDesc_.posy,  //绘制的y坐标
			btnDesc_.width, //在所选设备中绘制图像的宽度
			btnDesc_.height, //在所选设备中绘制图像的高度
			imgDC, //图像设备上下文句柄
			0, 0, //绘制图像起点坐标
			btnDesc_.width, //绘制图像的宽度
			btnDesc_.height, //绘制图像的高度
			bf
		);

		return rt;
	}

我们使用枚举类BUTTON_MSG的枚举值作为Draw函数的返回值,目的是在于告诉Draw的调用者鼠标与按钮产生了什么交互。

制作一张简单的按钮图片

先制作了一张简单的按钮图片,老规矩,将它放在images文件夹:
(由于我们是使用图片大小作为按钮大小,所以图片最好不要有太多没有内容的地方,不然会影响判定范围,当然你也可以自定义一个判定范围,或者不使用图片而使用画矩形的方式实现按钮类)

绘制按钮,验证返回值

main.cpp

#include 
#include 
#include 
#include 
#include 
#include "button.h"

constexpr int SCREEN_WIDTH = 640;
constexpr int SCREEN_HEIGHT = 640;

void EventBtnDown() {
	std::cout << "鼠标在按钮内按下了左键" << std::endl;
}

void EventBtnUp() {
	std::cout << "鼠标在按钮内释放了左键" << std::endl;
}

int main() {
	initgraph(SCREEN_WIDTH, SCREEN_HEIGHT, EW_SHOWCONSOLE); //初始化窗口

	MyUI::BUTTON_DESC btnDesc = { "images/button1.png", 200, 200, 200, 60, 255 }; //按钮描述结构体
	MyUI::Button btn1(btnDesc); //构造按钮对象
	MyUI::BUTTON_MSG btnMsg; //用于接收按键消息的结构体对象

	BeginBatchDraw();

	while (1) {
		ExMessage msMsg;
		
		//easyx非阻塞获取鼠标消息,如果peekmessage返回值为false,说明没有消息
		//如果此时不清空msMsg,msMsg的值会是之前的消息值
		if (!peekmessage(&msMsg, EM_MOUSE, true)) {
			msMsg = { 0 }; //没消息就清空msMsg
		}

		btnMsg = btn1.Draw(msMsg);

		if (MyUI::BUTTON_MSG::MOUSE_LDOWN == btnMsg) {
			EventBtnDown();
		}
		else if (MyUI::BUTTON_MSG::MOUSE_LUP == btnMsg) {
			EventBtnUp();
		}

		FlushBatchDraw();

		Sleep(16); //程序休眠16毫秒
		cleardevice(); //16毫秒后清空窗口中的内容
	}

	EndBatchDraw();

	_getch();
	closegraph(); //关闭窗口

	return 0;
}

你可以像这样通过判断按钮返回值作出响应操作(这里是分别调用EventBtnDown和EventBtnUp函数)。

如下,也可以做成一个单输入参数函数EventBtn:

#include 
#include 
#include 
#include 
#include 
#include "button.h"

constexpr int SCREEN_WIDTH = 640;
constexpr int SCREEN_HEIGHT = 640;

void EventBtn(MyUI::BUTTON_MSG _btnMsg) {
	if (MyUI::BUTTON_MSG::MOUSE_LDOWN == _btnMsg) {
			std::cout << "鼠标在按钮内按下了左键" << std::endl;
	}
	else if (MyUI::BUTTON_MSG::MOUSE_LUP == _btnMsg) {
			std::cout << "鼠标在按钮内释放了左键" << std::endl;
	}
}

int main() {
	initgraph(SCREEN_WIDTH, SCREEN_HEIGHT, EW_SHOWCONSOLE); //初始化窗口

	MyUI::BUTTON_DESC btnDesc = { "images/button1.png", 200, 200, 200, 60, 255 }; //按钮描述结构体
	MyUI::Button btn1(btnDesc); //构造按钮对象
	MyUI::BUTTON_MSG btnMsg; //用于接收按键消息的结构体对象

	BeginBatchDraw();

	while (1) {
		ExMessage msMsg;
		
		//easyx非阻塞获取鼠标消息,如果peekmessage返回值为false,说明没有消息
		//如果此时不清空msMsg,msMsg的值会是之前的消息值
		if (!peekmessage(&msMsg, EM_MOUSE, true)) {
			msMsg = { 0 }; //没消息就清空msMsg
		}

		btnMsg = btn1.Draw(msMsg);

		FlushBatchDraw();

		Sleep(16); //程序休眠16毫秒
		cleardevice(); //16毫秒后清空窗口中的内容
	}

	EndBatchDraw();

	_getch();
	closegraph(); //关闭窗口

	return 0;
}

结果都一样:

截至目前,我们将Button类进行了简单的实现,它已经可以派上用场了。

提示:接下来的内容会涉及到函数指针和map容器,不懂这些知识的同学可以选择跳过。

更加细节的做法

接下来我们将在Button类添加一个c++的map容器对象成员eventMap_:

std::map eventMap_;

它用来为每个按钮对象的每个消息提供一个映射,这个映射是void(*)()类型的函数指针。
(原本可以使用一些例如模板的技术,但秉承由浅入深的原则,就限定为单一类型),也就是说,我们使用返回类型为void,参数列表为空的函数作为这个消息的响应函数。

button.h

//button.h

#pragma once //#pragma once是一个比较常用的C/C++预处理指令,只要在头文件的最开始加入这条预处理指令,使头文件不会被重复包含。

#include 
#include 

class IMAGE; //声明IMAGE类
struct ExMessage; //声明MOUSEMSG结构体

namespace MyUI {
	
	enum class BUTTON_MSG : short {
		NOMSG = 0, //鼠标与按钮无关联
		MOUSE_IN, //鼠标位于按钮内
		MOUSE_LDOWN, //鼠标左键按下
		MOUSE_LUP, //鼠标左键抬起
		MOUSE_RDOWN, //鼠标右键按下
		MOUSE_RUP, //鼠标右键抬起
	};
	
	//Button描述结构体
	struct BUTTON_DESC {
		LPCTSTR pImgFile; //图片路径
		int posx; //图片初始x坐标
		int posy; //图片初始y坐标
		unsigned int width; //图片宽度
		unsigned int height; //图片高度
		unsigned int transparency; //图片透明度
	};

	class Button {
	public:

		Button(const BUTTON_DESC& _btnDesc);
		Button(const Button& _btn) = delete; //禁用默认拷贝构造函数
		Button& operator = (const Button& _btn) = delete; //禁用默认赋值拷贝运算符
		~Button();

		void Draw(const ExMessage& _msMsg); //绘制
		bool BindEvent(BUTTON_MSG _btnMsg, void(*_event)()); //为按钮消息绑定处理函数

	private:
		bool DoEvent(BUTTON_MSG _btnMsg); //执行事件

	protected:
		IMAGE* pImg_; //指向图像对象的指针
		BUTTON_DESC btnDesc_; //按钮属性
		std::map eventMap_; //消息映射表

	};
}

button.cpp

//button.cpp

#include "button.h"
#include 
#include 
#pragma comment(lib, "MSIMG32.lib")

namespace MyUI {
	Button::Button(const BUTTON_DESC& _btnDesc) : btnDesc_(_btnDesc) {
		pImg_ = new IMAGE;
		loadimage(pImg_, _btnDesc.pImgFile, _btnDesc.width, _btnDesc.height);
	}

	Button::~Button() {
		delete pImg_;
	}

	void Button::Draw(const ExMessage& _msMsg) {
		HDC imgDC = GetImageHDC(pImg_); //获取图像设备上下文句柄

		BUTTON_MSG bm = BUTTON_MSG::NOMSG;

		//通过坐标值判断鼠标是否在按钮内
		if (_msMsg.x >= btnDesc_.posx && _msMsg.x <= (btnDesc_.posx + btnDesc_.width) &&
			_msMsg.y >= btnDesc_.posy && _msMsg.y <= (btnDesc_.posy + btnDesc_.height)) 
		{
			if (WM_LBUTTONDOWN == _msMsg.message) {
				bm = BUTTON_MSG::MOUSE_LDOWN;
			} 
			else if (WM_LBUTTONUP == _msMsg.message) {
				bm = BUTTON_MSG::MOUSE_LUP;
			}
			else {
				bm = BUTTON_MSG::MOUSE_IN;
			}
		}

		DoEvent(bm);

		// 结构体的第三个成员表示额外的透明度,0 表示全透明,255 表示不透明。
		BLENDFUNCTION bf = { AC_SRC_OVER, 0, btnDesc_.transparency, AC_SRC_ALPHA };

		// 使用AlphaBlend函数实现半透明贴图
		AlphaBlend(
			GetImageHDC(NULL), //设备上下文句柄
			btnDesc_.posx, //绘制的x坐标
			btnDesc_.posy,  //绘制的y坐标
			btnDesc_.width, //在所选设备中绘制图像的宽度
			btnDesc_.height, //在所选设备中绘制图像的高度
			imgDC, //图像设备上下文句柄
			0, 0, //绘制图像起点坐标
			btnDesc_.width, //绘制图像的宽度
			btnDesc_.height, //绘制图像的高度
			bf
		);
	}

	bool Button::BindEvent(BUTTON_MSG _btnMsg, void(*_event)()) {
		//检查_btnMsg是否是BUTTON_MSG的枚举值
		if (_btnMsg < BUTTON_MSG::NOMSG || _btnMsg > BUTTON_MSG::MOUSE_RUP) {
			return false;
		}
		
		eventMap_[_btnMsg] = _event;
	}

	bool Button::DoEvent(BUTTON_MSG _btnMsg) {
		try {
			eventMap_.at(_btnMsg)(); //如果不存在_btnMsg这个key,抛出out_of_range异常
			return true;
		}
		catch (std::out_of_range e) {
			return false;
		}
	}
}

BindEvent方法用来绑定消息处理函数,DoEvent方法是私有成员,在Draw方法里被调用。

main.cpp

#include 
#include 
#include 
#include 
#include 
#include "button.h"

constexpr int SCREEN_WIDTH = 640;
constexpr int SCREEN_HEIGHT = 640;

void EventLBtnDown() {
	std::cout << "左键按下" << std::endl;
}

void EventLBtnUp() {
	std::cout << "左键抬起" << std::endl;
}

int main() {
	initgraph(SCREEN_WIDTH, SCREEN_HEIGHT, EW_SHOWCONSOLE); //初始化窗口

	MyUI::BUTTON_DESC btnDesc = { "images/button1.png", 200, 200, 200, 60, 255 }; //按钮描述结构体
	MyUI::Button btn1(btnDesc); //构造按钮对象
	MyUI::BUTTON_MSG btnMsg; //用于接收按键消息的结构体对象

	//绑定消息处理函数
	btn1.BindEvent(MyUI::BUTTON_MSG::MOUSE_LDOWN, EventLBtnDown);
	btn1.BindEvent(MyUI::BUTTON_MSG::MOUSE_LUP, EventLBtnUp);

	BeginBatchDraw();

	while (1) {
		ExMessage msMsg;

		//easyx非阻塞获取鼠标消息,如果peekmessage返回值为false,说明没有消息
		//如果此时不清空msMsg,msMsg的值会是之前的消息值
		if (!peekmessage(&msMsg, EM_MOUSE, true)) {
			msMsg = { 0 }; //没消息就清空msMsg
		}

		btn1.Draw(msMsg);

		FlushBatchDraw();

		Sleep(16); //程序休眠16毫秒
		cleardevice(); //16毫秒后清空窗口中的内容
	}

	EndBatchDraw();

	_getch();
	closegraph(); //关闭窗口

	return 0;
}

可以看到,我们为按钮消息绑定了我们自定义的处理函数。运行结果截图:

至此,此Button类可以基本投入使用。
还有一些效果如动态按钮(鼠标移入、点击都会产生效果)的实现会在以后的内容中为大家介绍。

文章持续更新中!
求点赞、收藏!欢迎在评论区留言,有问必答!
作者水平有限,如果有误,欢迎指正!
编译环境:Visual Studio 2019、Easyx_20220116

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/873176.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号