《金山詞霸2002》中的附錄收集了很多古詩,有時為了尋找一篇古詩,得找很久很久(俺文科很差)。觀察其附錄的結構,發現是個Tree-View控件,如果能查找裡面的項目該有多好。
要查找首先得遍歷,連范圍都確定不好何談查找?所以本篇分兩部分進行講解:第一部分解決遍歷的問題;第二部分解決查找指定項目的問題。
第一部分:遍歷外部程序Tree-View中的項目
一:程序說明:
如圖一所示Tree-View控件的典型結構圖,我們將按照圖示的順序來遍歷其中的項目。
圖一
翻閱SDK手冊中關於Tree-View控件的相關章節,發現了幾個有用的消息:
TVM_GETNEXTITEM:得到項目的句柄(參數:TVGN_ROOT得到根句柄,TVGN_NEXTVISIBLE得到下一個可見項目的句柄);
TVM_EXPAND:展開或折疊指定項目(參數:TVE_EXPAND展開指定項目);
TVM_SELECTITEM:選中指定項目。
利用這些消息和SendMessage()函數,我們可以很容易寫出遍歷代碼。
二:具體實踐
在本文所提供的DEMO中,有一段將十六進制字符串轉換成十進制無符號長整型的代碼,作用是將用戶輸入的十六進制TV句柄值轉換成十進制並存放在變量dec_sum中。此代碼不列入本文討論的范疇,大家不閒弱智的話就將就著用吧。下面是實現遍歷功能的關鍵代碼:
/* Tree-View Control_Demo_SeqShow 1.0 版
三:TV_Demo_SeqShow的使用方法(圖2):
* 版權所有 (C) 2006 天津 趙春生
* 2006.08.28
* http://timw.yeah.net
* http://timw.126.com
* 本程序能順序遍歷TV控件中的所有項目。
* 代碼在Win2000P+SP4 + VC6+SP6測試通過。
*/
if(error==0)//如果在數據驗證轉換的過程中未出現錯誤(error==0時無錯誤)
{
//下面為核心部分:順序顯示(選中)指定Tree-View控件中的所有Item.
hwnd=HWND(dec_sum);//得到轉換後的數據
//得到根句柄
tvitem.hItem=(HTREEITEM)::SendMessage(hwnd, TVM_GETNEXTITEM,TVGN_ROOT, 0x0);
::SendMessage(hwnd, TVM_SELECTITEM,TVGN_CARET, (long)tvitem.hItem);//選中狀態
while((long)tvitem.hItem)
{
//當此項目能展開時
while(::SendMessage(hwnd, TVM_EXPAND,TVE_EXPAND, (long)tvitem.hItem))
{
//選擇下一個可見項目
tvitem.hItem=(HTREEITEM)::SendMessage(hwnd,
TVM_GETNEXTITEM,TVGN_NEXTVISIBLE,
(long)tvitem.hItem);
//選中狀態
::SendMessage(hwnd, TVM_SELECTITEM,TVGN_CARET, (long)tvitem.hItem);
continue;
}
//當不能再展開的時候,選擇下一個可見項目
tvitem.hItem=(HTREEITEM)::SendMessage(hwnd,
TVM_GETNEXTITEM,TVGN_NEXTVISIBLE,
(long)tvitem.hItem);
//選中狀態
::SendMessage(hwnd, TVM_SELECTITEM,TVGN_CARET,
(long)tvitem.hItem);
}
}
//釋放內存
CloseHandle(hwnd);
//順序顯示(選中)完畢
圖二
用SPY++的[Find Window]功能獲得目標TV的句柄;
將句柄值輸入到TV_Demo_SeqShow中的[Tree-View Control''s Handle:];
點擊[GO!];
如果你把[Windows 資源管理器]中的[文件夾]作為目標,那你可要作好心理准備了……如果實在忍受不了這種刺激,干脆把管理器關掉就可以了。
第二部分:查找外部程序Tree-View中的項目
一:程序說明:
我們已經成功得對外部程序Tree-View中的項目進行了遍歷,如果能在遍歷的過程中讀取每一個項目的名稱,結合我們給定的項目名進行比較,那麼查找某個項目的問題將變得易如反掌。由此可見:關鍵的問題是如何讀取項目的名稱。
讀取項目的名稱要發送TVM_GETITEM消息,由於該消息需要為LPARAM參數提供一個TV_ITEM結構的地址,在跨進程發送消息的前提下,為了使外部程序正常使用該內存地址,所以我們必須將TV_ITEM結構插入到目標進程的地址空間中去,代碼如下:
ptvitem=(TVITEM*)VirtualAllocEx(hProcess,NULL,sizeof(TVITEM),MEM_COMMIT,PAGE_READWRITE);//分配內存
在寫入內存之前,要將TV_ITEM結構配置好:
WriteProcessMemory(hProcess,ptvitem,&tvitem,sizeof(TVITEM),NULL);//寫入內存 tvitem.mask=TVIF_TEXT;
mask要設置成TVIF_TEXT,因為我們需要的是pszText的值;cchTextMax可以設置得稍微大一些,cchTextMax=512即可;hItem的值用來指定究竟哪個項目來接收TVM_GETITEM消息,該值在遍歷的過程中動態獲得;重要的是用來存放項目名稱的緩沖區地址,即pszText參數的設置:和TV_ITEM結構一樣,也要把她插入到目標進程的地址空間中去:
tvitem.cchTextMax=512;
tvitem.pszText=pItem;pItem=(char*)VirtualAllocEx(hProcess,NULL,16,MEM_COMMIT,PAGE_READWRITE);
二:具體實踐:
作為演示,下面的這段程序將在我們指定的Tree-View控件中查找我們需要的項目,在發現第一個部分匹配的項目後,程序將停止運行,不再進行查找操作。作為演示程序,程序並沒有做速度上的優化,大家在具體應用的過程中可自行修改。程序找到目標後的效果圖(圖 三):
/* Tree-View Control_Demo_SeqSearch 1.0 版
結束語
* 版權所有 (C) 2006 天津 趙春生
* 2006.08.28
* http://timw.yeah.net
* http://timw.126.com
* 本程序能按用戶指定的項目名稱順序查找TV控件中的項目。
* 代碼在Win2000P+SP4 + VC6+SP6測試通過。
*/
if(error==0)//如果在數據驗證轉換的過程中未出現錯誤(error==0時無錯誤)
{
//下面為核心部分:按用戶指定的項目名稱順序查找Tree-View控件中的Item.
hwnd=HWND(dec_sum);//得到轉換後的數據
GetWindowThreadProcessId(hwnd, &PID);
hProcess=OpenProcess(PROCESS_ALL_ACCESS,false,PID);
if (!hProcess)
MessageBox("獲取進程句柄操作失敗!","錯誤!");
else
{
ptvitem=(TVITEM*)VirtualAllocEx(hProcess,
NULL,
sizeof(TVITEM),
MEM_COMMIT,
PAGE_READWRITE);
pItem=(char*)VirtualAllocEx(hProcess,
NULL,
16,
MEM_COMMIT,
PAGE_READWRITE);
if (!ptvitem)
MessageBox("無法分配內存!","錯誤!");
else
{
MessageBox("本演示程序將按用戶指定的項目名稱順序查找。","提示");
tvitem.mask=TVIF_TEXT;
tvitem.cchTextMax=512;
tvitem.pszText=pItem;
//得到根句柄
tvitem.hItem=(HTREEITEM)::SendMessage(hwnd,
TVM_GETNEXTITEM,
TVGN_ROOT,
0x0);
//選中狀態
::SendMessage(hwnd,
TVM_SELECTITEM,
TVGN_CARET,
(long)tvitem.hItem);
//將設置好的結構插入目標進程
WriteProcessMemory(hProcess,
ptvitem,
&tvitem,
sizeof(TVITEM), NULL);
//發送TVM_GETITEM消息
::SendMessage(hwnd,
TVM_GETITEM,
0,
(LPARAM)ptvitem);
//獲取pszText
ReadProcessMemory(hProcess, pItem, ItemBuf, 512, NULL);
//MessageBox(ItemBuf,"ITEM TEXT");
if( strnicmp( ItemBuf,
str_item_text,
strlen(str_item_text) ) == 0)
{
MessageBox("已經找到!","恭喜");
Bingo=1;
//如果根就是我們要找的目標,那麼程序執行到這裡就可以結束了。
tvitem.hItem=(HTREEITEM)0x0;
}
while((long)tvitem.hItem)
{
//當此項目能展開時
while(::SendMessage(hwnd,
TVM_EXPAND,
TVE_EXPAND,
(long)tvitem.hItem))
{
//選擇下一個可見項目
tvitem.hItem=(HTREEITEM)::SendMessage(hwnd,
TVM_GETNEXTITEM,TVGN_NEXTVISIBLE,
(long)tvitem.hItem);
//選中狀態
::SendMessage(hwnd,
TVM_SELECTITEM,TVGN_CARET,
(long)tvitem.hItem);
//將設置好的結構插入目標進程
WriteProcessMemory(hProcess,
ptvitem,
&tvitem,
sizeof(TVITEM),
NULL);
//發送TVM_GETITEM消息
::SendMessage(hwnd,
TVM_GETITEM,
0,
(LPARAM)ptvitem);
//獲取pszText
ReadProcessMemory(hProcess,
pItem,
ItemBuf,
512,
NULL);
//MessageBox(ItemBuf,"ITEM TEXT");
if( strnicmp( ItemBuf,
str_item_text,
strlen(str_item_text) ) == 0)
{
MessageBox("已經找到!","恭喜");
Bingo=1;
//如果發現我們要找的目標,那麼程序執行到這裡就可以結束了。
tvitem.hItem=(HTREEITEM)0x0;
break;
}
continue;
}
if(Bingo!=1)
{
//當不能再展開的時候,選擇下一個可見項目
tvitem.hItem=(HTREEITEM)::SendMessage(hwnd,
TVM_GETNEXTITEM,TVGN_NEXTVISIBLE,
(long)tvitem.hItem);
//選中狀態
::SendMessage(hwnd,
TVM_SELECTITEM,
TVGN_CARET,
(long)tvitem.hItem);
//將設置好的結構插入目標進程
WriteProcessMemory(hProcess,
ptvitem,
&tvitem,
sizeof(TVITEM),
NULL);
//發送TVM_GETITEM消息
::SendMessage(hwnd,
TVM_GETITEM,
0,
(LPARAM)ptvitem);
ReadProcessMemory(hProcess,
pItem,
ItemBuf,
512,
NULL);//獲取pszText
//MessageBox(ItemBuf,"ITEM TEXT");
if( strnicmp( ItemBuf,
str_item_text,
strlen(str_item_text) ) == 0)
{
MessageBox("已經找到!","恭喜");
Bingo=1;
//如果發現我們要找的目標,那麼程序執行到這裡就可以結束了。
tvitem.hItem=(HTREEITEM)0x0;
break;
}
}
}
}
}
}
//釋放內存
CloseHandle(hwnd);
CloseHandle(hProcess);
VirtualFreeEx(hProcess, ptvitem, 0, MEM_RELEASE);
//順序查找完畢
代碼寫得不夠幽雅,大家見笑了。在此之前,類似的拙文我已經寫了四篇,希望大家看完後能舉一反三。謝謝。