首先,我們要創建一個基本對話框的MFC工程MFC_TreeCRTL(名字隨便給一個)。然後在資源視圖中插入兩個Dialog,ID分別爲IDD_DIALOG11和IDD_DIALOG211,都更改Style屬性爲Child,Border屬性爲None,爲它們建立兩個類,分別命名爲Cdialog11和Cdialog211,並在MFC_TreeCRTLDlg.CPP文件中包含dialog11.h和dialog211.h兩個頭文件。再導入幾個資源圖標作爲樹形控件節點的圖標及裝飾面板。最後在主面板上添加一個CTreeCtrl控件,ID爲默認,並在ClassWizard中添加它的一個變量,命名爲m_mytree。
接着,我們進行具體代碼編寫。
我們必須在CMFC_TreeCRTLDlg類中加入這些變量和函數
CDialog * m_treePages[2]; CString node_name; BOOL InitMytree();
我們還要在CMFC_TreeCRTLDlg類的構造函數中爲m_treePages[2]分配空間,
m_treePages[0]=new Cdialog11; m_treePages[1]=new Cdialog211;
InitMytree()函數爲m_mytree的初始化過程
BOOL CMFC_TreeCRTLDlg::InitMytree() { //節點的圖標 int i=0; int i_count=2; //載入圖標 HICON icon[4]; icon[0]=AfxGetApp()->LoadIcon (IDI_ICON6); icon[1]=AfxGetApp()->LoadIcon (IDI_ICON7); //創建圖像列表控件 CImageList *m_imagelist=new CImageList; m_imagelist->Create(16,16,0,7,7); m_imagelist->SetBkColor (RGB(255,255,255)); for(int n=0;n<i_count;n++) { m_imagelist->Add(icon[n]); //把圖標載入圖像列表控件 } m_mytree.SetImageList(m_imagelist,TVSIL_NORMAL); //爲m_mytree設置一個圖像列表,使CtreeCtrl的節點顯示不同的圖標 m_mytree.SetBkColor(RGB(0,250,255));//設置m_mytree的背景色 //創建節點 //父節點 HTREEITEM root0=m_mytree.InsertItem("Dialog1",0,1,TVI_ROOT,TVI_LAST); HTREEITEM root1=m_mytree.InsertItem("Dialog2",0,1,TVI_ROOT,TVI_LAST); //一層子節點 HTREEITEM sub_son0=m_mytree.InsertItem("Dialog 1-1",0,1,root0,TVI_LAST); HTREEITEM sub_son1=m_mytree.InsertItem("Dialog 2-1",0,1,root1,TVI_LAST); //二層孫子節點 HTREEITEM sub_m_son0=m_mytree.InsertItem("Dialog 2-1-1",0,1,sub_son1,TVI_LAST); //建立節點對應的Dialog m_treePages[0]->Create(IDD_DIALOG11,this); m_treePages[1]->Create(IDD_DIALOG211,this); m_treePages[0]->ShowWindow(SW_SHOW); m_treePages[1]->ShowWindow(SW_HIDE); //把Dialog移到合適位置 CRect m_rect; GetClientRect(m_rect); m_rect.left=200; m_treePages[0]->MoveWindow(m_rect); m_treePages[1]->MoveWindow(m_rect); return true; }
始初化完成後,我們要添加CTreeCtrl的消息響應事件,這樣才能讓它按我們的要求起作用。我們打開Class Wizard點選IDC_TREE1添加TVN_SELCHANGED消息,並在消息響應函數中寫入代碼。
void CMFC_TreeCRTLDlg::OnSelchangedTree1(NMHDR* pNMHDR, LRESULT* pResult) { NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR; // TODO: Add your control notification handler code here UpdateData(true); node_name=m_mytree.GetItemText(pNMTreeView->itemNew.hItem); //在標題欄顯示節點信息 SetWindowText(node_name); //切換面板 if(node_name=="Dialog 1-1"){ m_treePages[0]->ShowWindow(SW_SHOW); m_treePages[1]->ShowWindow(SW_HIDE); } else if(node_name=="Dialog 2-1-1"){ m_treePages[0]->ShowWindow(SW_HIDE); m_treePages[1]->ShowWindow(SW_SHOW); } UpdateData(false); *pResult = 0; }
最後,我們在 CMFC_TreeCRTLDlg::OnInitDialog()初始化函數裏調用InitMytree()函數。程序運行效果
到這裏爲止,我們就把一個Dialog粘貼到了主Dialog上了,通過CTreeCtrl控件的節點的變化,讓不同的Dialog交替地粘貼在主Dialog上,從而方便於我們只用少數的窗口,調用更多的功能模塊,不必再爲每個模塊都作爲彈出窗口,而顯得繁雜。
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
遍歷和查找外部程序 Tree-View 中的項目
天津 趙春生
《金山詞霸2002》中的附錄收集了很多古詩,有時爲了尋找一篇古詩,得找很久很久(俺文科很差)。觀察其附錄的結構,發現是個Tree-View控件,如果能查找裏面的項目該有多好,可這個功能軟件本身卻並沒有提供(不知道現在最新的版本是否已經提供了這個功能,如果沒有,趕快加上吧,順便獎勵俺一套該產品的最新版,哈哈)……問題出來了:我們要編寫一個程序,讓她在外部程序中的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 版 * 版權所有 (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); //順序顯示(選中)完畢
三:TV_Demo_SeqShow的使用方法(圖2):
圖二
- 用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);//分配內存 WriteProcessMemory(hProcess,ptvitem,&tvitem,sizeof(TVITEM),NULL);//寫入內存
在寫入內存之前,要將TV_ITEM結構配置好:
tvitem.mask=TVIF_TEXT; tvitem.cchTextMax=512; tvitem.pszText=pItem;
mask要設置成TVIF_TEXT,因爲我們需要的是pszText的值;cchTextMax可以設置得稍微大一些,cchTextMax=512即可;hItem的值用來指定究竟哪個項目來接收TVM_GETITEM消息,該值在遍歷的過程中動態獲得;重要的是用來存放項目名稱的緩衝區地址,即pszText參數的設置:和TV_ITEM結構一樣,也要把她插入到目標進程的地址空間中去:
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); //順序查找完畢