杰瑞科技汇

directui 教程

目录

  1. 什么是 DirectUI?
  2. 为什么选择 DirectUI?
  3. 核心概念与原理
  4. 开发环境搭建
  5. 一个最简单的 DirectUI 程序(Hello World)
  6. 深入解析:控件、消息与绘制
  7. 实战案例:制作一个简单的登录界面
  8. 进阶学习与资源推荐

什么是 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++ 环境。

  1. 编译器:Visual Studio 是不二之选,推荐使用 Visual Studio 2025 或 2025,它们对 C++11/14/17 的支持非常好,这些现代 C++ 特性能极大简化 DirectUI 的开发。
  2. 图形库
    • GDI (Graphics Device Interface):Windows 自带的 2D 图形 API,最基础,无需额外安装。
    • GDI+:GDI 的一个增强版,支持更丰富的图形功能(如渐变、路径、图像格式支持),推荐使用。
    • Direct2D/DirectWrite:更现代、高性能的 2D 图形和文本 API,功能强大,但学习曲线稍陡。
  3. UI 框架/库
    • 从零开始:不推荐新手这样做,除非是为了学习原理。
    • 使用开源 DirectUI 框架:这是最高效的方式,这些框架已经帮你实现了底层的消息循环、控件管理、绘制等繁琐工作,你可以专注于业务逻辑和界面设计。
      • DuiLib:国内最著名、使用最广泛的 DirectUI 框架,资料多,社区活跃。本教程将主要以 DuiLib 为例进行讲解。
      • DuilibEx:DuiLib 的一个分支或衍生版本,做了一些改进。
      • QProperty:另一个优秀的 DirectUI 框架。

DuiLib 安装步骤:

  1. 从 DuiLib 的官方 GitHub 仓库下载源码:https://github.com/duilib/duilib
  2. 下载 DuiLib 的示例工程,这通常是最简单的方式。
  3. 在 Visual Studio 中打开示例工程(如 HelloDuiLib),直接编译运行即可,它会帮你配置好所有必要的库文件和头文件路径。

一个最简单的 DirectUI 程序(Hello World)

我们将使用 DuiLib 来创建一个最简单的窗口,上面显示一个“Hello, DirectUI!”的标签。

步骤:

  1. 创建一个基于对话框的 Win32 工程 (在 VS 中选择 "Windows Desktop Application")。
  2. 添加 DuiLib 库文件:将 DuiLib 源码中的 DuiLib.libDuiLib.dll 以及 include 文件夹下的头文件复制到你的工程目录,并在 VS 中配置好包含目录和库目录。
  3. 编写代码

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 官方 GitHubhttps://github.com/duilib/duilib - 源码、文档和示例是学习的最佳资料。
  • DuiLib 教程博客:搜索 "DuiLib 教程",有很多中文博客文章详细讲解了各种控件的使用和技巧。
  • DuiLib 论坛/社区:寻找活跃的社区,遇到问题时可以求助。
  • 《Windows界面程序设计——基于DuiLib》:有一本专门讲解 DuiLib 的书籍,内容比较系统。

这份教程希望能为你打开 DirectUI 的大门,学习 DirectUI 的关键在于理解其原理,并多看、多练、多模仿优秀开源项目的实现,祝你学习顺利!

分享:
扫描分享到社交APP
上一篇
下一篇