目录
- 什么是 DirectUI?
- 为什么选择 DirectUI?
- 核心概念与原理
- 开发环境搭建
- 一个最简单的 DirectUI 程序(Hello World)
- 深入解析:控件、消息与绘制
- 实战案例:制作一个简单的登录界面
- 进阶学习与资源推荐
什么是 DirectUI?
DirectUI 的字面意思是“直接用户界面”,它并不是微软官方提供的某个 SDK 或库,而是一种界面设计思想和技术实现模式。
核心思想: 完全由开发者自己绘制界面,而不是使用操作系统提供的标准控件(如 Win32 的按钮、文本框),开发者通过截获和处理 Windows 消息(如 WM_PAINT, WM_MOUSEMOVE 等),在指定的窗口区域内使用 GDI/GDI+/DirectX 等图形 API 手动绘制出所有界面元素(按钮、编辑框、列表等),并处理所有用户交互。
一个形象的比喻:
- 标准 Win32 界面:就像你用乐高积木搭建房子,积木是现成的(标准控件),你只需要把它们拼在一起。
- DirectUI 界面:就像你用一块块黏土自己捏出房子的所有部件,然后上色、雕刻,完全由你掌控。
为什么选择 DirectUI?
DirectUI 的优势非常明显,这也是它被广泛应用的原因:
- 高度自定义外观:你可以实现任何你想要的设计,不受系统主题限制,打造独一无二、酷炫的界面。
- 优秀的性能:由于界面是直接绘制在窗口上的,没有标准控件的额外开销,尤其是在复杂界面和频繁重绘的场景下,性能通常更好。
- 统一的外观风格:在一个大型应用中,可以确保所有控件的外观、行为、动画效果完全一致,提供统一的用户体验。
- 丰富的动画效果:可以轻松实现平滑的过渡动画、悬浮效果、点击反馈等,让界面更生动。
它也有缺点:
- 开发复杂度高:每一个控件都需要自己实现,工作量巨大。
- 开发周期长:从零开始实现一个功能完善的控件库需要大量时间。
- 调试困难:需要手动处理各种边界情况(如焦点切换、键盘输入、拖拽等),调试起来比较繁琐。
DirectUI 通常用于对界面要求极高的专业软件,如:
- 安全软件(360、火绒等)
- 工业控制软件
- 金融交易软件
- 多媒体播放器
- 各类工具软件
核心概念与原理
理解 DirectUI 的工作原理至关重要,它主要围绕以下几个核心概念:
a. 窗口与控件
- 主窗口:一个标准的 Win32 窗口,作为所有控件的“画布”。
- 控件:不是真正的窗口(没有
WS_CHILD窗口样式),而是主窗口的一个逻辑区域,主窗口负责管理所有控件的位置、大小和状态。
b. 消息循环与分发
- 主窗口的消息循环:程序运行时,有一个标准的 Windows 消息循环 (
GetMessage->TranslateMessage->DispatchMessage)。 - 消息过滤与分发:当消息到达主窗口时,DirectUI 框架会先拦截它,如果是与控件相关的消息(如鼠标点击、键盘输入),框架会根据鼠标位置判断当前操作的是哪个控件,然后将消息“分发”给该控件进行处理。
c. 绘制
WM_PAINT消息:当窗口需要重绘时(如窗口大小改变、被其他窗口遮挡后重新显示),系统会发送WM_PAINT消息。OnPaint函数:DirectUI 框架会调用主窗口的OnPaint函数,在这个函数里,框架会遍历所有控件,根据它们当前的状态(正常、悬浮、按下、禁用等)和样式,调用各自的Draw方法进行绘制。- 双缓冲:为了防止绘制时出现闪烁,DirectUI 通常会使用双缓冲技术,即在内存中先绘制好整个界面,然后一次性将内存中的图像复制到屏幕上。
d. 控件树
- 界面上的所有控件通常以树状结构组织,一个窗口可能包含一个面板,面板里又包含一个标签、一个编辑框和一个按钮,这种层级关系有助于布局和消息传递。
开发环境搭建
DirectUI 不是一个独立的库,所以它的“搭建”主要是配置一个能够高效开发的 C++ 环境。
- 编译器:Visual Studio 是不二之选,推荐使用 Visual Studio 2025 或 2025,它们对 C++11/14/17 的支持非常好,这些现代 C++ 特性能极大简化 DirectUI 的开发。
- 图形库:
- GDI (Graphics Device Interface):Windows 自带的 2D 图形 API,最基础,无需额外安装。
- GDI+:GDI 的一个增强版,支持更丰富的图形功能(如渐变、路径、图像格式支持),推荐使用。
- Direct2D/DirectWrite:更现代、高性能的 2D 图形和文本 API,功能强大,但学习曲线稍陡。
- UI 框架/库:
- 从零开始:不推荐新手这样做,除非是为了学习原理。
- 使用开源 DirectUI 框架:这是最高效的方式,这些框架已经帮你实现了底层的消息循环、控件管理、绘制等繁琐工作,你可以专注于业务逻辑和界面设计。
- DuiLib:国内最著名、使用最广泛的 DirectUI 框架,资料多,社区活跃。本教程将主要以 DuiLib 为例进行讲解。
- DuilibEx:DuiLib 的一个分支或衍生版本,做了一些改进。
- QProperty:另一个优秀的 DirectUI 框架。
DuiLib 安装步骤:
- 从 DuiLib 的官方 GitHub 仓库下载源码:https://github.com/duilib/duilib
- 下载 DuiLib 的示例工程,这通常是最简单的方式。
- 在 Visual Studio 中打开示例工程(如
HelloDuiLib),直接编译运行即可,它会帮你配置好所有必要的库文件和头文件路径。
一个最简单的 DirectUI 程序(Hello World)
我们将使用 DuiLib 来创建一个最简单的窗口,上面显示一个“Hello, DirectUI!”的标签。
步骤:
- 创建一个基于对话框的 Win32 工程 (在 VS 中选择 "Windows Desktop Application")。
- 添加 DuiLib 库文件:将 DuiLib 源码中的
DuiLib.lib、DuiLib.dll以及include文件夹下的头文件复制到你的工程目录,并在 VS 中配置好包含目录和库目录。 - 编写代码:
main.cpp
#include "stdafx.h"
#include "UIlib.h" // DuiLib 的主头文件
// 使用 DuiLib 的命名空间
using namespace DuiLib;
// 应用程序类
class CDuiFrameWnd : public CWindowWnd, public INotifyUI
{
public:
virtual LPCTSTR GetWindowClassName() const { return _T("DUIMainFrame"); }
virtual UINT GetClassStyle() const { return CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW; }
// 窗口初始化
virtual void OnFinalMessage(HWND hWnd)
{
delete this;
}
// 处理通知消息(例如按钮点击)
virtual void Notify(TNotifyUI& msg)
{
if (msg.sType == _T("click"))
{
// 这里可以处理按钮点击事件
}
}
// 窗口初始化时创建控件
virtual void InitWindow()
{
// 加载 XML 界面描述文件
CDialogBuilder builder;
CControlUI* pRoot = builder.Create(_T("test.xml"), (UINT)0, NULL, &m_pm);
ASSERT(pRoot && _T("Failed to parse XML"));
AttachDialog(pRoot);
// 设置窗口标题
m_pm.SetWindowSize(this, 800, 600);
}
virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LRESULT lRes = 0;
if (uMsg == WM_CREATE)
{
m_pm.Init(m_hWnd);
return lRes;
}
if (m_pm.MessageHandler(uMsg, wParam, lParam, lRes))
{
return lRes;
}
return CWindowWnd::HandleMessage(uMsg, wParam, lParam);
}
CPaintManagerUI m_pm; // DuiLib 的核心,负责管理所有控件和消息
};
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lpCmdLine, int nCmdShow)
{
CDuiFrameWnd* pFrame = new CDuiFrameWnd();
pFrame->Create(NULL, _T("Hello DuiLib"), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE);
pFrame->CenterWindow();
pFrame->ShowWindow(true);
return 0;
}
test.xml (放在你的工程根目录下)
<Window size="800,600">
<VerticalLayout bkcolor="0xFFFFFFFF">
<Label text="Hello, DirectUI!" textcolor="0xFF000000" font="2" />
</VerticalLayout>
</Window>
代码解析:
- CDuiFrameWnd:我们创建的窗口类,继承自
CWindowWnd(提供基本窗口功能)和INotifyUI(处理控件通知)。 - CPaintManagerUI m_pm:这是 DuiLib 的心脏,它负责管理窗口内的所有控件、处理消息和绘制。
InitWindow():在窗口创建完成后调用,这里我们加载了test.xml文件来描述界面。test.xml:DuiLib 使用 XML 文件来描述界面布局。<Window>是根容器,<VerticalLayout>表示一个垂直布局的容器,<Label>就是一个标签控件,这种方式将界面逻辑与界面描述分离,非常方便。
编译并运行,你将看到一个显示 "Hello, DirectUI!" 的窗口。
深入解析:控件、消息与绘制
a. 控件
DuiLib 提供了丰富的控件,如:
Label: 标签Button: 按钮Edit: 编辑框ListContainerEx: 列表框TabLayout: 选项卡Container: 容器控件
你可以通过 XML 或 C++ 代码来创建和配置它们。
b. 消息处理
Notify(TNotifyUI& msg):这是处理控件交互的核心,当控件有事件发生(点击、选择等),DuiLib 会封装一个TNotifyUI对象,并调用此函数。msg.sType:事件类型,如click,selectchanged。msg.pSender:发送事件的控件的指针,你可以通过dynamic_cast将其转换为具体的控件类型(如CButtonUI*)。msg.lParam/msg.wParam:附加参数。
示例:处理按钮点击
在 Notify 函数中添加:
if (msg.sType == _T("click"))
{
CButtonUI* pButton = static_cast<CButtonUI*>(msg.pSender);
if (pButton && pButton->GetName() == _T("myButton"))
{
// 获取按钮的文本
CDuiString strText = pButton->GetText();
// ... 执行你的逻辑 ...
}
}
在 XML 中给按钮命名:
<Button name="myButton" text="Click Me" />
c. 绘制与皮肤
DuiLib 的外观由“皮肤”控制,皮肤通常是一个或多个图片文件(.png),以及一个描述皮肤如何应用的 XML 文件(.xml)。
- 控件的属性(如
bkimage,normalimage,hotimage,pushedimage)指定了在不同状态下使用皮肤的哪一部分。 - 一个按钮的
normalimage指定了它正常状态的背景图,hotimage指定了鼠标悬浮时的背景图。
实战案例:制作一个简单的登录界面
目标:创建一个包含用户名、密码输入框和登录按钮的界面。
login.xml
<Window size="400,300" caption="0,0,0,32" roundcorner="5,5">
<VerticalLayout bkcolor="0xFFE8E8E8">
<!-- 标题 -->
<Label text="用户登录" textcolor="0xFF333333" font="20" height="40" align="center" />
<!-- 用户名输入框 -->
<HorizontalLayout height="40" padding="10,10,10,10">
<Label text="用户名:" width="60" align="center" textcolor="0xFF666666" />
<Edit name="editUsername" width="200" />
</HorizontalLayout>
<!-- 密码输入框 -->
<HorizontalLayout height="40" padding="10,10,10,10">
<Label text="密码:" width="60" align="center" textcolor="0xFF666666" />
<Edit name="editPassword" width="200" password="true" /> <!-- password="true" 表示密码显示为点 -->
</HorizontalLayout>
<!-- 登录按钮 -->
<HorizontalLayout height="50" padding="10,10,10,10">
<Button name="btnLogin" text="登 录" width="200" height="40" />
</HorizontalLayout>
</VerticalLayout>
</Window>
修改 main.cpp 中的 Notify 函数
virtual void Notify(TNotifyUI& msg)
{
if (msg.sType == _T("click"))
{
if (msg.pSender->GetName() == _T("btnLogin"))
{
CEditUI* pEditUser = static_cast<CEditUI*>(m_pm.FindControl(_T("editUsername")));
CEditUI* pEditPass = static_cast<CEditUI*>(m_pm.FindControl(_T("editPassword")));
if (pEditUser && pEditPass)
{
CDuiString strUser = pEditUser->GetText();
CDuiString strPass = pEditPass->GetText();
if (strUser.IsEmpty() || strPass.IsEmpty())
{
MessageBox(m_hWnd, _T("用户名和密码不能为空!"), _T("提示"), MB_OK | MB_ICONWARNING);
}
else
{
// 这里可以添加登录逻辑
MessageBox(m_hWnd, (CDuiString(_T("登录成功!\n用户名: ")) + strUser).GetData(), _T("成功"), MB_OK | MB_ICONINFORMATION);
}
}
}
}
}
关键点:
- 在 XML 中给控件设置了
name属性,如name="editUsername"。 - 在 C++ 代码中,使用
m_pm.FindControl(_T("控件名"))来查找并操作控件。 - 通过
CEditUI*指针调用GetText()获取编辑框内容。
进阶学习与资源推荐
当你掌握了基础后,可以深入学习以下内容:
- 自定义控件:继承 DuiLib 的基础控件类(如
CControlUI),重写其Paint方法,实现完全自定义的控件。 - 动画系统:DuiLib 内置了简单的动画支持,可以实现控件位置、大小、透明度等的平滑过渡。
- 复杂布局:深入学习
HorizontalLayout,VerticalLayout,TileLayout等布局管理器的使用。 - 数据绑定:将控件与数据模型关联,实现数据驱动的界面更新。
推荐资源:
- DuiLib 官方 GitHub:https://github.com/duilib/duilib - 源码、文档和示例是学习的最佳资料。
- DuiLib 教程博客:搜索 "DuiLib 教程",有很多中文博客文章详细讲解了各种控件的使用和技巧。
- DuiLib 论坛/社区:寻找活跃的社区,遇到问题时可以求助。
- 《Windows界面程序设计——基于DuiLib》:有一本专门讲解 DuiLib 的书籍,内容比较系统。
这份教程希望能为你打开 DirectUI 的大门,学习 DirectUI 的关键在于理解其原理,并多看、多练、多模仿优秀开源项目的实现,祝你学习顺利!
