HiEasyX 快速入门¶
在这之前请您熟悉 EasyX 的操作以及代码编写,因为 HiEasyX 是一个基于 EasyX 的全面扩展库。
如果具备一定 EasyX 基础,可以直接入手 快速基础,快速掌握 HiEasyX 基础模块。
准备 HiEasyX¶
配置库¶
请确保您的项目已经配置好了 HiEasyX 库。如果没有,请单击以下按钮
包含此库¶
只需要加上此头文件
#include "HiEasyX.h"
命名空间¶
HiEasyX 在代码中使用 HiEasyX
命名空间,缩写 hiex
,兼容旧版命名空间 EasyWin32
。
警告
请不要偷懒在程序中加入 using namespace hiex
,因为这 可能 会导致重名错误。
正确用法:在 HiEasyX 函数前面加上 hiex::
即可。
库全局设置¶
在 HiDef.h
中有控制库全局设置的宏定义,可以自行设置,此处不展开。
在 Ver 0.2.1 及更早发布的版本中
HiEasyX 默认在 Release 模式下启用程序开场动画,如需关闭,请在 HiDef.h 中设置相关宏。
窗口篇:HiWindow 窗口模块¶
我们先从一个简单示例开始:
#include "HiEasyX.h" // 包含 HiEasyX 头文件
int main()
{
initgraph(); // 初始化窗口
BEGIN_TASK(); // (不同于 EasyX)启动任务,标识开始绘制
circle(320, 240, 100); // 画圆
END_TASK(); // (不同于 EasyX)完成绘制,结束任务
FLUSH_DRAW(); // (不同于 EasyX)将绘制内容刷新到屏幕
getmessage(EM_KEY); // 任意键退出
closegraph(); // 关闭窗口
return 0;
}
接下来我们为您解释如何使用 HiEasyX。
创建绘图窗口¶
由于 HiEasyX 完全重写了 EasyX 的绘图窗口实现,所以可以支持创建多窗口、拉伸窗口,也支持自定义窗口过程函数。
在 HiEasyx 中,创建、管理窗口的模块名为 HiWindow
。
创建窗口的正确方式:
// 方法 1:直接使用 initgraph,它实际上被宏定义为 HiEasyX 的窗口创建函数
initgraph(640, 480);
// 方法 2:调用 HiEasyX 的窗口创建函数
hiex::initgraph_win32(640, 480);
// 方法 3:使用 HiEasyX 的窗口类创建窗口
hiex::Window wnd(640, 480);
// 也可以这样使用窗口类创建窗口
hiex::Window wnd;
wnd.Create(640, 480);
创建窗口时还有一些可选参数,例如窗口名称、窗口属性、过程函数、父窗口句柄,等等。详情请转到进阶教程 快速基础/自定义窗口样式 。
如果想要创建多个窗口,再次调用创建窗口函数即可。
想使用原生 EasyX ?
在 HiDef.h
中定义 _NATIVE_EASYX_
宏后,initgraph
函数将创建原生的 EasyX 窗口。但是,这也意味着不再支持 HiEasyX 的许多扩展功能。
确定窗口是否还存在¶
EasyX 的用户可能早已习惯不判断绘图窗口是否还存在,因为在 EasyX 中,窗口一旦被关闭,将自动退出程序。
但是 HiEasyX 在 EasyX 的基础上给您更多的选择。
如果使用 initgraph
创建窗口,则所有窗口被关闭后,程序将自动退出。
如果使用 hiex::initgraph_win32
或 hiex::Window
创建窗口,则不会自动退出程序。
对于第二种情况,您可以随时使用 hiex::isAnyWindow()
检测是否还存在任何窗口,然后再选择是否退出程序。
如果想检测某一窗口是否存在,可以使用 hiex::isAliveWindow()
或 hiex::Window::isAlive()
。
如果想设置所有窗口关闭时,程序自动退出,可以调用一次 AutoExit()
,即可。
IMAGE* 的空指针¶
在原生 EasyX 中,(IMAGE*)(nullptr)
代表着绘图窗口的图像指针。但是 HiWindow
创建的不是原生 EasyX 窗口,所以不支持 (IMAGE*)(nullptr)
。
诸如以下函数都默认会传入 IMAGE* pImg = NULL
:
GetImageBuffer
SetWorkingImage
GetImageHDC
等等。
使用这些函数时,都应该传入具体的图像指针。您可以使用 GetWoringImage()
或 GetWindowImage()
获取窗口的图像指针。
我遇到了无法打印 png 图片至绘图窗口的问题
如果您透明绘图部分的代码是使用了如下代码:
DWORD *dst = GetImageBuffer(); // GetImageBuffer()函数,用于获取绘图设备的显存指针,EASYX自带
DWORD *draw = GetImageBuffer();
DWORD* dst = GetImageBuffer(GetWorkingImage()); // GetImageBuffer()函数,用于获取绘图设备的显存指针,EASYX自带
DWORD* draw = GetImageBuffer(GetWorkingImage());
GetWorkingImage()
才是指向 HiEasyX 窗口的,留空则表示原 EasyX 窗口。通俗来讲,就是绘制错了窗口。
活动窗口的概念¶
由于 HiWindow
支持多窗口,所以操作窗口时要指定目标操作窗口。而 HiEasyX 操作多窗口的逻辑和 EasyX 中的 SetWorkingImage()
类似,也就是在操作某个窗口前,将这个窗口设置为活动窗口,然后再对其进行操作。
可以通过 hiex::SetWorkingWindow()
设置当前活动窗口,同时,当前工作绘图对象 WorkingImage
也会被设置到活动窗口的 IMAGE 对象。
窗口任务¶
由于 HiWindow
支持多窗口和窗口拉伸,所以会导致绘制冲突问题。为了协调冲突,需要在调用 EasyX 绘图函数前,需要标记开启一个窗口任务。
BEGIN_TASK()
为当前活动窗口开启窗口任务
BEGIN_TASK_WND()
设置某个窗口为活动窗口,再为它开启窗口任务
END_TASK()
结束窗口任务,它必须和上述的两个宏之一配套,因为宏内含有不成对的大括号,将宏配套使用才能使大括号匹配。
上面的宏可以这样展开:
if (hiex::SetWorkingWindow(_YourWindowHandle_))
{
if (hiex::BeginTask())
{
// 窗口任务内的代码
hiex::EndTask();
}
}
调用 getmessage()
一系列消息获取函数时,无需启动窗口任务,因为它们可以指定获取哪个窗口的消息,不会产生冲突。
注意
尽量将不必要在窗口任务中执行的代码移出窗口任务代码块,因为它们可能会导致窗口任务耗时过长,窗口消息就不能及时被处理,进而引发窗口卡顿或假死。(例如实现延时绘图效果时,Sleep
语句不应当放在窗口任务内)
此外,在两个窗口任务之间插入适当的间隙也很有必要,例如在无限循环的绘制中插入 Sleep
语句,这样同时也能降低 CPU 占用率。
双缓冲机制¶
HiEasyX 强制使用双缓冲,所以无需再调用 EasyX 的 BeginBatchDraw()
系列函数。
EasyX 原生的 FlushBatchDraw()
和 EndBatchDraw()
函数都被宏定义为刷新窗口函数( hiex::RedrawWindow()
)
HiEasyX 刷新双缓冲的机制是:每次窗口任务结束时,也就是调用 hiex::EndTask();
或者 END_TASK();
时,都会标记需要刷新双缓冲,然后在窗口接受到重绘消息时,再刷新双缓冲。
备注
调用 hiex::EndTask();
或者 END_TASK();
时,也可以传入参数指定是否标记需要刷新双缓冲。
这种刷新双缓冲的机制叫做自动刷新双缓冲,因为每次窗口任务结束都自动标记了需要刷新双缓冲,不需要手动去刷新。当然,也可以通过 hiex::EnableAutoFlush
函数关闭自动刷新双缓冲。
如果关闭了自动刷新双缓冲,那么就需要你调用 hiex::FlushDrawing
函数来手动刷新双缓冲(这个函数必须在窗口任务内调用),这个函数也可以只刷新局部的双缓冲。
注意
由于支持窗口拉伸,缓冲区 IMAGE
对象在窗口拉伸时会自动调整大小。如果您保存了缓冲区 IMAGE
对象的显存指针,则一定要检测窗口是否被拉伸(使用 hiex::isWindowSizeChanged()
或 hiex::Window::isSizeChanged()
),然后更新显存指针。
消息事件¶
由于 HiEasyX 的窗口是在 HiWindow 中重新实现的,所以 ExMessage 的消息队列也是重新实现的,它现在已经完美兼容 EasyX 的 ExMessage 消息队列了。
所以在 HiEasyX 中,获取鼠标、键盘消息和在 EasyX 中是一样的。由于 HiEasyX 支持多窗口,所以在 HiEasyX 中可能还需要额外指定需要获取哪个窗口的消息。
自定义窗口过程函数¶
为了维护 HiWindow 的正常运行,自定义窗口函数并不是使用 Win32 API 设置窗口的过程函数,您应该使用 hiex::SetWndProcFunc()
或 hiex::Window::SetProcFunc()
。
自定义的过程函数的签名和普通的 Win32 过程函数相同,唯一的区别就是,返回 DefWindowProc()
时,改为返回 HIWINDOW_DEFAULT_PROC
宏标志。
下面是一段自定义过程函数的示例代码:
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_PAINT:
BEGIN_TASK_WND(hWnd);
circle(100, 100, 70);
END_TASK();
break;
case WM_CLOSE:
DestroyWindow(hWnd);
break;
case WM_DESTROY:
// TODO: 在此处释放申请的内存
PostQuitMessage(0);
break;
default:
return HIWINDOW_DEFAULT_PROC; // 标识使用默认消息处理函数继续处理
// 若要以默认方式处理,请勿使用此语句
//return DefWindowProc(hWnd, msg, wParam, lParam);
break;
}
return 0;
}
创建托盘¶
这个无需多说,看一个很简单的示例代码即可。
#include "HiEasyX.h"
#define IDC_A 101
#define IDC_B 102
void OnTray(UINT id)
{
BEGIN_TASK();
switch (id)
{
case IDC_A:
outtextxy(100, 100, L"A");
break;
case IDC_B:
outtextxy(100, 100, L"B");
break;
}
END_TASK();
FLUSH_DRAW();
}
int main()
{
hiex::Window wnd;
wnd.Create();
wnd.CreateTray(L"Tray Name");
HMENU hMenu = CreatePopupMenu();
AppendMenu(hMenu, MF_STRING, IDC_A, L"选项 A");
AppendMenu(hMenu, MF_SEPARATOR, 0, NULL); // 分隔符
AppendMenu(hMenu, MF_STRING, IDC_B, L"选项 B");
wnd.SetTrayMenu(hMenu); // 设置菜单
wnd.SetTrayMenuProcFunc(OnTray); // 设置菜单响应函数
hiex::init_end(); // 阻塞等待所有窗口关闭
DestroyMenu(hMenu); // 销毁菜单,释放内存
return 0;
}
运行此示例代码,将会在托盘中创建您的程序图标。只要在托盘菜单里面点击某一项,程序就会作出反应。
自定义程序图标¶
HiEasyX 默认为程序加载 HiEasyX 图标。在 EasyX 中,只要在 Visual Studio 项目中加入图标资源,程序就会自动加载您的图标。
在 HiEasyX 中,您加入图标资源后,还需要在第一次创建窗口前调用一次 hiex::SetCustomIcon()
,即可。
下面列举两种在 Visual Studio 中添加图标资源的方法:
在资源视图中右键资源文件 -> 添加 -> 资源 -> 导入,选择图标并确定。
在项目中创建 resource.h 和 【项目名】.rc。
在 resource.h 中添加:
#define IDI_ICON1 101
在 【项目名】.rc 中添加:
#include "resource.h"
IDI_ICON1 ICON "icon.ico" /* 修改为您的图标文件路径 */
然后可以使用如下示例代码测试:
#include "resource.h"
#include "HiEasyX.h"
int main()
{
// 在创建窗口前设置图标
hiex::SetCustomIcon(MAKEINTRESOURCE(IDI_ICON1), MAKEINTRESOURCE(IDI_ICON1));
hiex::Window wnd;
wnd.Create();
hiex::init_end();
return 0;
}
即可自定义图标。
自定义窗口样式¶
如果您想改变窗口样式,例如取消最大化按钮,禁止用户拉伸窗口,您可以使用 Win32 API SetWindowLong
函数,这需要一些 Win32 知识。
还有更简便的方式,直接使用 hiex::SetWindowStyle()
或 hiex::Window::SetStyle()
。
HiEasyX 可以使用一下函数用于快速设置窗口样式
EnableResizing
设置是否允许窗口拉伸
EnableSystemMenu
设置是否启用系统标题栏按钮
EnableToolWindowStyle
设置是否启用窗口的工具栏样式
等等。更多请参见此文档和 HiWindow.h
。
绘图篇:HiCanvas 绘图模块¶
概念¶
画布(hiex::Canvas
)是对 EasyX 绘图函数的封装和扩展。它的使用方法和 IMAGE
对象一样,不同的是,使用画布绘制时不需要 SetWorkingImage
,可以直接调用对象方法进行绘制,而且它支持透明通道。
图像块(hiex::ImageBlock
)是 hiex::Canvas
的扩展,它保存了画布的位置,透明通道信息,可以更方便地存储在图层中。
图层(hiex::Layer
)中存储有若干个图像块,图层的透明度可以叠加到所有图像块上。
场景(hiex::Scene
)中存储有若干个图层,以及一些特殊图层。渲染整个场景时,可以使图层按次序渲染。
使用 Canvas 绘制¶
您可以创建一个画布对象,然后直接调用它的成员方法进行绘制。它们和 EasyX 原生绘图函数名称很像,区别仅在于它们使用驼峰命名法。
此外,Canvas 还提供一些更方便的绘制方式。例如:调用 Canvas 的绘图函数时可以选择直接设置绘制颜色,直接操作显存绘制或获取像素,支持透明通道的图片加载、缩放、旋转,直接设置字体名称(而不必设置字体大小)、字符(串)的绘制角度,格式化输出文本,等等。
示例代码:
#include "HiEasyX.h"
int main()
{
hiex::Window wnd(640, 480); // 创建窗口
hiex::Canvas canvas(60, 60); // 创建画布对象
canvas.Circle(30, 30, 30); // 绘制画布
if (wnd.BeginTask()) // 启动窗口任务
{
putimage(100, 100, &canvas); // 将画布内容输出到窗口
wnd.EndTask(); // 结束窗口任务
wnd.Redraw(); // 重绘窗口
}
hiex::init_end(); // 阻塞等待窗口关闭
return 0;
}
使用 Canvas 绑定窗口或 IMAGE 对象¶
Canvas 还可以和 HiWindow 更好地融合,可以直接将窗口和画布绑定,这样,在绘制时甚至不需要启动窗口任务,直接调用画布的绘制方法即可。例如:
#include "HiEasyX.h"
int main()
{
hiex::Window wnd(640, 480); // 创建窗口
hiex::Canvas canvas; // 创建画布对象
wnd.BindCanvas(&canvas); // 将窗口和画布绑定
canvas.Circle(130, 130, 30); // 绘制画布对象
wnd.Redraw(); // 重绘窗口
hiex::init_end(); // 阻塞等待窗口关闭
return 0;
}
也可以将一个 Canvas 对象绑定到已有的 IMAGE 对象,让 Canvas 为其绘制,只需要:
canvas.BindToImage(_Your_Image_Pointer_);
一旦画布绑定窗口,或者绑定到其他 IMAGE
对象,请不要再使用 &canvas
的方式获取画布指针,请使用 hiex::Canvas::GetImagePointer()
,这很重要。
应用场景、图层、Alpha 通道¶
它们都很易于使用,您可以看下面的一个例子:
透明通道 - 小球示例(1)
透明通道 - 小球示例(2)
示例中,透明小球在窗口中运动,在碰到边界时反弹。
为了缩短篇幅,请您查看 示例源代码
控件篇:使用更完善的 Win32 UI 库¶
HiEasyX 封装了常用 Win32 控件,这个控件模块被称为 HiSysGUI。
目前支持的控件类型 (此文档可能更新不及时) :
- 分组框
- 静态文本(图像)
- 按钮
- 复选框
- 单选框
- 编辑框
- 组合框
一般情况下,这些控件已经足够。而且,您也可以自定义窗口过程函数,直接调用其它 Win32 控件。
体验 HiSysGUI 的极速构建¶
请看这个例子:
#include "HiEasyX.h"
int main()
{
hiex::Window wnd(300, 200);
hiex::SysButton btn(wnd.GetHandle(), 100, 85, 100, 30, L"Button");
hiex::init_end();
return 0;
}
创建按钮
没错!使用按钮就是这么容易。
还可以在按钮中添加图片,像这样:
#include "HiEasyX.h"
int main()
{
hiex::Window wnd(300, 200);
hiex::SysButton btn(wnd.GetHandle(), 100, 85, 100, 30, L"Button");
// 创建画布,绘制绿色填充圆
hiex::Canvas canvas(30, 22);
canvas.Clear(true, 0xe1e1e1);
canvas.SolidCircle(15, 10, 10, true, GREEN);
// 添加按钮图像
btn.Image(true, &canvas, true);
hiex::init_end();
return 0;
}
添加按钮图片
提示
代码中使用了 Canvas 绘制按钮图像,如果使用 IMAGE 同样可以。
如果要响应按钮消息,可以使用 RegisterMessage
方法,或者使用 GetClickCount
函数获取按钮点击次数。
例如,使用 GetClickCount
函数获取按钮点击次数:
#include "HiEasyX.h"
int main()
{
hiex::Window wnd(300, 200);
hiex::SysButton btn(wnd.GetHandle(), 100, 85, 100, 30, L"Button");
// 窗口存在时,程序才保持运行
while (wnd.isAlive())
{
// 如果按钮的点击次数不为 0,说明用户已点击按钮
if (btn.GetClickCount())
{
// 处理点击消息
}
Sleep(50);
}
return 0;
}
或者注册点击消息:
#include "HiEasyX.h"
void OnBtn()
{
// 在此处理点击消息
}
int main()
{
hiex::Window wnd(300, 200);
hiex::SysButton btn(wnd.GetHandle(), 100, 85, 100, 30, L"Button");
btn.RegisterMessage(OnBtn); // 注册点击消息
hiex::init_end();
return 0;
}
其余控件的使用方式大同小异,可以看看相应的头文件介绍。此处再举一例,创建编辑框。
像这样:
#include "HiEasyX.h"
int main()
{
hiex::Window wnd(300, 200);
hiex::SysEdit edit; // 编辑框
// 预设样式为支持多行输入,因为有的控件样式必须在创建之前就指定
edit.PreSetStyle(true, false, true, true, true, true);
edit.Create(wnd.GetHandle(), 10, 10, 280, 180, L"Multiline Edit Box\r\n\r\nEdit here");
// 设置编辑框字体
edit.SetFont(24, 0, L"微软雅黑");
hiex::init_end();
return 0;
}
创建编辑框
加上按钮,获取文本:
#include "HiEasyX.h"
int main()
{
hiex::Window wnd(300, 200);
// 编辑框
hiex::SysEdit edit;
edit.PreSetStyle(true, false, true, true);
edit.Create(wnd.GetHandle(), 10, 10, 280, 140, L"Type here~");
edit.SetFont(24, 0, L"微软雅黑");
// 按钮
hiex::SysButton btn;
btn.Create(wnd.GetHandle(), 190, 160, 100, 30,L"Submit");
while (wnd.isAlive())
{
// 按下按钮时,弹窗显示输入的文本
if (btn.isClicked())
MessageBox(wnd.GetHandle(), edit.GetText().c_str(), L"Submit", MB_OK);
Sleep(50);
}
return 0;
}
获取编辑框文本
还可以设置文字颜色、背景颜色、密码框、左中右对齐方式、仅数字输入、禁用控件,等等,不一一列举。这个教程不可能面面俱到,也有可能更新延迟,如果您想具体了解每个控件,可以看看它们的声明,此处不再展开。
参阅 文档。
下面这个示例用到的控件比较全面,可以帮您更深入地了解 HiSysGUI:
控件示例
在此查看此示例的 源代码
常见问题整合¶
- 创建窗口后没有判断窗口是否被关闭
- 调用 EasyX 库函数没有启动窗口任务
- 在程序主循环中读取鼠标操作卡顿。可能是使用了
if
语句读取,应改为使用while
,一次读完所有消息 - 向 EasyX 库函数传入空的 IMAGE 指针来代指窗口画布,应使用
GetWorkingImage()
获取窗口画布地址 - 窗口响应卡顿,可能是因为窗口任务中存在不必要的延时代码(如
Sleep
语句)、耗时的计算、死循环等等,或在两个任务之间几乎无间隙 BEGIN_TASK()
有极小概率启动任务失败,如果失败,窗口任务中的代码将不会被执行。如果代码没有成功执行会对后续代码运行产生影响,则应当使用BEGIN_TASK()
宏的展开形式,判断是否启动任务成功
Release 模式的程序启动动画¶
HiEasyX 默认在 Release 模式下开启程序启动动画,此动画改编自慢羊羊的《艺术字系列:冰封的 EasyX》。如需关闭,请在 HiDef.h
中按照注释指示定义相应的宏取消开场动画。
结语¶
HiEasyX 还有一些实用但零碎的功能,在此恐不能详述。例如:
HiMacro.h
宏定义相关HiFunc.h
HiEasyX 常用杂项函数HiFPS.h
帧率相关HiDrawingProperty.h
保存绘图属性功能HiMouseDrag.h
更方便快捷地处理鼠标拖动消息HiMusicMCI.h
声音相关HiGif.h
动图相关
如您感兴趣,可以自行查阅头文件和文档。