【案例】文件夹监控-ahkv2

这段代码是一个完整的文件夹监视工具,基于 AutoHotkey 编写。主要功能包括:

  1. GUI 界面,用户可以选择要监视的文件夹,并设置监视选项。
  2. 使用 WinAPI 进行文件夹变更监视。
  3. 实时更新监视结果,并显示在 ListView 控件中。

具体的实现细节通过注释进行了详细说明。

; 创建一个新的 GUI 界面,并设置边距
ui := Gui.New(), ui.MarginX := ui.MarginY := 20

; 添加文本控件,显示“文件夹监视”标签
ui.Add("Text", , '文件夹监视')

; 添加一个只读的编辑框,用于显示当前选择的文件夹路径,默认值为桌面路径
ui.Add("Edit", 'xm y+5 w730 cGray +ReadOnly vFolder', A_Desktop)

; 添加一个按钮用于选择文件夹,当点击时触发 SelectFolder 函数
ui.Add("Button", 'x+m yp w50 +Default', '...').OnEvent('Click', 'SelectFolder')

; 添加“选项”标签
ui.Add("Text", 'xm y+10', '选项')

; 添加多个复选框,用于选择监视的内容,包括子目录、文件名、目录名、属性、文件大小、最后写入时间、最后访问时间、创建时间、安全描述符
for _ in [['xm y+10 vSubTree', '子目录'], ['x+5 yp vFiles Checked', '文件名'], ['x+5 yp vFolders Checked', '目录名'], ['x+5 yp vAttributes', '属性'],
    ['x+5 yp vSize', '文件大小'], ['x+5 yp vLast_write', '最后写入时间'], ['x+5 yp vLast_access', '最后访问时间'], ['x+5 yp vCreation', '创建时间'], ['x+5 yp vSecurity', '安全描述符']]
    ui.Add("Checkbox", _[1], _[2])

; 添加一个 ListView 控件,用于显示文件夹监视的变化日志,列包括修改时间、路径、行为、文件(夹)名、旧文件(夹)名
ui.Add("ListView", 'xm w800 r15 vLV', ['修改时间','路径','行为','文件(夹)名','旧文件(夹)名',' '])

; 添加一个“开始”按钮,当点击时触发 StartorStop 函数
ui.Add("Button", 'xm w100 vStart', '开始').OnEvent('Click', 'StartorStop')

; 添加一个“停止”按钮,当点击时触发 StartorStop 函数,初始状态为禁用
ui.Add("Button", 'x+m yp wp vStop +Disabled', '停止').OnEvent('Click', 'StartorStop')

; 添加一个“清除”按钮,当点击时删除 ListView 中的所有记录
ui.Add("Button", 'x+m yp wp', '清除').OnEvent('Click', (ctl, *) => ctl.Gui['LV'].Delete())

; 设置窗口标题并显示 GUI,设置关闭事件处理函数
ui.Title := '文件夹监视', ui.Show(), ui.OnEvent('Close', (*) => ExitApp())

; 结束自动执行部分,等待用户操作
return

; 选择文件夹的回调函数
SelectFolder(ctl, *) {
    ui := ctl.Gui
    if (folder := DirSelect()) ; 弹出文件夹选择对话框
        ui['Folder'].Text := folder, ui['Start'].Enabled := true ; 更新编辑框文本,并启用“开始”按钮
}

; 开始或停止监视的回调函数
StartorStop(ctl, *) {
    static mon := '', _ := OnExit((*) => (mon := '')) ; 静态变量 mon 用于保存监视器实例
    ui := ctl.Gui
    if (ctl.Name = 'Start') { ; 如果点击的是“开始”按钮
        ui.Submit(false), filter := 0 ; 提交表单数据,初始化过滤器
        if !InStr(FileExist(ui['Folder'].Text), 'D') ; 检查选择的路径是否有效
            return MsgBox(ui['Folder'].Text '不是一个有效的文件夹', 'Error')
        ; 根据复选框的选择设置过滤器
        for i, v in ['Files', 'Folders', 'Attributes', 'Size', 'Last_write', 'Last_access', 'Creation', 'Security']
            filter |= ui[v].Value ? [1, 2, 4, 8, 16, 32, 64, 256][i] : 0
        ; 创建文件监视器实例,并启动监视
        mon := FileMonitoring.New(ui['Folder'].Text, filter, Func('update'), ui['SubTree'].Value), ui['Start'].Enabled := !(ui['Stop'].Enabled := true)
    } else mon := '', ui['Start'].Enabled := !(ui['Stop'].Enabled := false) ; 如果点击的是“停止”按钮,停止监视
}

; 更新 ListView 的回调函数
update(change) {
    ; 添加一条新的记录到 ListView
    ui['LV'].Add('vis', FormatTime(, 'yyyy/MM/dd hh:mm:ss'), ui['Folder'].Text, ['新建', '删除', '修改', '重命名'][change.Action], change.Name, change.HasOwnProp('OldName') ? change.OldName : '', '')
    ui['LV'].ModifyCol() ; 自动调整列宽
}

; 文件监视类定义
class FileMonitoring {
    static NOTIFY := {FILE_NAME: 0x1, DIR_NAME: 0x2, ATTRIBUTES: 0x4, CHANGE_SIZE: 0x8, LAST_WRITE: 0x10, LAST_ACCESS: 0x20, CREATION: 0x40, SECURITY: 0x100} ; FILE_NOTIFY_CHANGE

    __New(folderPath, notifyFilter, UserFunc, watchSubtree := false) {
        ; 创建事件对象和缓冲区
        this.Event := FileMonitoring._Event.New(), this.buffer := BufferAlloc(1024), pBuffer := this.buffer.Ptr, this.overlapped := BufferAlloc(A_PtrSize * 3 + 8), this.pOverlapped := this.overlapped.Ptr
        ; 初始化目录监视
        this.Directory := FileMonitoring._ReadDirectoryChanges.New(folderPath, notifyFilter, watchSubtree, pBuffer, this.pOverlapped, this.Event.Ptr)
        ; 创建事件信号处理对象并启动监视
        this.EventSignal := FileMonitoring._EventSignal.New(this.Directory, this.Event.Ptr, pBuffer, UserFunc), this.EventSignal.folderPath := folderPath, this.Directory.Read()
    }

    __Delete() {
        ; 停止监视,清理资源
        DllCall('CancelIoEx', 'Ptr', this.Directory.Handle, 'Ptr', this.pOverlapped)
        this.Event.Set(), this.EventSignal.Clear(), this.Directory.Clear(), this.buffer := ''
    }

    class _Event {
        __New() => (this.Ptr := DllCall('CreateEvent', 'Int', 0, 'Int', 0, 'Int', 0, 'Int', 0, 'Ptr')) ; 创建事件对象
        Set() => DllCall('SetEvent', 'Ptr', this) ; 设置事件对象的状态
        __Delete() => DllCall('CloseHandle', 'Ptr', this) ; 关闭事件对象句柄
    }

    class _EventSignal {
        __New(Directory, hEvent, pBuffer, UserFunc) {
            ; 初始化事件信号对象
            for v in ['Directory', 'hEvent', 'pBuffer']
                this.%v% := %v%
            ; 注册自定义消息
            OnMessage(this.WM_EVENTSIGNAL := DllCall('RegisterWindowMessage', 'Str', 'WM_EVENTSIGNAL', 'UInt'), this.OnEvent := ObjBindMethod(this, 'On_WM_EVENTSIGNAL'))
            this.UserFunc := IsObject(UserFunc) ? UserFunc : Func(UserFunc), this.startAddress := this.CreateWaitFunc(this.hEvent, A_ScriptHwnd, this.WM_EVENTSIGNAL)
            ; 创建等待线程
            this.Thread := FileMonitoring._EventSignal._Thread.New(this.startAddress)
        }

        On_WM_EVENTSIGNAL(wp, *) {
            ; 处理事件信号
            if !(wp = this.hEvent && DllCall('GetOverlappedResult', 'Ptr', this.hEvent, 'Ptr', this.pBuffer, 'UInt*', written := 0, 'UInt', false))
                return
            addr := this.pBuffer, offset := 0, preName := '', preAction := 0
            Loop {
                ; 解析监视事件
                addr += offset, Action := NumGet(addr + 4, 'UInt'), Name := StrGet(addr + 12, NumGet(addr + 8, 'UInt') // 2, 'UTF-16')
                if (Name == preName && Action == preAction)
                    continue
                if (Action = 4) {
                    ; 重命名事件的旧名称
                    Change := {Action: Action, OldName: Name}, preName := Name, preAction := Action
                    continue
                } else if (Action = 5 && preAction = 4) {
                    ; 重命名事件的新名称
                    Change.Path := this.folderPath '' Name, Change.Name := Name
                } else Change := {Action: Action, Name: Name, Path: this.folderPath '' Name}
                SetTimer(this.UserFunc.Bind(Change), -1), preName := Name, preAction := Action
            } until !(offset := NumGet(addr + 0, 'UInt'))
            ; 重新启动监视
            this.Thread.Wait(), this.Thread := FileMonitoring._EventSignal._Thread.New(this.startAddress), this.Directory.Read()
        }

        CreateWaitFunc(Handle, hWnd, Msg, Timeout := -1) {
            ; 创建等待函数,用于等待事件信号
            ptr := DllCall('VirtualAlloc', 'Ptr', 0, 'Ptr', A_PtrSize = 4 ? 49 : 85, 'UInt', MEM_COMMIT := 0x1000, 'UInt', PAGE_EXECUTE_READWRITE := 0x40, 'Ptr')
            pWaitForSingleObject := DllCall('GetProcAddress', 'Ptr', DllCall('GetModuleHandle', 'Str', 'kernel32.dll', 'Ptr'), 'AStr', 'WaitForSingleObject', 'Ptr')
            pPostMessageW := DllCall('GetProcAddress', 'Ptr', DllCall('GetModuleHandle', 'Str', 'user32.dll', 'Ptr'), 'AStr', 'PostMessageW', 'Ptr')
            NumPut('Ptr', pWaitForSingleObject, 'Ptr', pPostMessageW, ptr + 0)
            if (A_PtrSize = 4) {
                ; 32 位系统下的等待函数实现
                NumPut('UChar', 0x68, ptr + 8), NumPut('UInt', Timeout, ptr + 9), NumPut('UChar', 0x68, ptr + 13)
                NumPut('Ptr', Handle, ptr + 14), NumPut('UShort', 0x15FF, ptr + 18), NumPut('Ptr', ptr, ptr + 20)
                NumPut('UShort', 0x6850, ptr + 24), NumPut('Ptr', Handle, ptr + 26), NumPut('UChar', 0x68, ptr + 30)
                NumPut('UInt', Msg, ptr + 31), NumPut('UChar', 0x68, ptr + 35), NumPut('Ptr', hWnd, ptr + 36), NumPut('UShort', 0x15FF, ptr + 40)
                NumPut('Ptr', ptr + 4, ptr + 42), NumPut('UChar', 0xC2, ptr + 46), NumPut('UShort', 4, ptr + 47)
            } else {
                ; 64 位系统下的等待函数实现
                NumPut('UChar', 0x53, ptr + 16), NumPut('UInt', 0x20EC8348, ptr + 17), NumPut('UInt', 0xBACB8948, ptr + 21)
                NumPut('UInt', Timeout, ptr + 25), NumPut('UShort', 0xB948, ptr + 29), NumPut('Ptr', Handle, ptr + 31)
                NumPut('UShort', 0x15FF, ptr + 39), NumPut('UInt', -45, ptr + 41), NumPut('UShort', 0xB849, ptr + 45)
                NumPut('Ptr', Handle, ptr + 47), NumPut('UChar', 0xBA, ptr + 55), NumPut('UInt', Msg, ptr + 56), NumPut('UShort', 0xB948, ptr + 60)
                NumPut('Ptr', hWnd, ptr + 62), NumPut('UInt', 0xC18941, ptr + 70), NumPut('UShort', 0x15FF, ptr + 73)
                NumPut('UInt', -71, ptr + 75), NumPut('UInt', 0x20C48348, ptr + 79), NumPut('UShort', 0xC35B, ptr + 83)
            }
            return ptr + A_PtrSize * 2
        }

        Clear() {
            ; 清理事件信号对象
            this.Thread.Wait(), OnMessage(this.WM_EVENTSIGNAL, this.OnEvent, 0), this.OnEvent := ''
            DllCall('VirtualFree', 'Ptr', this.startAddress - A_PtrSize * 2, 'Ptr', A_PtrSize = 4 ? 49 : 85, 'UInt', MEM_DECOMMIT := 0x4000)
        }

        class _Thread {
            ; 线程类定义
            __New(startAddress) {
                ; 创建新线程
                if !(this.handle := DllCall('CreateThread', 'Ptr', 0, 'UInt', 0, 'Ptr', startAddress, 'Ptr', 0, 'UInt', 0, 'UInt', 0, 'Ptr'))
                    throw Exception('Failed to create thread.`nError code: ' . A_LastError)
            }
            Wait() => DllCall('WaitForSingleObject', 'Ptr', this.handle, 'Int', -1) ; 等待线程结束
            __Delete() => DllCall('CloseHandle', 'Ptr', this.handle) ; 关闭线程句柄
        }
    }

    class _ReadDirectoryChanges {
        ; 目录变更监视类定义
        __New(dirPath, notifyFilter, watchSubtree, pBuffer, pOverlapped, hEvent) {
            static OPEN_EXISTING := 3, access := (FILE_SHARE_READ := 1) | (FILE_SHARE_WRITE := 2), flags := (FILE_FLAG_OVERLAPPED := 0x40000000) | (FILE_FLAG_BACKUP_SEMANTICS := 0x2000000)
            for v in ['notifyFilter', 'watchSubtree', 'pBuffer', 'pOverlapped', 'hEvent']
                this.%v% := %v%
            ; 打开目录句柄
            this.handle := DllCall('CreateFile', 'Str', dirPath, 'UInt', 1, 'UInt', access, 'Int', 0, 'UInt', OPEN_EXISTING, 'UInt', flags, 'Int', 0, 'Ptr')
        }

        Read() {
            ; 读取目录变更
            DllCall('RtlZeroMemory', 'Ptr', this.pOverlapped, 'Ptr', A_PtrSize * 3 + 8), NumPut('Ptr', this.hEvent, this.pOverlapped + A_PtrSize * 2 + 8)
            return DllCall('ReadDirectoryChangesW', 'Ptr', this.handle, 'Ptr', this.pBuffer, 'UInt', 1024, 'UInt', this.watchSubtree, 'UInt', this.notifyFilter, 'Ptr', 0, 'Ptr', this.pOverlapped, 'Ptr', 0)
        }

        Clear() => DllCall('CloseHandle', 'Ptr', this.handle) ; 关闭目录句柄
    }
}
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

给TA捐赠
共{{data.count}}人
人已捐赠
案例

输入法状态切换(简易板)

2021-5-20 10:28:18

案例

清理Excel单元格中不可见的垃圾字符

2021-6-10 10:55:13

3 条回复 A文章作者 M管理员
  1. 逆风

    V2??版本太多也不是什么好事,脚本不能共享,语法有差异

  2. arwar88

    学习

  3. hexuren

    注释非常详细,感谢分享!

个人中心
购物车
优惠劵
有新私信 私信列表
搜索