AutoHotkey监控Excel事件应用(1)---根据当前列的值高亮行
实现的效果见视频。
使用方法:
- 以下代码保存为 ahk 文件,并运行。
- 鼠标停留到Excel的关键列任意单元格,按F9即可。
说明:
- Excel表数据要带标题行
- 不要在关键列左侧增加列
- 相同的值要连续才会是同颜色
- 颜色是根据下表 Excel 自带 ColorIndex 的33-41 颜色循环使用,可自行在代码里修改
#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("oleacc\AccessibleObjectFromWindow", "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
}