c/c++游戏编程之Easyx图形库基础(一) EasyX基础
c/c++游戏编程之Easyx图形库基础(二) 绘制图片
c/c++游戏编程之Easyx图形库基础(三) 用Easyx封装按钮

文章目录

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

新建button头文件和源文件

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

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

button.h:

//button.h#pragma once //#pragma once是一个比较常用的C/C++预处理指令,只要在头文件的最开始加入这条预处理指令,使头文件不会被重复包含。#include <Windows.h>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_
/*
class xxx {
...
}
*/
#endif

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

创建命名空间MyUI

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

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

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

button.cpp:

//button.cpp#include "button.h"
#include <graphics.h>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 <Windows.h>
#include <iostream>
#include <graphics.h>
#include <conio.h>
#include <map>
#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;
}

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

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

#include <Windows.h>
#include <iostream>
#include <graphics.h>
#include <conio.h>
#include <map>
#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<BUTTON_MSG, void(*)()> eventMap_;

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

button.h

//button.h#pragma once //#pragma once是一个比较常用的C/C++预处理指令,只要在头文件的最开始加入这条预处理指令,使头文件不会被重复包含。#include <Windows.h>
#include <map>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<BUTTON_MSG, void(*)()> eventMap_; //消息映射表};
}

button.cpp

//button.cpp#include "button.h"
#include <graphics.h>
#include <stdexcept>
#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 <Windows.h>
#include <iostream>
#include <graphics.h>
#include <conio.h>
#include <map>
#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

c/c++游戏编程之用Easyx封装按钮相关推荐

  1. python之torchlight使用_python游戏编程之pgzero使用介绍

    Pgzero是在pygame基础上封装的一个简化版本软件包,使得在python环境下进行游戏编程更加简单.适合于入门学习者. 怎么用 开发一款简单的小游戏,我们可能会立刻想到以下几个要素: 1. 创建 ...

  2. 我的u3d游戏编程之路

    因为最近进行求职的缘故,需要一个地方来show自己的所学所用.并非所有的求职网站都有相应的作品展示区域,因此选择了在csdn完成作品的展示.在这里只展示部分在我编程过程中完成的难点问题.有些东西也只是 ...

  3. 球球大作战Java编写_Unity经典游戏编程之:球球大作战

    版权声明: 本文原创发布于博客园"优梦创客"的博客空间(网址:http://www.cnblogs.com/raymondking123/)以及微信公众号"优梦创客&qu ...

  4. Unity经典游戏编程之:球球大作战

    版权声明: 本文原创发布于博客园"优梦创客"的博客空间(网址:http://www.cnblogs.com/raymondking123/)以及微信公众号"优梦创客&qu ...

  5. Libgdx游戏编程之Touchpad摇杆控制角色行走

    先上效果: 以下素材来源网络,人物只有4向行走,遥感的图片就没有打包了,人物行走的用GDX Texture Packer打成atlas文件. 创建touchpad的代码 Touchpad.Touchp ...

  6. 看完知乎轮子哥的编程之路,我只想说,收下我的膝盖...

    点击上方"Datawhale",选择"星标"公众号 第一时间获取价值内容 作者:vczh 来源:https://dwz.cn/sWwZoQEl vczh,本名陈 ...

  7. 知无涯,行者之路莫言终 [- 编程之路2018 -]

    零.前言 2017年标签:"海的彼岸,有我未曾见证的风采" 2018年标签:"海的彼岸,吾在征途" 2019年标签:"向那些曾经无法跨越的鸿沟敬上-- ...

  8. 【专业造轮子】 一位大神的编程之路,让我大吃一惊

    vczh,本名陈梓瀚,因知乎的个人信息介绍上写有 "专业造轮子",所以江湖人称 "轮子哥". vczh 大学时代就在微软实习,毕业后即加入微软.开始时是在微软上 ...

  9. 异步编程之Promise(2):探究原理

    异步编程系列教程: (翻译)异步编程之Promise(1)--初见魅力 异步编程之Promise(2):探究原理 异步编程之Promise(3):拓展进阶 异步编程之Generator(1)--领略魅 ...

最新文章

  1. Spring Boot中如何扩展XML请求和响应的支持
  2. 如何去应付你的上司给你一个变化无常的需求?
  3. java向指定文件写入内容
  4. C语言深度剖析书籍学习记录 第二章 符号
  5. python内置json模块_python的常用内置模块之序列化模块json
  6. 《Python数据挖掘:概念、方法与实践》一1.3 在数据挖掘中使用哪些技术
  7. VRay5材质库下载慢、快速安装的解决办法
  8. MySQL数据库锁机制
  9. C语言编程>第二十三周 ② 请补充fun函数,该函数的功能是:交换数组a中最大和最小两个元素的位置,结果重新保存在原数组中,其它元素位置不变。注意数组a中没有相同元素。
  10. html2canvas实现网页局部存为图片和打印
  11. ubuntu安装deb文件包
  12. Python爬虫包 BeautifulSoup 递归抓取
  13. python文件怎么另存为_python+pywin32处理另存为弹出框保存文件
  14. VIII openstack(2)
  15. 于的繁体字有几种写法_于字的意思、于的繁体字、于的笔顺笔画、于字部首和繁体字于的意思...
  16. 20189200余超 2018-2019-2 移动平台应用开发实践作项目代码分析
  17. Python自动玩俄罗斯方块小游戏
  18. 我的十几年技术总结(一):从疯狂到无奈
  19. C++ 游戏开发(二)见缝插针小游戏
  20. springboot启动失败

热门文章

  1. linux sudo也显示权限不够,重定向时权限不够问题解决
  2. 《三国演义》与“项目管理”——从诸葛亮的锦囊妙计再谈团队管理
  3. 7-19 阶梯电价 (8 分)
  4. DTOJ#5203. 小 T 与灵石
  5. Agora SDK 在Android中的使用(在线视频通话)| 掘金技术征文
  6. 手把手教你在Hexo中使用Github贡献日历(以Next主题为例)
  7. 前端资源(css,js,图片,接口等)加载过程
  8. 排序(Sort)知识点归纳
  9. Css视觉格式化模型
  10. MFC中的GetDlgItem