📊 vlistEx 虚表增强版

完整架构分析 · 版本 23.0 · 2026-07-17
aardio Win32 虚表 高性能 高定制

📌 概述

vlistEx 是基于 Windows SysListView32 深度扩展的高性能虚表组件, 专为 aardio 设计。通过 LVS_OWNERDATA 虚表机制,支持百万级数据流畅显示, 并提供远超原生控件的丰富功能。

⚡ 高性能
虚表 · 百万级数据流畅滚动
🎨 高定制
20+ 绘制事件 · 像素级控制
🧩 多类型
10 种列类型 · 图文混排
🌳 树形结构
多级树形列表 · 一键启用
📐 高级表头
多行 · 合并 · 图文混排
🔄 交互完整
编辑 · 拖拽 · 排序 · 合并
📋 数据操作
导入/导出 · 过滤 · 排序
🖼️ 资源管理
图片/字体缓存 · 自动清理

🏗️ 核心架构设计

📦 数据适配器
tableAdapter / customAdapter
解耦数据与显示
🧩 列类型系统
_COLTYPE 策略模式
10 种渲染方式
🖌️ 绘制流水线
6 阶段分步绘制
每步可拦截
🔗 元属性封装
_metaProperty
属性即方法

1. 数据适配器模式 (MVC)

通过 tableAdapter / customAdapter数据层显示层解耦。 表格仅通过适配器的 getItem(row, col) 获取数据,支持数组、字典表或完全自定义回调。

// 普通数组数据
var data = {
    { "张三", 28, "技术部" };
    { "李四", 34, "产品部" };
};
vlist.setTable(data, { "姓名", "年龄", "部门" });

// 字典表数据
var dict = {
    { name="张三"; age=28; dept="技术部" };
    { name="李四"; age=34; dept="产品部" };
};
vlist.setTable(dict, { "name", "age", "dept" });

2. 列类型系统 (_COLTYPE)

通过 setColumnType(col, type, ...) 为每列定义渲染行为:

代码 类型名称 说明 典型用途
-1 多行文本 自动换行,可配置垂直对齐 备注 · 描述 · 长文本
0 普通文本 默认类型,支持图文混排 姓名 · 编号 · 日期
1 下拉列表框 点击弹出下拉列表 枚举 · 分类 · 性别
2 图片 居中 / 缩放 / 填满 三种模式 头像 · 图标 · 缩略图
3 进度条 数值渲染为进度条,可自定义颜色 完成度 · 百分比 · 进度
4 图片开关 多状态切换,点击自动切换 启用/禁用 · 开关
5 图片按钮 带悬浮效果的按钮 编辑 · 删除 · 查看
6 纯自绘 完全由用户回调绘制 图表 · 仪表 · 复杂UI
7 完全自定义 基于模型绘制任意元素组合 混合布局 · 卡片式
8 复选框 勾选框,支持单选/多选模式 多选 · 标记 · 权限
// 设置第4列为复选框(状态列)
vlist.setColumnType(4, 8, 16, 0xFF2A7FAA, "在职", "离职", false);

// 设置第5列为进度条
vlist.setColumnType(5, 3, 0xEEEEEE, 0x4CAF50, true, 4, false);

// 设置第6列为图片按钮
vlist.setColumnType(6, 5, true, 0x2A7FAA, 0x1A5A7A, 0xFFFFFF, 0xFFFFFF);

3. 绘制流水线 (Rendering Pipeline)

NM_CUSTOMDRAW 分 6 阶段绘制,每阶段可触发回调拦截:

# 触发事件 绘制内容 控制方式 / 说明
1 onDrawCellBg 整个单元格背景(含边框区域) 可拦截 · 跳过默认
2 系统内置 复选框 · 树形指示器 系统绘制 · 不可拦截
3 onDrawCellRectBg 单元格有效区域背景(内边距以内) 可拦截 · 跳过默认
4 onDrawCellRectContent 文本 · 图片 · 进度条 · 开关 · 按钮 · 自定义 可拦截 · 跳过默认
5 系统内置 横线 · 竖线 · 行拖拽标记 系统绘制 · 不可拦截
6 onDrawCellBorderEnd 边框 / 装饰(收尾阶段) 可拦截 · 跳过默认
可拦截 返回 true 跳过默认绘制 系统绘制 内部自动完成,不提供拦截
// 自定义单元格背景
vlist.onDrawCellBg = function(row, col, hdc, rect, bkcolor, text) {
    if (row % 2 === 0) {
        ..gdi.fillRect(hdc, 0xF5F9FF, rect);  // 斑马纹
        return true;  // 跳过默认绘制
    }
}

// 自定义单元格内容
vlist.onDrawCellRectContent = function(row, col, hdc, rect, bkcolor, text, font, align, valign) {
    if (col === 3 && text === "技术部") {
        ..gdi.drawText(hdc, font, "🔧 " ++ text, rect, align | valign | 0x20);
        return true;
    }
}

4. 元属性封装 (_metaProperty)

利用 aardio 元表特性,将方法封装为直观属性: vlist.rowHeight = 30 等价于 vlist.setRowHeight(30)

// 属性方式(推荐)
vlist.rowHeight = 30;
vlist.headerHeight = 40;
vlist.padding = 6;
vlist.textColor = 0x222222;

// 方法方式(等价)
vlist.setRowHeight(30);
vlist.setHeaderHeight(40);
vlist.PADDING = 6;
vlist.font.color = 0x222222;

⭐ 优秀设计模式

🔹 好莱坞原则
20+ 回调事件,扩展无需修改核心代码
🔹 策略模式
列类型可互换,渲染策略独立于表格主体
🔹 适配器模式
统一数据接口,适配多种数据源
🔹 API 钩子
raw.apiHook 精细控制滚动条行为

🧩 功能模块详解

📐 基础配置

// 行高与表头
vlist.setRowHeight(28);
vlist.setHeaderHeight(36);

// 颜色主题
vlist.bkColor = 0xFFFFFF;
vlist.selectedBkColor = 0xCCE5FF;
vlist.selectedTextColor = 0x000000;
vlist.lineColorH = 0xDDDDDD;
vlist.lineColorV = 0xDDDDDD;

// 内边距
vlist.padding = 6;                    // 统一内边距
vlist.padding = { left=4; top=2; right=4; bottom=2 };  // 分别设置

// 启用全行选择
vlist.fullRow = true;

// 启用斑马纹
vlist.bkColor1 = 0xFFFFFF;
vlist.bkColor2 = 0xF5F8FC;

📋 列操作

// 设置列(字段名, 宽度, 对齐方式)
vlist.setColumns(
    { "姓名", "年龄", "部门", "状态", "进度" },
    { 100, 60, 120, 80, 100 },
    { 0, 1, 0, 1, 0 }  // 0左 1中 2右
);

// 单独设置列宽
vlist.setColumnWidth(1, 120);
vlist.setColumnWidth({1, 3, 5}, {120, 80, 100});

// 自动适配列宽
vlist.fitColWidth();                  // 全部列
vlist.fitColWidth(1, 20);            // 单列,附加20px
vlist.fitColWidth({1, 3}, 10);       // 多列

// 自动填满父容器宽度
vlist.fillParent(3);                 // 第3列自动填满剩余宽度

📊 数据绑定

// 设置数据(自动创建列)
vlist.setTable(data, fields);

// 追加数据
vlist.addTable(moreData);

// 获取数据
var allData = vlist.getTable();
var rowData = vlist.getRowData(3);
var cellText = vlist.getCellText(3, 2);

// 修改数据
vlist.setCellText(3, 2, "新值");
vlist.setRowData(3, { "张三", 30, "技术部" });

// 插入/删除行
vlist.insertRow(3, { "新员工", 25, "市场部" });
vlist.addRow({ "末尾员工", 40, "人事部" });
vlist.delRow(5);
vlist.delRow({2, 4, 6});

☑️ 选择与复选框

// 启用复选框(第一列)
vlist.checkBox.show = true;
vlist.checkBox.size = 16;

// 获取/设置选中行
var sel = vlist.getSelected();       // 返回 {行号数组, 数据数组}
vlist.setSelected(3);                 // 选中单行
vlist.setSelected({2, 4, 6});         // 选中多行
vlist.setSelected();                  // 取消全部选中

// 复选框操作
var checked = vlist.getChecked();   // 返回 {行号数组, 数据数组}
vlist.setChecked({1, 3, 5});          // 勾选指定行
vlist.setChecked();                   // 取消全部勾选

// 根据文本选中/勾选
vlist.setSelectedByText("技术部", 3);   // 在第三列查找"技术部"并选中
vlist.setCheckedByText("技术部", 3);    // 在第三列查找"技术部"并勾选

🌳 树形结构

// 启用树形结构
vlist.tree.show = true;
vlist.tree.spaceWidth = 20;

// 数据中设置层级(示例)
var treeData = {
    { name="总部"; ["@treeLevel"]=0 };      // 根节点
    { name=" 技术部"; ["@treeLevel"]=1 };    // 子节点
    { name="  前端组"; ["@treeLevel"]=2 };  // 孙节点
    { name="  后端组"; ["@treeLevel"]=2 };
    { name=" 产品部"; ["@treeLevel"]=1 };
};
vlist.setTable(treeData, { "name" });

// 展开/折叠(通过设置数据实现)
vlist.hideRow(2);                      // 隐藏指定行
vlist.hideRowAndSubRow(1);            // 隐藏行及其所有子行
vlist.isHideRow(2);                   // 判断行是否隐藏

🔗 合并单元格

// 合并行1-2,列1-2
vlist.addMerge(1, 1, 2, 2);

// 合并行3,列2-4
vlist.addMerge(3, 2, 3, 4);

// 取消合并
vlist.delMerge(1, 1);                 // 取消指定单元格所在的合并
vlist.clearAllMerges();               // 取消所有合并

// 判断/获取合并信息
var isMerged = vlist.isMerged(1, 1);
var mergeInfo = vlist.getMerge(1, 1); // 返回 {row1, col1, row2, col2}

📊 排序

// 启用列头点击排序
vlist.onSortColumn = true;

// 手动排序(按第2列,降序,数值模式)
vlist.sort(2, true, 2);              // convert=2 数值模式

// 按文本排序(使用 Windows 自然排序)
vlist.sort(1, false, 3, true);     // stringMode=true 自然排序

// 自定义排序函数
vlist.onSortColumn = function(col, desc) {
    if (col === 1) {
        // 自定义排序逻辑
        vlist.sort(col, desc, 3, true);
    }
};

🔍 搜索与过滤

// 查找(返回第一个匹配)
var result = vlist.find("张三", 1, 0, 1);  // 在第1列查找"张三"

// 查找全部
var all = vlist.find("技术", 1, 0, 3, true, true);  // 模糊匹配

// 过滤(保留匹配行)
vlist.filter("技术部", 3);              // 只显示第三列为"技术部"的行
vlist.filter("技术", 3, true);          // 模糊匹配
vlist.filter();                         // 取消过滤,恢复全部数据

// 高级过滤(支持 >, <, >=, <=, !=, include 等)
vlist.filterEx({
    { 3, "startWith", "技术" };        // 第3列以"技术"开头
    { 2, ">=", 25 };                    // 第2列 >= 25
}, true);                         // true=或条件,false=且条件

🔄 事件回调

// 点击事件
vlist.onClick = function(row, col, x, y, btnIdx) {
    winform.msgbox("点击了第" ++ row ++ "行第" ++ col ++ "列");
};

// 双击事件
vlist.onDblClick = function(row, col, x, y, btnIdx) {
    vlist.edit(row, col);              // 双击编辑
};

// 右键点击
vlist.onRClick = function(row, col, x, y, btnIdx) {
    vlist.setSelected(row);            // 右键选中行
};

// 行选择变化
vlist.onItemChanged = function(row) {
    statusBar.text = "当前选中: " ++ row;
};

// 单元格编辑
vlist.onEditBegin = function(row, col) {
    return col === 2;                  // 只允许编辑第2列
};

// 拖拽行
vlist.onDragRow = function(dragRow, dragList) {
    return true;                     // 允许拖拽
};
vlist.onDragRowEnd = function(dragList, targetRow) {
    // 拖拽结束处理
    return true;
};

🖼️ 图片与字体

// 添加图片到缓存
vlist.addImg("avatar", "/res/avatar.png", false, 32, 32);

// 单元格中使用图片(图文混排)
vlist.setCellText(1, 1, ' 张三');

// 添加字体
vlist.addFont("title", { name="微软雅黑"; h=16; color=0xCC0000 });
vlist.addFont("highlight", { name="宋体"; h=12; color=0x0066CC });

// 单元格中使用字体
vlist.setCellText(1, 1, '');

📁 CSV 导入导出

// 导出 CSV
vlist.saveToCsv("data.csv", true, true);  // 带表头,UTF-8

// 导出为字符串
var csvStr = vlist.saveToCsv();

// 导入 CSV
vlist.openCsv("data.csv", true, true);     // 带表头,重建列

// 从字符串导入
vlist.openCsv(csvStr, true, true);

📝 总结

vlistEx 是设计精巧、功能丰富的自定义表格控件,展现了对 Windows 编程和 aardio 语言的深刻理解。 通过 虚表适配器列类型系统丰富回调, 实现了高性能与高灵活性的统一。

设计充分体现 开闭原则(对扩展开放,对修改封闭), 可通过事件回调、列类型扩展、自定义适配器等方式实现各种定制需求。