如何在多轮FindNextChangeNotification事件后刷新ListView文件列表
我需要确定在一系列FindNextChangeNotification事件后,何时可以在ListView中显示最终的文件列表。当多个事件连续触发(如同时触发FILE_NOTIFY_CHANGE_FILE_NAME和FILE_NOTIFY_CHANGE_SIZE)时,每次事件都刷新ListView会引发冲突错误,因此需仅在最后一次通知时执行刷新,但难点在于判断该实体的最后一次通知何时结束。
为简化处理,我仅保留名称相关通知,该设置在单文件重命名、单/多文件粘贴、单/多文件删除场景下均可正常工作,批量操作仅需检查批次中最后一个基于名称的项:
DWORD dwWaitStatus; HANDLE dwChangeHandles[2]; dwChangeHandles[0] = FindFirstChangeNotification(path.c_str(), FALSE, FILE_NOTIFY_CHANGE_FILE_NAME); dwChangeHandles[1] = FindFirstChangeNotification(path.c_str(), FALSE, FILE_NOTIFY_CHANGE_DIR_NAME); while (TRUE) { dwWaitStatus = WaitForMultipleObjects(2, dwChangeHandles, FALSE, INFINITE); if (dwWaitStatus == WAIT_OBJECT_0) { // A file was created, renamed or deleted in the directory. refreshFunc(); FindNextChangeNotification(dwChangeHandles[0]); } else if (dwWaitStatus == WAIT_OBJECT_0 + 1) { // A directory was created, renamed, or deleted. refreshFunc(); FindNextChangeNotification(dwChangeHandles[1]); } }
Refresh函数还会通过全局变量检查批量操作:
if (batchOperation) { batchOperationCurrentCount--; if (batchOperationCurrentCount == 0) { batchOperation = false; // Reset batchOperation flag ContentsListView_Refresh(); // Ready to refresh } } else { // Not a batch operation (e.g. single Rename), refresh immediately ContentsListView_Refresh(); }
但存在一个异常场景:从外部应用保存新的非空文件时,会针对同一文件触发2次File-Name通知。例如在记事本中新建文件并首次另存到目标目录时,会收到两次File-Name通知,推测可能是创建和重命名操作均触发了通知,但这并非目录通知,而是明确的重复文件通知。
若添加FILE_NOTIFY_CHANGE_FILE_SIZE通知,各类操作的通知次数会进一步增加:重命名/粘贴/删除操作会增至2次,新建文件操作会增至3次。
因此我想知道如何判断通知已全部完成?是否需要固定定时器检查,或是用队列顺序执行刷新?我更倾向于使用FindFirstFile系列方案而非ReadDirectoryChangesW。
针对你的需求,这里提供几种无需切换到ReadDirectoryChangesW的可行方案:
1. 定时器防抖(最推荐)
核心思路是:每次收到通知时不立即刷新,而是重置一个短定时器(200-500ms均可,可根据实际场景调整)。如果定时器到期前没有新通知,说明操作已完成,再执行ListView刷新。这种方式能自动合并连续触发的多次通知,完美解决重复通知和批量操作的问题。
修改思路:
- 新增全局或类成员的定时器句柄
- 在
refreshFunc中,先关闭现有定时器(如果存在),再创建新定时器,到期后执行刷新 - 定时器回调函数中完成ListView刷新并清理定时器
示例修改后的refreshFunc逻辑:
// 全局/成员变量 HANDLE hRefreshTimer = NULL; VOID CALLBACK RefreshTimerCallback(PVOID lpParam, BOOLEAN TimerOrWaitFired) { ContentsListView_Refresh(); hRefreshTimer = NULL; } void refreshFunc() { // 重置定时器:关闭旧定时器,创建新的300ms后触发的定时器 if (hRefreshTimer != NULL) { DeleteTimerQueueTimer(NULL, hRefreshTimer, NULL); hRefreshTimer = NULL; } CreateTimerQueueTimer(&hRefreshTimer, NULL, RefreshTimerCallback, NULL, 300, 0, WT_EXECUTEDEFAULT); }
2. 优化批量操作计数逻辑
基于你现有的批量操作判断逻辑,可扩展为:
- 收到通知时,用
FindFirstFile遍历目录统计当前文件/目录总数 - 对比上一次统计的数量,若连续两次统计结果一致,判定操作完成,执行刷新
- 对于记事本保存这类重复通知,连续两次统计的文件数量不会变化,可直接跳过重复的刷新触发
3. 过滤重复通知
维护一个最近通知的记录集合(比如记录最近触发的文件名、操作类型),如果新收到的通知和上一条完全重复(如同一文件的连续文件名通知),则跳过本次refreshFunc调用。这种方式适合作为防抖方案的辅助补充,单独使用对批量操作的适配性较差。
内容的提问来源于stack exchange,提问作者gene b.

