c/c++游戏编程之Easyx图形库基础(一)
c/c++游戏编程之Easyx图形库基础(二)
c/c++游戏编程之Easyx图形库基础(三)
- 新建button头文件和源文件
- 创建命名空间MyUI
- 禁用拷贝构造函数和赋值拷贝运算符
- 定义Draw函数
- 制作一张简单的按钮图片
- 绘制按钮,验证返回值
- 更加细节的做法
阅读本节内容建议掌握的前置知识:多文件编程。
这节内容我们介绍如何利用easyx来实现一个Button(按钮)类。
如图所示,在上一节的项目中分别新建头文件button.h,源文件button.cpp。
button.h:
//button.h #pragma once //#pragma once是一个比较常用的C/C++预处理指令,只要在头文件的最开始加入这条预处理指令,使头文件不会被重复包含。 #includeclass 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" #includenamespace 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
你可以像这样通过判断按钮返回值作出响应操作(这里是分别调用EventBtnDown和EventBtnUp函数)。
如下,也可以做成一个单输入参数函数EventBtn:
#include#include #include #include #include
结果都一样:
截至目前,我们将Button类进行了简单的实现,它已经可以派上用场了。
更加细节的做法提示:接下来的内容会涉及到函数指针和map容器,不懂这些知识的同学可以选择跳过。
接下来我们将在Button类添加一个c++的map容器对象成员eventMap_:
std::mapeventMap_;
它用来为每个按钮对象的每个消息提供一个映射,这个映射是void(*)()类型的函数指针。
(原本可以使用一些例如模板的技术,但秉承由浅入深的原则,就限定为单一类型),也就是说,我们使用返回类型为void,参数列表为空的函数作为这个消息的响应函数。
button.h
//button.h #pragma once //#pragma once是一个比较常用的C/C++预处理指令,只要在头文件的最开始加入这条预处理指令,使头文件不会被重复包含。 #include#include
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
可以看到,我们为按钮消息绑定了我们自定义的处理函数。运行结果截图:
至此,此Button类可以基本投入使用。
还有一些效果如动态按钮(鼠标移入、点击都会产生效果)的实现会在以后的内容中为大家介绍。
文章持续更新中!
求点赞、收藏!欢迎在评论区留言,有问必答!
作者水平有限,如果有误,欢迎指正!
编译环境:Visual Studio 2019、Easyx_20220116



