Chapter 207 · 综合实战

实战:文件管理器

TreeView + ListView + Menu + Breadcrumb:双栏文件浏览器。

207

项目概述

构建文件管理器(类 Windows Explorer):
• 目录树:TreeView 左侧导航(懒加载子目录)
• 文件列表:ListView 右侧内容(图标/详情视图)
• 右键菜单:Menu(新建/重命名/删除/复制)
• 面包屑:Breadcrumb 路径导航
• 工具栏:Toolbar(前进/后退/上级/刷新)

技术栈

模块XUI API
目录树xuiTreeViewCreate(懒加载/展开回调)
文件列表xuiListViewCreate(虚拟化/多选)
右键菜单xuiMenuCreate(Popup 层)
面包屑xuiBreadcrumbCreate
工具栏xuiToolbarCreate

编译本章示例

gcc -O2 -I. -o ch207.exe ch207_main.c src\xui_*.c build\xge.lib -lm -lws2_32 -liphlpapi -lgdi32 -luser32 -lshell32 -lopengl32 -lole32 -lwinmm -lavrt
.\ch207.exe

核心代码:目录树懒加载

// TreeView 展开回调(懒加载子目录)
void OnTreeExpand(xui_widget pTree,
    int iNode, void* pUser)
{
    const xui_tree_view_node_t* pNode =
        xuiTreeViewGetNodeById(pTree, iNode);
    const char* sPath = (const char*)pNode->pUser;

    // 扫描子目录,构建节点数组
    dir_entry_t* pDirs = ScanDirs(sPath);
    int iCount = CountDirs(pDirs);
    xui_tree_view_node_t* arrNodes = BuildNodes(pDirs, iNode, iCount);
    xuiTreeViewSetNodes(pTree, arrNodes, iCount);
}

// 点击目录 → 更新文件列表
void OnTreeSelect(xui_widget pTree,
    int iNode, void* pUser)
{
    const xui_tree_view_node_t* pNode =
        xuiTreeViewGetNodeById(pTree, iNode);
    const char* sPath = (const char*)pNode->pUser;

    file_entry_t* pFiles = ScanFiles(sPath);
    int iCount = CountFiles(pFiles);
    const char** arrNames = BuildNameArray(pFiles, iCount);
    xuiListViewSetItems(pFileList, arrNames, iCount);

    // 更新面包屑
    xuiBreadcrumbClearItems(pCrumb);
    AddPathSegments(pCrumb, sPath); // 逐段 AddItem
}

完整代码

将以下代码保存为 ch207_main.c,编译后运行即可看到文件管理器框架(目录树 + 文件列表):

#include "xge.h"
#include "xui.h"

static xui_context g_pCtx;

static int onFrame(void* pUser) {
    (void)pUser;
    xgeClear(0x1e1e2e);
    xui_vec2_t pos;
    xgeMouseGet(&pos.x, &pos.y);
    xuiInputPointerMove(g_pCtx, pos.x, pos.y);
    if (xgeMouseDown(XGE_MOUSE_LEFT))
        xuiInputPointerDown(g_pCtx, pos.x, pos.y, 0);
    else
        xuiInputPointerUp(g_pCtx, pos.x, pos.y, 0);
    xuiUpdate(g_pCtx, xgeGetDelta());
    xuiLayout(g_pCtx);
    xuiRender(g_pCtx, NULL, NULL, 0);
    return XGE_OK;
}

int main(void) {
    xge_desc_t d = {0};
    d.iWidth = 900; d.iHeight = 600;
    d.sTitle = "ch207 - File Manager";
    d.iFlags = XGE_INIT_VSYNC;
    xgeInit(&d);
    xuiCreate(&g_pCtx);
    extern const xui_proxy_t tXgeProxy;
    xui_proxy_t tXgeProxy = xuiProxyXge();
xuiSetProxy(g_pCtx, &tXgeProxy);
    xuiSetViewportSize(g_pCtx, 900, 600);

    xui_widget pRoot;
xuiPanelCreate(g_pCtx, &pRoot, NULL);
    xuiWidgetSetRect(pRoot, (xui_rect_t){0, 0, 900, 600});

    /* 目录树(左侧) */
    xui_widget pTree;
xuiTreeViewCreate(g_pCtx, &pTree, NULL);
xuiWidgetAddChild(pRoot, pTree);
    xuiWidgetSetRect(pTree, (xui_rect_t){0, 36, 240, 564});
    xuiWidgetSetRect(pTree, (xui_rect_t){0, 0, 240, 564});

    /* 文件列表(右侧) */
    xui_widget pList;
xuiListViewCreate(g_pCtx, &pList, NULL);
xuiWidgetAddChild(pRoot, pList);
    xuiWidgetSetRect(pList, (xui_rect_t){240, 36, 660, 564});
    xuiWidgetSetRect(pList, (xui_rect_t){0, 0, 660, 564});

    /* 工具栏 */
    xui_widget pBar;
xuiPanelCreate(g_pCtx, &pBar, NULL);
xuiWidgetAddChild(pRoot, pBar);
    xuiWidgetSetRect(pBar, (xui_rect_t){0, 0, 900, 36});
    xuiWidgetSetRect(pBar, (xui_rect_t){0, 0, 900, 36});

    xgeRun(onFrame, NULL);
    xuiDestroy(g_pCtx);
    xgeUnit();
    return 0;
}

代码讲解

  • TreeView:左侧目录树,展开时懒加载子目录,避免一次性加载全部。
  • ListView:右侧文件列表,大目录启用虚拟化渲染。
  • 联动:点击目录树节点 → 更新文件列表 + 面包屑路径。
  • 右键菜单:Popup 层弹出上下文菜单(复制/粘贴/删除)。

要点与陷阱

  • 懒加载:目录树仅加载一级,展开时动态加载子目录。
  • 虚拟化:大目录(万文件)ListView 必须虚拟化。
  • 异步:网络驱动器/大目录扫描放后台线程。
  • 拖拽:文件拖拽移动/复制(Drag 系统)。

进阶练习

  1. 双栏对比:两个 ListView 并排,拖拽复制文件。
  2. 搜索:Input 实时过滤文件名(模糊匹配)。
实战:文件管理器效果
图:实战:文件管理器渲染效果