> 文章列表 > 使用 EasyX 实现 UI 原理教程(章三 基础 UI 程序的结构与基础按钮)

使用 EasyX 实现 UI 原理教程(章三 基础 UI 程序的结构与基础按钮)

使用 EasyX 实现 UI 原理教程(章三 基础 UI 程序的结构与基础按钮)

一篇适合普通大一大二学生(指需要使用 EasyX 构建普通小界面来完成小作业)阅读的文章,在阅读完本文以后,你们已经具备了构造一个适合自己作业中的 UI 库的能力,而往后的所有章节,难度将会提高,所学习的知识对于你用 EasyX 来实现小作业(甚至是部分大作业)没有任何帮助,只会徒增学习成本,特此做出提醒。

正文

在开始第三章的学习之前,我们先来理解几个概念:

  1. 父与子

 图上所示,Form 1 窗口下有一 LineText 3 控件与 Button 2 控件,假设 LineText 3 与 Button 2 是 Form1 下一控件,那么我们便说 Form 1 是 LineText 3 与 Button 2 的“父亲”,相对的 LineText 3 与 Button 2 我们叫做“儿子”,专业点说 LineText 3 与 Button 2 是 Form 1 的子控件,Form 1 是 Button 2 与 LineText 3 的父窗口。

  1. 事件(Event)

如果你有略微接触过 Win32 Api 的话,你肯定知道“事件”的概念,事件,简单来说就是用户的任意交互(例如:点击鼠标,移动鼠标,按下键盘),或者是一些提示消息,你可以把你的程序想象成人体的神经系统,你的函数 / 类就是神经元细胞,函数 / 类之间的通信就是基于事件(Event)。

在前两章中,我们已经学会了触发器以及绘图单元的相关知识,所以今天我们来正式进入 UI 的大门,首先我们要了解的是 UI 的基础结构。

我们需要了解 UI 是如何处理鼠标消息并且将它传递给控件的,不知道读者是否还记得我们第一章里边是如何实现触发器的 is_trigger 函数与 main 函数的,忘记了也没关系我把代码放在下面。

// rectangle_trigger 的 is_trigger 函数
bool is_trigger(ExMessage message)
{// 判断鼠标是否在矩形范围内return (message.x >= x && message.x <= x + width &&message.y >= y && message.y <= y + height);
}
int main()
{initgraph(640, 480, SHOWCONSOLE);setfillcolor(WHITE);// 绘制矩形和圆fillrectangle(40, 40, 90, 90);solidellipse(120, 40, 160, 80);// 测试触发器rectangle_trigger  rect_trigger(40, 40, 50, 50);geometry_trigger   geom_trigger(shape_geometry::CIRCLE, {120 - 20, 40 - 20, 40, 40, 20});ExMessage message;while (true){getmessage(&message);if (message.message == WM_LBUTTONDOWN){if (rect_trigger.is_trigger(message) == true){printf("■");}if (geom_trigger.is_trigger(message) == true){printf("●");}}}_getch();return 0;
}

当时在第一章我并没有解释为什么传参要是 ExMessage 与为什么要这样子用 getmessage 去写 main,其实这就是我们今天的内容:UI 的结构。

其实对于鼠标事件的获取,应该是使用 getmessage 一直去等消息,如果 main 获得了消息,后消息类型的判断然后传递给控件,整个流程从原理上来讲是可以用单线程来实现的。

接下来我们以实现一个基础控件:按钮,为例子来实现一个基础的 UI,这里我分成几部分供读者阅读。

★一切的基石 - Object

一般来说,因为 UI 库里的各种控件,大多数时候他们都是殊途同归的,所以为了方便我们在继承派升上下文章,我们需要所有类(无论控件还是判断器还是绘图单元)都要继承一个基础的类:base_object,base_object 要求需要拥有几个虚函数 mouse_pressed_event(),mouse_move_event(),mouse_released_event()负责用户或默认触发方法,且需要一个 process_event 处理 main 传入的 ExMessage,除此之外还需要一些属性如父子关系等,这里我给大家绘制了一个结构体供大家参考。

 有了上面这一个大概的梳理,我们能很轻易地将 base_object 写出来,并且将它封装于 object.h 中。

注意:base_object 应继承于 geometry_trigger,所以图中的 x, y,height,width 不用在代码中重复定义。


//      	  object.h
#pragma once#include "trigger.h"#include <vector>// 一切基石 base_object
class base_object :public geometry_trigger
{
private:// 焦点是否曾经在控件上bool used_to_on = false;// 父对象base_object*			  parent = nullptr;// 子对象std::vector<base_object*> child_objects;public:base_object(base_object* init_parent = nullptr): parent(init_parent){}public:// 处理事件消息 (一般来说没有必要修改)virtual void event_process(ExMessage message){if (is_trigger(message) == true){switch (message.message){case WM_LBUTTONDOWN:{used_to_on = true;mouse_pressed_event();}default:{mouse_move_event();}}}else{if (used_to_on == true){mouse_released_event();}}}// 用户自定消息处理函数virtual void mouse_pressed_event(){}virtual void mouse_released_event(){}virtual void mouse_move_event(){}
};

利用先前已经写好了的触发器,我们非常快速地就完成了 base_object ,接下来进入下一小节的内容

★一些细节上的调整

如果我们直接开始写按钮的话,现在我们的代码还是无法正常运行,我们需要做如下几个调整:

  1. 在 <cell.h> 中将 image_cell 改为继承自 base_object
#include "object.h"class image_cell :public base_object
{
// ...
}

为什么?因为到时候我们写按钮控件的时候将需要继承自 image_cell 并重写 draw 函数。

  1. 在 <trigger.h> 中,最上方加入如下的代码:
// Anti easyx _WINVER def
#ifndef WINVER
#	define WINVER 0x0500
#endif

这句代码是什么意思呢?主要的意思是设置当前程序的最低兼容版本,为什么要这么干呢?主要原因是因为 EasyX 为了兼容老旧系统会将 WINVER 定义为 0x0400 而这样的结果会导致部分将来要用到的功能无法正常使用。

★正式开始写 pushbutton 控件

万事俱备,经过前面缕清过逻辑以及写完了 trigger 还有绘图单元相关代码,相信对于你来说写一个 pushbutton 已经不是一艰难事了,这里我也不多言,直接贴我的代码:

#pragma once#include "object.h"
#include "cell.h"#include <functional>// 自动创建矩形的掩码图
IMAGE* get_fillrectangle_mask(int width, int height)
{IMAGE* result = new IMAGE(width, height);SetWorkingImage(result);setfillcolor(BLACK);fillrectangle(0, 0, width, height);SetWorkingImage();return result;
}class pushbutton :public image_cell
{
public:std::function<void()> mouse_on_clicked;public:void draw(){mask = get_fillrectangle_mask(width, height);setfillcolor(WHITE);fillrectangle(x, y, width + x, height + y);}public:pushbutton(base_object* init_parent = nullptr){parent = init_parent;}public:void mouse_pressed_event(){mouse_on_clicked();}void mouse_move_event(){// 设置鼠标样式 有代入感SetClassLongPtr(GetHWnd(), GCLP_HCURSOR, reinterpret_cast<LONG_PTR>(IDC_HAND));}void mouse_released_event(){// 设置鼠标样式 有代入感SetClassLongPtr(GetHWnd(), GCLP_HCURSOR, reinterpret_cast<LONG_PTR>(IDC_ARROW));}
};

可以看到,程序正常运行且结果正常

 到此如果已经全部明白,可以开始自己尝试实现一个功能简陋的 UI 了~ 是不是跃跃欲试了呢?给大家留个例题:写出一个能够现实文字,自定义背景样式的按钮。如果你对文章内容了解透彻,这绝对将不会花费你超过 20 分钟的时间。

关注B站号:小鱼快来啊,+q粉丝群:725022484 免费领取300G精品编程资料

免费领取300g精品编程资料icon-default.png?t=N176https://jq.qq.com/?_wv=1027&k=HTkOJ4WE