CDiskClean 开发历程——忽略进程_拖拽功能实现参考

前言

这篇博客,我打算结合我最近正在开发的 C盘管理工具 内部的功能实现,给各位读者一些在实现方式上的参考,文章较长,但会很详细,请做好心理准备。

问题引入

接着上文,在实现了 特定目录的所有进程修改监控 之后,记录中可能会包含 Windows 后台进程的读写操作。
比如 SearchProtocolHost.exe (Windows 索引服务) 会在文件发生更改后 查看对整体索引树的 变化并做出调整。

这里我列举一些常见的 Windows 后台进程:

SearchProtocolHost: Windows 搜索索引进程
SearchIndexer : Windows 搜索服务进程

System : 系统内核操作进程

svchost : 系统服务宿主进程

MsMpEng : Windows Defender 进程

Explorer :资源管理器

RuntimeBroker : Windows 运行时代理

taskhostw : Windows 任务宿主

知道了这些进程,就可以在后台静态添加 ,但是运行的时候记录中出现的需要过滤的进程,怎么处理掉呢?
很容易想到,首先可以使用 filter 过滤 ,让页面上不显示其他的进程

  • 但是那么多其他进程,得搞多少次?而且也是治标不治本。 Pass

要不设定一个过滤线程的列表,在后台监测的时候就进行排除?

  • 然后呢?还有吗?如果记录中已经出现了没有过滤的线程,是需要额外添加进去吧,有没有什么方便的方式可以操作? Interested

添加一个按钮,让用户输入,然后添加进列表就可以了吧?

  • 如果进程名字很长 怎么办?在监测场景下记录的变化会很快吧,而且不能保证展示的表格每次都能展示完全 哦。Expecting

想到了,是不是可以通过拖拽 ?将记录 拖拽进 列表展示框,然后直接载入列表,这样又快又方便。

  • 认可。Yes

所以,一番 思考(shen wen) 下来,拖拽是一个很好的方式。那我接下来就讲讲如何实现
依旧 只讲思路 以及提供一些代码参考不完完整整地进行教学

拖拽相关事件

整个过程将以 Winform-DataGridView + Winform-ListView 之间的交互为例。
先考虑一个问题,拖拽的 过程可以拆解成哪些步骤?

  1. 先是从 源头 选中记录。
  2. 将记录 拖出 源头的边界
  3. 将记录 移入 目标内部
  4. 目标 完成记录接收

所以 理论上来讲,拖拽需要:

  • 选中目标记录

  • 检测鼠标指针位置:

    • 是否移出了源头
    • 是否到达了目标内部
  • 目标确认接收数据

  • 目标完成数据接收

    所以经过这一轮的分析,最后确定下来的事件有 :

MouseDown:鼠标按下 => 记录起点
MouseMove : 鼠标移动 => 检测位置,并尝试确定 要转移的记录

MouseUp : 鼠标指针松开 => 取消拖拽状态

DragEnter : 检测到有外部数据进入组件 => 决定是否 接收数据

DragDrop : 数据已经到达内部 => 最终确认,并处理数据


确认好事件之后,我们可以开始实现了。

MouseDown

这里主要是要记录起点,所以需要引入一个类变量,并将这个函数与 Winform-DataGridView 进行绑定 订阅关系

代码参考:

csharp 复制代码
  private bool _isGridDragging; // 是否正在进行拖拽
  private Point _gridMouseStartPos; // 起点

  // 鼠标按下:记录起点
  private void changesDataGrid_MouseDown(object sender, MouseEventArgs e)
  {
      if (e.Button == MouseButtons.Left)
      {
          _gridMouseStartPos = e.Location;
          _isGridDragging = false;
      }
  }

实现完这个之后,后续就是检测鼠标的位置了,二者需要配合。

MouseMove

这个处理函数中,主要是检测鼠标的位置,看是否可以触发拖拽,并将需要移动的记录确认下来(需要和 MouseDown 中记录的起点进行配合)

确认记录的方式 这里需要使用
Winform-DataGridView 对象的 DoDragDrop(record, DragDropEffects.Copy) 函数。

如果读者在写代码的过程中 搞乱了执行顺序,会扣大分,并且报错
Windows 默认的这两个事件的触发顺序是正确 的,但是自己调用的 就不一定了.. ....

代码参考:

ini 复制代码
 // 鼠标移动:达到拖拽阈值后启动拖拽,携带整条变更记录
  private void changesDataGrid_MouseMove(object sender, MouseEventArgs e)
  {
      if (e.Button != MouseButtons.Left || _isGridDragging) return;

      int moveRange = SystemInformation.DragSize.Width;
      var offset = Math.Abs(e.X - _gridMouseStartPos.X) + Math.Abs(e.Y - _gridMouseStartPos.Y);
      if (offset < moveRange) return;

      var hit = changesDataGrid.HitTest(e.X, e.Y);
      if (hit.RowIndex < 0) return;

      if (changesDataGrid.Rows[hit.RowIndex].DataBoundItem is not FileChangeRecord record) return;
      // 未解析出进程名的记录无法用于忽略进程
      if (string.IsNullOrEmpty(record.SourceProcess) || record.SourceProcess == "未知进程") return;

      _isGridDragging = true; // 确认现在处于拖拽状态
      changesDataGrid.DoDragDrop(record, DragDropEffects.Copy);
  }

MouseUp

这个处理函数中执行的操作 比较简单,重置拖拽的标记 即可
代码参考:

typescript 复制代码
// 鼠标松开:重置标记
  private void changesDataGrid_MouseUp(object sender, MouseEventArgs e)
  {
      _isGridDragging = false;
  }

至此,源头端的处理函数告一段落 ,现在开始接收端

DragEnter

这个处理函数中执行的操作主要是 判断是否可以进行数据接收。

代码参考:

csharp 复制代码
 private void ignoreProcessView_DragEnter(object sender, DragEventArgs e)
 {
     if (e.Data?.GetDataPresent(typeof(FileChangeRecord)) == true &&
         !string.IsNullOrWhiteSpace(
             (e.Data.GetData(typeof(FileChangeRecord)) as FileChangeRecord)?.SourceProcess))
     {
         e.Effect = DragDropEffects.Copy;
     }
     else
     {
         e.Effect = DragDropEffects.None;
     }
 }

DragDrop

这个处理函数中 主要是对传入的数据进行二次判断 ,并进行一些处理,存入列表。

代码参考:

csharp 复制代码
 private void ignoreProcessView_DragDrop(object sender, DragEventArgs e)
  {
      if (e.Data?.GetData(typeof(FileChangeRecord)) is not FileChangeRecord record ||
          string.IsNullOrEmpty(record.SourceProcess))
          return;

      // 监视进行中不允许接收拖拽记录,给出提示
      if (_monitorService.IsRunning)
      {
          MessageBox.Show("监视进行中不能接收拖拽记录,请先点击「暂停」后再拖拽。",
              "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
          return;
      }

      AddIgnoreProcessInternal(record.SourceProcess);
  }

忽略进程的 其他细节处理

拖拽期间,因为需要停止监测(我的逻辑),这里给出我停止 ETW 的代码参考:

csharp 复制代码
   public void Stop()
   {
       IsRunning = false; // 标识符
       _cts?.Cancel(); // token 取消 让代码抛异常 进而停止
       try { _session?.Dispose(); } catch { } // 停止 Session 的事件监听
       _session = null; // 释放
       _eventBuffer.Clear(); // 缓冲区释放
   }

不知道 不停止会有什么问题,感兴趣的可以自己试试。


将忽略线程 添加进列表之后,需要将 列表同步进 ETW 。但是 ETW 可以占用的列表是线程共享 的,而且是不可变的数组
所以在 同步进 ETW 的过程 需要加锁 ,并且要将动态列表转换成数组 ,然后重新赋值给 ETW占用的数组。

代码参考:
AddIgnoreProcess: 添加忽略进程(加锁

csharp 复制代码
public IgnoreProcessRecord AddIgnoreProcess(string processName)
{
   IgnoreProcessRecord record;
   lock (_ignoreProcessLock)
   {
       var existing = IgnoreProcessRecords.FirstOrDefault(r =>
           string.Equals(r.ProcessName, processName, StringComparison.OrdinalIgnoreCase));
       if (existing != null) return existing;

       record = new IgnoreProcessRecord(processName);
       IgnoreProcessRecords.Add(record);
   }
   _etwService.OperateIgnoreProcessArr(record.ProcessName, 1);
   return record;
}

IgnoreProcessArray 相关操作

ini 复制代码
#region IgnoreProcessArray 相关操作
public void OperateIgnoreProcessArr(string processName, int type)
{
    var list = _ignoreProcessArray.ToList();
    if (type == 1)
        list.Add(processName);
    if (type == 2)
        list.RemoveAll(p => string.Equals(p, processName, StringComparison.OrdinalIgnoreCase));
    _ignoreProcessArray = list.Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
}
public void OperateIgnoreProcessArr(string[] processNames)
{
    _ignoreProcessArray = processNames.ToArray();
}
#endregion

需要注意的是 ETW 在监控 特定目录的进程修改 时,修改了监控目录,也需要 加锁
本质上来讲 是由于 ETW 使用的 数组都是 线程共享的,在多线程下需要加锁 保证同步

总结------ From DeepSeek

本文聚焦于 C 盘管理工具中动态过滤无关系统进程 这一交互痛点,提出并实现了一套基于拖拽操作的解决方案。在梳理了常见的 Windows 后台进程(如 SearchIndexer、svchost 等)后,明确了静态黑名单的局限性,转而采用用户可随时调整的动态忽略列表。

技术层面,文章详细拆解了从 DataGridView 拖拽记录至 ListView 的完整事件链:MouseDown 记录起点、MouseMove 触发拖拽并携带数据、MouseUp 重置状态;接收端通过 DragEnter 校验数据合法性,DragDrop 完成最终添加。同时,考虑到 ETW 监控服务与忽略列表在多线程环境下的同步问题,引入了加锁机制及数组动态更新策略,确保了运行时过滤逻辑的线程安全

整个设计既简化了用户操作(一次拖拽即可屏蔽干扰进程),又维持了底层监控的稳定高效,为类似工具的用户自定义过滤功能提供了一个轻量且实用的参考范例。

相关推荐
xcLeigh3 小时前
Unity基础:C#脚本的创建与挂载——让你的游戏物体活起来
游戏·unity·c#·教程
花落人散处3 小时前
CDiskClean 开发历程——文件&进程实时监控-实现方案参考
c#
猿长大人6 小时前
C# | MediatR 入门指南:后端架构解耦
分布式·后端·架构·c#·.net
回忆2012初秋6 小时前
C# 中的 EAP:基于事件的异步编程全面指南(一)
开发语言·c#
曹牧7 小时前
C#:Type
开发语言·c#
用户3721574261358 小时前
C# 如何在 Excel 中创建下拉列表:3 种常见数据源实现方式
c#
czhc11400756639 小时前
工业视觉检测中的通用设计模式与技术笔记
c#
明如正午9 小时前
【C#】volatile 关键字深度解析:为什么我的停止按钮有时失灵?
c#