SysListView321控制元件查詢並定位
阿新 • • 發佈:2022-04-04
SysListView321控制元件在windows裡相當普遍,比如【程式和功能】頁面的已安裝軟體列表,
因為這種控制元件無法搜尋,如果內容一多,給定位帶來困難,於是有了如下指令碼。
具體邏輯是:
- 提取控制元件的所有內容
- 用gui介面寫個查詢功能,並確定要查詢的內容
- 用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) } }