Chapter 185 · XUI 专业控件

MessageList 消息列表

聊天/日志消息列表:节点模型、流式追加、折叠、事件回调。

185

概念

MessageList 是消息/聊天列表控件:
• Node:消息节点(ID/发送者/文本/头像/时间戳)
• 流式追加:AppendNodeText 逐字追加(AI 打字效果)
• 折叠:长消息可折叠/展开
• 事件:点击/链接/按钮交互回调
适用:聊天界面、AI 对话、日志查看器、通知中心。

API 速查

函数说明
int xuiMessageListCreate(pCtx, ppW, pDesc)创建消息列表
int xuiMessageListSetNodes(pW, pNodes, iCount)批量设置节点
int xuiMessageListAddNode(pW, pNode)追加节点
int xuiMessageListUpdateNodeText(pW, sId, sText)更新文本
int xuiMessageListAppendNodeText(pW, sId, sText)追加文本
int xuiMessageListSetNodeCollapsed(pW, sId, bCollapsed)折叠/展开
int xuiMessageListClear(pW)清空
int xuiMessageListSetEvent(pW, onEvent, pUser)事件回调

编译本章示例

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

示例:聊天界面

xui_message_list_desc_t desc = { 0 };

xui_widget pMsgList;
xuiMessageListCreate(pCtx, &pMsgList, &desc);

// 添加用户消息
xui_message_node_t msg = { 0 };
msg.sId = "msg_001";
msg.sSender = "User";
msg.sText = "Hello, XGE!";
msg.bSelf = 1;
xuiMessageListAddNode(pMsgList, &msg);

// AI 回复(流式追加)
msg.sId = "msg_002";
msg.sSender = "Assistant";
msg.sText = "";
msg.bSelf = 0;
xuiMessageListAddNode(pMsgList, &msg);

// 模拟打字效果
xuiMessageListAppendNodeText(pMsgList, "msg_002", "Hi! ");
xuiMessageListAppendNodeText(pMsgList, "msg_002", "How can ");
xuiMessageListAppendNodeText(pMsgList, "msg_002", "I help?");

// 事件回调
xuiMessageListSetEvent(pMsgList, OnMsgEvent, NULL);

完整代码

将以下代码保存为 ch185_main.c,编译后运行即可看到聊天消息列表效果:

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

static xui_context g_pCtx;
static xui_widget g_pMsgList;
static int g_iTick = 0;

static int onFrame(void* pUser) {
    (void)pUser;
    xgeClear(0x1A1A2EFF);
    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);

    // 模拟流式输出
    g_iTick++;
    if (g_iTick == 60)
        xuiMessageListAppendNodeText(g_pMsgList, "msg_002", "Hi! ");
    if (g_iTick == 90)
        xuiMessageListAppendNodeText(g_pMsgList, "msg_002", "How can ");
    if (g_iTick == 120)
        xuiMessageListAppendNodeText(g_pMsgList, "msg_002", "I help you?");

    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 = 600; d.iHeight = 700;
    d.sTitle = "ch185 - MessageList";
    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, 600, 700);

    xui_message_list_desc_t mdesc = { 0 };
    xuiMessageListCreate(g_pCtx, &g_pMsgList, &mdesc);

    // 用户消息
    xui_message_node_t msg = { 0 };
    msg.sId = "msg_001";
    msg.sSender = "User";
    msg.sText = "Hello, XGE!";
    msg.bSelf = 1;
    xuiMessageListAddNode(g_pMsgList, &msg);

    // AI 回复(流式)
    msg.sId = "msg_002";
    msg.sSender = "Assistant";
    msg.sText = "";
    msg.bSelf = 0;
    xuiMessageListAddNode(g_pMsgList, &msg);

    xui_widget pRoot = xuiGetRootWidget(g_pCtx);
    xuiWidgetAddChild(pRoot, g_pMsgList);
    xuiWidgetSetRect(g_pMsgList, (xui_rect_t){0, 0, 600, 700});

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

代码讲解

  • MessageListCreate:创建消息列表控件,适合聊天/AI 对话界面。
  • AddNode:添加消息节点,bSelf=1 显示在右侧(自己),0 显示在左侧。
  • AppendNodeText:流式追加文本,模拟打字效果,每次追加触发增量渲染。
  • 帧计数:用 g_iTick 在不同帧追加文字,模拟网络流式响应。

要点与陷阱

  • AppendNodeText:适合流式输出,每次追加触发增量渲染。
  • ID 唯一:sId 用于定位节点,Update/Append/Collapse 均依赖 ID。
  • Clear:切换对话时调用 Clear 清空历史。
  • 自动滚动:新消息到达时自动滚动到底部(可配置)。

进阶练习

  1. AI 聊天:Input + MessageList + 流式 Append 实现对话界面。
  2. Markdown 渲染:消息文本支持富文本格式。
MessageList 消息列表效果
图:MessageList 消息列表渲染效果