通过窗口消息与远程内存访问外部进程(兼容 64/32 位)的标准 SysListView32 列表视图控件。对象以 win.ui.ctrl.listview 为基础,重新实现了需要跨进程传递结构体、文本缓冲区的常用操作。
winex.desktop 就使用了这个库:winex.desktop.listview 是绑定到 Windows 桌面图标列表的 winex.ctrl.listview 对象。
构造参数是列表控件自身的窗口句柄,不是主窗口句柄、进程 ID 或 TreeView 的节点句柄。
import winex;
import winex.ctrl.listview;
import console;
// 按实际情况替换窗口标题。
// findExists 返回值 1 是父窗口句柄,返回值 2 才是匹配的控件句柄。
var hwndParent,hwndList = winex.findExists("目标窗口标题",,,"SysListView32");
if(!hwndList){
console.log("未找到目标列表控件");
return;
}
var lv = winex.ctrl.listview(hwndList);
console.log("项目数:",lv.count);
for row,text in lv.each(){
console.log(row,text);
}
lv.process.free(); // 只释放本次绑定持有的进程资源,不关闭外部窗口。
如果已经取得控件句柄,可以直接调用 winex.ctrl.listview(hwndList)。
窗口重新创建后句柄可能改变,需要重新查找、绑定。
调用方需要有权打开目标进程并读写其内存;目标以管理员身份运行时,通常也需要相应权限。
常用读写接口的行号、列号从 1 开始。
| 接口 | 说明 |
|---|---|
count |
项目总数 |
columnCount |
报表视图的列数,通过列表头获取;图标视图不一定有可用的列表头 |
getItemText(row,col=1,len=100) |
读取单个单元格的 Unicode 文本 |
each(i=0,flags,col) |
从指定项目之后开始遍历,依次返回行号、指定列文本;默认遍历全部项目、读取第一列 |
getItemRect(row,col,rc,code=0) |
获取项目或子项矩形;省略 col 取整项范围,省略 rc 自动创建矩形 |
getItemText 的 len 是包含末尾终结符空间的 UTF-16 单元数,不是 UTF-8 字节数。
默认值为 100,长文本应显式增大;当前实现不会自动扩容。
空文本返回 "",不能仅据此判断项目是否存在。
外部绑定的 getItemText 每次只读取一列,不支持用 col=-1 读取整行。
读取报表视图的全部列时,请逐列调用:
// lv 为已经绑定的 winex.ctrl.listview 对象。
var readRow = function(lv,row){
var cells = [];
var columns = lv.columnCount;
for(col=1;columns){
table.push(cells,lv.getItemText(row,col,260));
}
return cells;
};
矩形坐标相对于列表控件客户区,单位是运行时的设备像素。
可使用 win.toScreen(lv.hwnd,x,y) 转为屏幕坐标,不要直接当作屏幕坐标点击。
each():遍历全部项目,返回 (row,text)。each(0,,2):遍历全部项目,读取第二列。eachSelected(col):遍历选中项目,返回行号与指定列文本。selected:选中行号数组,适合多选列表。getSelection(i,col):查找起始行之后的下一个选中项,未找到返回 0。getFocused(col) / selIndex:当前内部焦点项的行号,未找到返回 0;焦点项不等同于全部选中项。findItem(str,start=-1,sub=true,wrap=true):按项目主文本查找,返回 1 起始的行号,未找到返回 0。sub 控制部分文本匹配,wrap 控制是否从头继续查找;它不是任意子项列查询接口。查找起点的例外:当前 findItem 将 start 原样传给 Win32。
start=-1 表示从首项开始;指定其他值时,它是 0 起始、排除自身的原生项目索引,不要与返回的 1 起始行号混用。
// 只定义操作函数;调用时传入自己允许修改选择状态的列表对象。
var selectByText = function(lv,text){
var row = lv.findItem(text,-1,false,false); // 精确匹配,从首项开始,不回绕。
if(!row) return 0;
lv.setSelected(row); // 设置选中状态,不保证清除其他行的选中状态。
lv.ensureVisible(row); // 滚动到该项。
return row;
};
setSelected(row,false) 取消指定行的选中状态;selIndex=row 设置内部焦点及选中状态。
这些操作会影响目标程序的界面,并可能触发目标程序自己的事件处理。
仅在明确允许修改的目标上使用这些接口,建议先在自己创建的测试列表中验证。
| 接口 | 说明 |
|---|---|
setItemText(text,row,col=1) |
修改指定单元格;显式传入有效行号 |
setItemText(["第一列","第二列"],row) |
从第一列开始批量写入该行,数组写法不使用第三个列号参数 |
addItem(text,ind,...) |
插入项目,可传多列文本数组;省略位置追加,返回新增行号 |
delItem(row) / clear() |
删除指定项 / 全部项目 |
setItemState(row,state=0,mask) |
按掩码设置状态;省略掩码使用 state,清除状态位时应显式指定掩码 |
getItemState(row,mask=0) |
按掩码读取状态,读取特定位时应显式传入掩码 |
getChecked(row) / setChecked(row,value=true) |
读取 / 修改复选框状态,仅适用于本来就支持复选框的列表 |
setItemPos(row,x,y) |
设置图标项在客户区的位置;自动排列或对齐到网格可能影响结果 |
插入、删除、重命名以及修改样式可能破坏目标程序自身的数据模型。
尤其不要把桌面当作普通测试列表执行 clear()、delItem() 或批量改名。
winex.desktop 会查找桌面的 SHELLDLL_DefView,再取得其中的 SysListView32 控件,使用 winex.ctrl.listview 进行绑定。
因此下面的 lv 不是另一套桌面专用列表 API,而是可直接使用上述接口的列表对象。
import winex.desktop;
import console;
var lv = winex.desktop.listview;
console.log("桌面图标数:",lv.count);
// 只读枚举,不移动图标,也不修改选择或排列方式。
for(row=1;lv.count){
var text = lv.getItemText(row,1,260);
var rc = lv.getItemRect(row);
console.log(row,text,rc.left,rc.top,rc.right,rc.bottom);
}
桌面图标通常按图标视图显示,读取显示名称用第一列即可,不要依赖 columnCount 枚举桌面图标。
如果确实需要调整图标位置,可使用 lv.setItemPos(row,x,y);需先考虑桌面的自动排列、对齐到网格设置,并自行保存原位置以便恢复。
winex.desktop.listview 是库级共享对象,不要随手调用其 process.free(),以免影响后续桌面操作。
Explorer 重启后,旧的控件句柄可能失效,需要重新获取桌面控件。
创建、查找或管理桌面快捷方式文件请使用 fsys.lnk,而不是这个窗口控件绑定。
win.ui.ctrl.listview 的方法和属性,并不代表所有本地控件功能都已完成远程适配。优先使用本文列出的常用接口;依赖本地指针、图像列表、通知结构或本地窗口子类化的操作不能直接照搬。getItemText 循环读取;不要假定继承的 items、getTable 等内部结构体操作可安全用于外部进程。onnotify、onSelChanged、自绘等通知发给它自己的父窗口,不会因为创建绑定对象就转发给调用方。SysListView32。外观相似的网页表格、WPF、Qt、自绘或其他非标准列表不能直接套用;虚拟列表、回调文本等行为也由目标程序决定。lv.process.free() 释放进程句柄及其管理的远程内存。此后不要继续通过该对象进行远程操作。lv.close() 释放资源:它操作的是目标窗口。当前列表绑定也没有 TreeView 封装额外提供的 free(),应使用 lv.process.free()。