c/c++游戏编程之用Easyx封装按钮
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;
}
你可以像这样通过判断按钮返回值作出响应操作(这里是分别调用EventBtnDown和EventBtnUp函数)。
如下,也可以做成一个单输入参数函数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封装按钮相关推荐
- python之torchlight使用_python游戏编程之pgzero使用介绍
Pgzero是在pygame基础上封装的一个简化版本软件包,使得在python环境下进行游戏编程更加简单.适合于入门学习者. 怎么用 开发一款简单的小游戏,我们可能会立刻想到以下几个要素: 1. 创建 ...
- 我的u3d游戏编程之路
因为最近进行求职的缘故,需要一个地方来show自己的所学所用.并非所有的求职网站都有相应的作品展示区域,因此选择了在csdn完成作品的展示.在这里只展示部分在我编程过程中完成的难点问题.有些东西也只是 ...
- 球球大作战Java编写_Unity经典游戏编程之:球球大作战
版权声明: 本文原创发布于博客园"优梦创客"的博客空间(网址:http://www.cnblogs.com/raymondking123/)以及微信公众号"优梦创客&qu ...
- Unity经典游戏编程之:球球大作战
版权声明: 本文原创发布于博客园"优梦创客"的博客空间(网址:http://www.cnblogs.com/raymondking123/)以及微信公众号"优梦创客&qu ...
- Libgdx游戏编程之Touchpad摇杆控制角色行走
先上效果: 以下素材来源网络,人物只有4向行走,遥感的图片就没有打包了,人物行走的用GDX Texture Packer打成atlas文件. 创建touchpad的代码 Touchpad.Touchp ...
- 看完知乎轮子哥的编程之路,我只想说,收下我的膝盖...
点击上方"Datawhale",选择"星标"公众号 第一时间获取价值内容 作者:vczh 来源:https://dwz.cn/sWwZoQEl vczh,本名陈 ...
- 知无涯,行者之路莫言终 [- 编程之路2018 -]
零.前言 2017年标签:"海的彼岸,有我未曾见证的风采" 2018年标签:"海的彼岸,吾在征途" 2019年标签:"向那些曾经无法跨越的鸿沟敬上-- ...
- 【专业造轮子】 一位大神的编程之路,让我大吃一惊
vczh,本名陈梓瀚,因知乎的个人信息介绍上写有 "专业造轮子",所以江湖人称 "轮子哥". vczh 大学时代就在微软实习,毕业后即加入微软.开始时是在微软上 ...
- 异步编程之Promise(2):探究原理
异步编程系列教程: (翻译)异步编程之Promise(1)--初见魅力 异步编程之Promise(2):探究原理 异步编程之Promise(3):拓展进阶 异步编程之Generator(1)--领略魅 ...
最新文章
- Spring Boot中如何扩展XML请求和响应的支持
- 如何去应付你的上司给你一个变化无常的需求?
- java向指定文件写入内容
- C语言深度剖析书籍学习记录 第二章 符号
- python内置json模块_python的常用内置模块之序列化模块json
- 《Python数据挖掘:概念、方法与实践》一1.3 在数据挖掘中使用哪些技术
- VRay5材质库下载慢、快速安装的解决办法
- MySQL数据库锁机制
- C语言编程>第二十三周 ② 请补充fun函数,该函数的功能是:交换数组a中最大和最小两个元素的位置,结果重新保存在原数组中,其它元素位置不变。注意数组a中没有相同元素。
- html2canvas实现网页局部存为图片和打印
- ubuntu安装deb文件包
- Python爬虫包 BeautifulSoup 递归抓取
- python文件怎么另存为_python+pywin32处理另存为弹出框保存文件
- VIII openstack(2)
- 于的繁体字有几种写法_于字的意思、于的繁体字、于的笔顺笔画、于字部首和繁体字于的意思...
- 20189200余超 2018-2019-2 移动平台应用开发实践作项目代码分析
- Python自动玩俄罗斯方块小游戏
- 我的十几年技术总结(一):从疯狂到无奈
- C++ 游戏开发(二)见缝插针小游戏
- springboot启动失败
热门文章
- linux sudo也显示权限不够,重定向时权限不够问题解决
- 《三国演义》与“项目管理”——从诸葛亮的锦囊妙计再谈团队管理
- 7-19 阶梯电价 (8 分)
- DTOJ#5203. 小 T 与灵石
- Agora SDK 在Android中的使用(在线视频通话)| 掘金技术征文
- 手把手教你在Hexo中使用Github贡献日历(以Next主题为例)
- 前端资源(css,js,图片,接口等)加载过程
- 排序(Sort)知识点归纳
- Css视觉格式化模型
- MFC中的GetDlgItem