AutoHotkey监控Excel事件应用(1)---根据当前列的值高亮行

实现的效果见视频。

AutoHotkey监控Excel事件应用(1)---根据当前列的值高亮行

使用方法:

  1. 以下代码保存为 ahk 文件,并运行。
  2. 鼠标停留到Excel的关键列任意单元格,按F9即可。

说明:

  1. Excel表数据要带标题行
  2. 不要在关键列左侧增加列
  3. 相同的值要连续才会是同颜色
  4. 颜色是根据下表 Excel 自带 ColorIndex 的33-41 颜色循环使用,可自行在代码里修改

AutoHotkey监控Excel事件应用(1)---根据当前列的值高亮行

#SingleInstance Force
F12::ExitApp

F9::
event_highlightRowByColumnValue()
return

;根据当前列的值高亮表内【行】
;要求有标题行
event_highlightRowByColumnValue()
{
    idName := A_ThisFunc
    xl := ox()
    ac := xl.ActiveCell
    ;NOTE 记录关键列号,因为 target 不可靠
    cKey := ac.column
    ;定义颜色顺序 ColorIndex
    arrColor := [33,34,35,36,37,38,39,40,41]
    ;开启/关闭事件
    if !Events_Workbook.objEvent.haskey(idName)
    {
        highlightRowByColumn(ac) ;开启事件前直接运行一次
        new Events_Workbook(xl.ActiveWorkbook, idName, "Change", func("highlightRowByColumn")).start() ;用内部函数也没问题
    }
    else if (Events_Workbook.objEvent[idName].wb.name = xl.ActiveWorkbook.name) ;当前工作表 = 事件监控工作表
        Events_Workbook.objEvent[idName].stop() ;实例没保存到变量,用此方法引用
    highlightRowByColumn(target:="")
    {
        ;获取数据区域(不含标题行)
        try
            rngData := target.ListObject.DataBodyRange
        catch
        {
            rngCR := target.CurrentRegion
            rngData := rngCR.offset(1).resize(rngCR.rows.count-1)
        }
        ;关键列区域
        rngKeyCol := rngData.columns(cKey-rngData.column+1)
        ;判断修改值的单元格是否在 rngKeyCol 内
        if !isobject(xl.intersect(target, rngKeyCol))
            return
        else if (target.columns.count > 1) ;多列
            target := xl.intersect(target, rngKeyCol)
        ;单个单元格且为空值,则不处理
        if (target.cells.count == 1 & !strlen(target.value))
            return
        xl.ScreenUpdating := false
        arrV := rngKeyCol.value ;获取当前列的值
        idx := 1 ;颜色序号
        cnt := 1 ;同值行数
        _v := arrV[1,1] ;用来判断是否同个值
        rngRowStart := rngData.rows(1) ;当前高亮的第1行
        loop(arrV.MaxIndex(1)-1) ;从第2行开始循环
        {
            if (arrV[A_Index+1,1] != _v)
            {
                rngRowStart.resize(cnt).interior.ColorIndex := arrColor[idx]
                ;初始化
                rngRowStart := rngData.rows(A_Index+1)
                _v := arrV[A_Index+1,1]
                cnt := 1
                ;记录下一颜色序号
                idx++
                if (idx > arrColor.length())
                    idx := 1
            }
            else
                cnt++
        }
        ;NOTE 执行最后一类数据的高亮
        rngRowStart.resize(cnt).interior.ColorIndex := arrColor[idx]
        xl.ScreenUpdating := true
    }
}

; https://autohotkey.com/board/topic/69847-com-events-using-ByRef-parms/?p=442260
class Events_Workbook
{
    static objEvent := {} ;保存事件的所有信息供外部使用,以传入的 event 为key

    ;NOTE wb为事件的载体,其他工作簿单独事件逻辑
    ;用 idName 区别各功能
    ;同事件可满足多个需求
    __new(wb, idName, event, funcObj:="", data:="")
    {
        this.tipsLevel := 19
        this.idName := idName
        this.event := event ;监控的事件名称,多事件可用数组
        this.wb := wb ;实例要用,用 objEvent 提取太麻烦
        this.funcObj := funcObj ;TODO 是否要区分不同事件
        this.data := data ;其他传入的值
    }

    ;所有事件+方法都会运行 
    ;evtThis为 Sheet+事件名,args[1]=target
    __call(evtThis, args*)
    {
        ;tooltip(evtThis)
        if (evtThis = this.event || substr(evtThis,6) = this.event) ;筛选事件名称(从第6个字符开始提取是为了删除 Sheet 前缀)
            this.funcObj.call(args*)
        ;else if isobject(this.event) ;暂时没用
        ;{
            ;for _, evt in this.event
            ;{
                ;if (evtThis = evt || substr(evtThis,6) = evt) ;筛选事件
                ;{
                    ;this.funcObj.call(args*)
                    ;return
                ;}
            ;}
        ;}
    }

    ;TODO 监控工作表和工作簿,分别怎么实现
    start()
    {
        Events_Workbook.objEvent[this.idName] := this ;NOTE 保存实例,外部获取信息和 stop 用,这样外面不需要保存实例变量
        ComObjConnect(this.wb, this)
        this.tipsShow("start")
    }
    stop()
    {
        ComObjConnect(this.wb)
        Events_Workbook.objEvent.delete(this.idName)
        this.tipsShow("stop")
        this := "" ;ObjRelease 会出错
    }

    list()
    {
        return Events_Workbook.objEvent
    }

    ;事件结束会关闭显示
    tipsShow(str)
    {
        if !isobject(this.data)
        {
            tooltip(str,,, this.tipsLevel)
            SetTimer(func("tooltip").bind(,,, this.tipsLevel), -1000)
        }
        else
        {
            tooltip(this.data.tipStr,,, this.tipsLevel)
            if this.data.haskey("tipTime")
            {
                if this.data.tipTime
                    SetTimer(func("tooltip").bind(,,, this.tipsLevel), -this.data.tipTime)
            }
            else
                SetTimer(func("tooltip").bind(,,, this.tipsLevel), -1000)
        }
    }

}

ox(winTitle:="ahk_class XLMAIN")
{
    ctlID := ControlGetHwnd("EXCEL71", winTitle)
    if !ctlID
        ExitApp
    if dllcall("oleaccAccessibleObjectFromWindow", "ptr",ctlID, "uint",4294967280, "ptr",-VarSetCapacity(IID,16)+NumPut(0x46000000000000C0,NumPut(0x0000000000020400,IID,"int64"),"int64"), "ptr*",pacc) = 0
        win := ComObject(9, pacc, 1)
    loop
    {
        try
            xl := win.application
        catch
            ControlSend("{escape}", "EXCEL71", winTitle)
    }
    until !!xl
    return xl
}