SysListView321控件查找并定位

SysListView321控件在windows里相当普遍,比如【程序和功能】页面的已安装软件列表,
因为这种控件无法搜索,如果内容一多,给定位带来困难,于是有了如下脚本。
具体逻辑是:

  1. 提取控件的所有内容
  2. 用gui界面写个查找功能,并确定要查找的内容
  3. 用AutoHotkey定位到内容所在行。

运行脚本后,激活控件页面,按F9即可
附上 AutoHotkey v2-beta 代码

#SingleInstance force
persistent

F9::_ListView("SysListView321", "A").selectByInput()

class _ListView {

    __new(ctl, winTitle:="") {
        this.ctl := ctl
        this.hwnd := WinExist(winTitle)
        this.hCtl := ControlGetHwnd(ctl, "A")
        ; msgbox(ctl . "`n" . this.hwnd . "`n" . this.hCtl . "`n" . WinExist())
        this.pid := WinGetPID()
    }

    getIndexByText(str, idx:=1) {
        loop parse, ListViewGetContent(, this.hCtl), "`n", "`r" {
            ;msgbox(str . "`n" . json.stringify(StrSplit(A_LoopField,A_Tab), 4))
            if (StrSplit(A_LoopField,A_Tab)[idx] == str) {
                return A_Index
            }
        }
        return 0
    }

    selectByInput() {
        arr := []
        loop parse, ListViewGetContent(, this.hCtl), "`n", "`r"
            arr.push(RegExReplace(A_LoopField, "`t.*"))
        arrRes := searchArr(arr)
        if (arrRes.length)
            this.selectByText(arrRes[1])
    }
    selectByText(str) {
        idx := this.getIndexByText(str)
        if (!idx)
            throw ValueError(format('not found "{1}" in ListView', str))
        this.selectByIndex(idx)
    }
    selectByIndex(idx:=1) {
        bufLvItem := buffer(52+(2*A_PtrSize))
        numput("UPtr", state:=3, bufLvItem, 12)
        numput("UPtr", stateMask:=2, bufLvItem, 16)
        oRB := RemoteBuffer(this.pid, bufLvItem.size)
        oRB.write(bufLvItem)
        SendMessage(LVM_ENSUREVISIBLE:=0x1013, idx-1,,, "ahk_id" . this.hCtl)
        SendMessage(LVM_SETITEMSTATE:=0x102B, idx-1, oRB.arrBuffer[1],, "ahk_id" . this.hCtl)
        ;PostMessage(0x1043,, 2,, "ahk_id" . ControlGetHwnd(ctl, winTitle))
    }

}

class RemoteBuffer {
    ;size 一般多大
    ; https://docs.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights
    ;PROCESS_VM_OPERATION:=0x8 PROCESS_VM_READ:=0x10 PROCESS_VM_WRITE:=0x20 PROCESS_QUERY_INFORMATION:=0x400
    __new(pid, size:=0, DesiredAccess:=56) { ; 0x8|0x10|0x20==56
        if !(this.hProcess := dllcall("OpenProcess", "UInt",DesiredAccess, "Int",0, "UInt",pid, "Ptr"))
            return ""
        this.arrBuffer := [] ;NOTE 可申请多个内存,所以用数组
        this.arrSize := []
        if (size)
            this.addBuffer(size)
    }

    __delete(idx:=0) {
        if idx {
            pBuffer := this.arrBuffer.RemoveAt(idx)
            this.arrSize.RemoveAt(idx)
            dllcall("VirtualFreeEx", "Ptr",this.hProcess, "Ptr",pBuffer, "UInt",0, "UInt",MEM_RELEASE:=0x8000)
            dllcall("CloseHandle", "Ptr",this.hProcess)
        } else { ;删除全部
            for k, pBuffer in this.arrBuffer
                dllcall("VirtualFreeEx", "Ptr",this.hProcess, "Ptr",pBuffer, "UInt",0, "UInt",MEM_RELEASE:=0x8000)
            dllcall("CloseHandle", "Ptr",this.hProcess)
        }
    }

    ;比如给 SysTabControl321 的某一项申请空间(文字内容不定长,所以不能直接用 SendMessage 获取)
    ;文字内容往往在 pBuffer + size 的后面
    addBuffer(size) {
        if !(pBuffer := dllcall("VirtualAllocEx", "UInt",this.hProcess, "UInt",0, "UInt",size, "UInt",MEM_COMMIT:=0x1000, "UInt",PAGE_READWRITE:=4, "Ptr"))
            return ""
        this.arrBuffer.push(pBuffer)
        this.arrSize.push(size)
        return pBuffer
    }

    ;NOTE size 可能和 arrSize 不同
    write(pLocalBuff, size:=0, idx:=1, offset:=0) {
        size := size ? size : this.arrSize[idx] ;TODO size是否恒等于 this.arrSize[idx],是则可省略参数
        return dllcall("WriteProcessMemory", "Ptr",this.hProcess, "Ptr",this.arrBuffer[idx]+offset, "Ptr",pLocalBuff, "UInt",size, "UInt",0)
    }

    ;如果是单值,可直接设置 bVal=1
    read(idx:=1, bVal:=0, offset:=0, size:=0) {
        static bufLocal ;NOTE 不能少
        if !size
            size := this.arrSize[idx] - offset
        else
            size := min(size, this.arrSize[idx] - offset)
        bufLocal := buffer(size, 0)
        ;从 arrBuffer[idx]+offset 地址读取 size 长度内容,存到 &bufLocal 地址
        dllcall("ReadProcessMemory", "Ptr",this.hProcess, "Ptr",this.arrBuffer[idx]+offset, "Ptr",bufLocal, "UInt",size, "UInt",0)
        ;bufLocal := buffer(-1)
        return bVal ? bufLocal : bufLocal.ptr
    }

}

searchArr(arr, bAddPy:=false, bDistinct:=false) {
    ;NOTE 转成二维 顺带 push 序号(以第1项为主,用来生成拼音什么的,所以用 push)
    if !arr.length
        return []
    if !isobject(arr[1]) { ;一维转成[hot, item]
        for k, v in arr
            arr[k] := [v, k]
    }
    arrNew := []
    ;去重(根据 subArr[1])
    if (bDistinct) { ;去重,并过滤空值
        obj := map()
        for subArr in arr {
            v := subArr[1]
            if (strlen(v) && !obj.has(v)) {
                arrNew.push(subArr)
            }
            obj[v] := ""
        }
    } else {
        arrNew := arr
    }
    ;添加标题
    arrField := ["序号"]
    for v in arrNew[1]
        arrField.push("v" . A_Index)
    ;添加拼音
    if (bAddPy) {
        arrField.push("拼音")
        for k, v in arrNew
            arrNew[k].push(v[1].shouzimus())
    }
    ;msgbox(json.stringify(arrField, 4))
    ;msgbox(json.stringify(arrNew, 4))
    ;添加到 Gui
    oGui := gui("+resize")
    oGui.OnEvent("escape",doEscape)
    oGui.OnEvent("close",doEscape)
    oGui.SetFont("s13")
    oGui.add("Text",,"按 F1-F12 或【双击】可直接确定对应条目")
    oEdit := oGui.add("Edit", "Lowercase section")
    oEdit.OnEvent("change", loadLV)
    oCB1 := oGui.Add("Checkbox", "yp checked", arrField[2])
    oCB2 := oGui.Add("Checkbox", "yp", arrField[3])
    ;添加按键显示结果(点击复制)
    oButton1 := oGui.add("button", "w200 xs cRed")
    oButton1.OnEvent("click", (ctl, p*)=>A_Clipboard := ctl.text)
    if (arrNew[1].length > 2) {
        oButton2 := oGui.add("button", "w500 yp xp+300 cRed")
        oButton2.OnEvent("click", (ctl, p*)=>A_Clipboard := ctl.text)
    }
    ;ListView 标题名
    ;field := 65
    oLv := oGui.AddListView("vlv1 xs r20 cRed w1400", arrField) ;NOTE selectN 要用 lv1 获取控件,不要用 oLv(影响释放)
    oLv.OnEvent("DoubleClick", do)
    oLv.OnEvent("ItemFocus", tips)
    tooltip("加载数据...")
    timeSave := A_TickCount
    obj := ""
    nLoad := A_TickCount - timeSave
    tooltip("添加到Gui...")
    loadLV(oEdit)
    nGui := A_TickCount - timeSave - nLoad
    tooltip
    oGui.title := format("读取耗时 {1} 加载到Gui耗时 {2}", nLoad,nGui)
    oGui.show()
    resGui := []
    OnMessage(WM_KEYDOWN:=0x100, selectN)
    WinWaitClose("ahk_id " . oGui.hwnd)
    return resGui
    doEscape(oGui, p*) {
        oGui.destroy()
        OnMessage(WM_KEYDOWN, selectN, 0)
    }
    loadLV(ctl, p*) { ;中文则搜索第1个内容,否则搜索第2个内容
        oLv.delete()
        oLv.opt("-Redraw")
        ;获取匹配项
        arrKeysMatch := []
        if oCB1.value
            arrKeysMatch.push(1)
        if oCB2.value
            arrKeysMatch.push(2)
        if !arrKeysMatch.length
            return
        sInput := ctl.text
        arrKeysMatch[1] := (bAddPy && sInput ~= "[[:ascii:]]") ? arrNew[1].length : 1
        i := 1
        for subArr in arrNew {
            for idx in arrKeysMatch {
                if (sInput=="" || instr(subArr[idx], sInput)) {
                    oLv.add(, i++, subArr*)
                    break
                }
            }
        }
        ;搜网址有用没结果且只搜索标题,则搜索网址
        ;if (oLv.GetCount() == 0) {
        ;    for subArr in arrNew {
        ;        if instr(subArr[2], sInput)
        ;            oLv.add(, i++, subArr[1], subArr[2], subArr[3])
        ;    }
        ;}
        oLv.ModifyCol(, "+AutoHdr +center")
        oLv.ModifyCol(1, "48")
        oLv.opt("+Redraw")
        if (oLv.GetCount() == 1) { ;单结果
            do(oLv, 1)
        } else if (oLv.GetCount() > 1) {
            oLv.modify(1, "+select")
            tips(oLv, 1)
        }
    }
    selectN(wParam, lParam, msg, hwnd) { ;NOTE 由于这个函数不传入oGui或oControl,要用 hwnd获取oGui,用oGui[ctlNmae]获取控件
        try
            oLv := GuiFromHwnd(hwnd, 1)["lv1"] ;NOTE
        catch
            return
        if (wParam == 13) { ;enter
            do(oLv, oLv.GetNext())
        } else if (wParam == 40) { ;down
            n := oLv.GetNext()
            if (!n)
                oLv.modify(1, "+select")
            else {
                oLv.modify(n, "-select")
                oLv.modify(n+1, "+select")
            }
        } else if (wParam == 38) { ;up
            n := oLv.GetNext()
            if (n>1) {
                oLv.modify(n, "-select")
                oLv.modify(n-1, "+select")
            }
        } else {
            r := wParam-111
            if (r >= 1 && r <= 12) ;F1-F12
                do(oLv, r)
        }
    }
    tips(oLv, r, p*) {
        ;获取当前行整行内容
        arrRes := []
        loop(arrNew.length)
            arrRes.push(oLv.GetText(r, A_Index))
        oButton1.text := arrRes[2]
        try
            oButton2.text := arrRes[3]
    }
    do(oLv, r, p*) { ;NOTE 要做的事
        ;获取当前行整行内容
        arrRes := []
        loop(arrNew[1].length)
            arrRes.push(oLv.GetText(r, A_Index+1))
        ;做任何事
        ;设置返回值
        resGui := arrRes
        doEscape(oLv.gui)
    }
}
posted @ 2022-04-04 15:29  火冷  阅读(482)  评论(0编辑  收藏  举报