win32 sdk樹形控件的項拖拽實現
本課中,我們將學習如何使用樹型視圖控件。另外還要學習如何在樹型視圖中完成拖-拉動作,以及如何使用圖象列表。
理論:
樹型視圖是一種特別的窗口,我們可以使用它一目了然地表示某種層次關系。譬如象在資源管理器中左邊窗口中的就是樹型視圖。您可以調用CreateWindowEx來創建樹型視圖,傳遞一個類名“"SysTreeView32"”,或者您也可以把它放到一個對話框中去。不要忘了在您的代碼中加入InitCommonControls函數。
樹型視圖有幾種特有的風格。下面是幾種經常使用的。
TVS_HASLINES == 在層次中用線條來連接各個項目名稱。
TVS_LINESATROOT == 在根目錄下的項目也用線連接。如果沒有指定TVS_HASLINES風格,該風格也就會被忽略。
像其它的通用控件一樣,樹型視圖用消息來完成通信。父窗口發送一系列的消息給樹型視圖,而樹型視圖發送"notification"消息給它的父窗口。在這方面,樹型視圖和其它的通用控制沒什么兩樣。
當有事件發生時,樹型視圖發送一個WM_NOTIFY消息個父窗口,并在消息中附帶傳遞一些附加信息。
wParam ==控件的ID。因為該值不是唯一的,故我們不用它。我們使用NMHDR結構體中的hwndFrom或IDFrom成員變量。
lParam == 指向NMHDR結構體的指針。有一些控件可能傳遞一個指向更大一點的結構體的指針。但該結構體必須保證它的第一個成員變量是一個NMHDR型的變量。這樣,您在處理lParam變量時,至少可以得到一個NMHDR型的變量。
下面我們來看NMHDR:
typedef struct tagNMHDR
{
HWND hwndFrom;
UINT idFrom;
UINT code;
}NMHDR;hwndFrom是發送WM_NOTIFY消息的控件的窗口句柄。
idFrom是發送WM_NOTIFY消息的控件的ID。
code是控件發送給父窗口的數據。
樹型視圖發送給父窗口的通知消息以TVN_打頭。 樹型視圖接收到的消息以TVM_打頭,譬如:TVM_CREATEDRAGIMAGE。樹型視圖發送TVN_XXX消息時在code變量中放入NMHDR型變量。父窗口發送TVM_消息來控制樹型視圖。
在樹型視圖中加入項目
在創建完樹型視圖后可以通過發送TVM_INSERTITEM消息往其中加入項目了。
TVM_INSERTITEM
wParam = 0;
lParam = 指向結構體TV_INSERTSTRUCT的指針;
您應當知道一些關于樹型視圖中的項目之間關系的一些術語。一個項目可能是一個父親、兒子或兩者都是。父項目下含有子項目,而該父項目又有可能是其它項目的子項目。一個沒有父項目的項目叫根項目。在樹型視圖中可能有多個根項目。現在我們來看看TV_INSERTSTRUCT結構體:
typedef struct tagTVINSERTSTRUCT { HTREEITEM hParent; HTREEITEM hInsertAfter; #if (_WIN32_IE >= 0x0400) union { TVITEMEX itemex; TVITEM item; } DUMMYUNIONNAME; #else TVITEM item; #endif } TVINSERTSTRUCT, *LPTVINSERTSTRUCT;
hParent = 父項目的句柄。如果該值為TVI_ROOT value或NULL,該項目插在樹型視圖的根部。
hInsertAfter = 應該插入在起后面的項目的句柄或下面的值:
- TVI_FIRST ==> 插在列表的頭部。
- TVI_LAST ==> 插在列表的尾部。
- TVI_SORT ==> 按字母順序插入。
union { TVITEMEX itemex; TVITEM item; } DUMMYUNIONNAME;
我們僅使用TVITEM。
typedef struct tagTVITEM {
UINT mask;
HTREEITEM hItem;
UINT state;
UINT stateMask;
LPTSTR pszText;
int cchTextMax;
int iImage;
int iSelectedImage;
int cChildren;
LPARAM lParam;
} TVITEM, *LPTVITEM;
該結構體根據消息類型,用來發送或接收關于一個樹型視圖的項目的有關信息。譬如:對于消息TVM_INSERTITEM,它用來指定插入樹型視圖控件的項目的屬性。而對于消息TVM_GETITEM,該結構體用來填充關于選定項目的信息。
imask 用來指定TV_ITEM的那些成員變量有效。譬如,如果指定了TVIF_TEXT,這意味著pszText成員變量是有效的。您可以同時指定幾個標志位。
hItem 是樹型視圖項目的句柄。每一個項目都有它自己的句柄,就像窗口一樣。如果您想要操作一個項目,就必須選擇它的句柄。
pszText 是一個字符串指針。它是項目的標簽名。
cchTextMax僅在查詢項目的名稱時使用。由于在pszText中指定了指針,WINDOWS還要知道該緩沖去的大小。所以您必須給出該值。
iImage 和 iSelectedImage用來指定圖象列表以及一個索引號。這樣就知道當項目被選中或沒被選中時用哪個圖象來表示該項目。像資源管理器中左邊窗口中的文件夾等小圖表就是有這兩個參數來決定的。
為了在樹型視圖中插入一個項目,您必須至少設定hParent, hInsertAfter,另外您還要設定imask和pszText值。
把圖形加到圖形視圖中
如果您想要在項目的名稱左邊顯示圖標的話,您必須創建一個圖形列表,并且把它和樹形視圖相關聯起來。您可以調用ImageList_Create來創建一個圖形列表。
HIMAGELIST ImageList_Create( int cx, int cy, UINT flags, int cInitial, int cGrow );
如果創建成功的話,該函數返回一個空的圖象列表的句柄。
cx == 以像素為單位的圖象的寬度。
cy == 以像素為單位的圖象的高度。圖象列表中的每一幅的高度都必須相同。否則WINDOWS會對您的圖象進行裁剪,如果過大的話就可能裁剪成幾小塊。所以您必須指定相同大小的圖象。
flags == 指定圖象列表的圖象的顏色深度。詳細情況請參考WIN32 API 指南。
cInitial == 指定包含的圖象的數目。WINDWOS將依此來分配合適的內存。
cGrow == 在增加新圖象是一次增加的數目。
圖象列表不是窗口。僅僅是保存在那給其它的窗口使用的一種資源。在圖象列表產生后,您可以調用ImageList_Add來向其中加入圖象。
int ImageList_Add(
HIMAGELIST himl,
HBITMAP hbmImage,
HBITMAP hbmMask
);
如果該函數調用失敗的話,返回-1。
himl == 圖象列表的句柄。它是調用ImageList_Create時返回的值。
hbmImage == 加入圖象列表的位圖的句柄。您通常把位圖保存在資源中,然后調用LoadBitmap來把它加載進來。注意您沒有必要指定該位圖中包含的圖象的數目。WINDOWS會根據它的大小,自動計算。
hbmMask == 掩碼位圖的句柄。如果沒有使用掩碼位圖,可以忽略該值。通常我們加入兩種圖象到圖象列表中。一種時被選中時顯示的圖象,另一種時沒被選中時顯示的。
當圖象列表準備就緒后,您可以發送消息TVM_SETIMAGELIST給樹型視圖來讓圖象列表和樹型視圖聯系起來。
wParam = 圖象列表的狀態,一共有兩種:
- TVSIL_NORMAL 包含被選中和沒有被選中兩種狀態的圖象。
- TVSIL_STATE 包含了用戶自定義的狀態的圖象。
檢索樹型視圖的信息
您可以通過發送消息TVM_GETITEM來檢索圖形視圖的信息。
TVM_GETITEM
wParam = 0
lParam =指向結構體TV_ITEM的指針。該結構體將用來得到相關的信息。
在發送該消息前必須設置成員變量imask的值,以便WINDOWS能告訴相關的信息。當然,最重要的是,您必須傳遞您想得到信息的項目的句柄。這就引起了一個問題,您如何得到項目的句柄?要保存所有項目的句柄嗎?
答案是很簡單的:沒有必要。您可以發送消息TVM_GETNEXTITEM到樹型視圖以檢索您想要得到其屬性的項目的句柄。譬如:您可以查詢第一個子項目的句柄、根目錄的句柄、選中的項目的句柄等等。
wParam = 標志
lParam = 樹型視圖的句柄(僅僅當wParam的值是某些標志位時才是必須的)。
wParam中的值非常重要, 我解釋如下:
- TVGN_CARET 選中的項目
- TVGN_CHILD hitem參數指定項目的第一個子項目
- TVGN_DROPHILITE 拖-拉操作的目的項目
- TVGN_FIRSTVISIBLE 第一個可見項目
- TVGN_NEXT 下一個同級項目
- TVGN_NEXTVISIBLE 下一個可見項目,指定的項目必須可見。發送消息TVM_GETITEMRECT 來決定項目是否可見
- TVGN_PARENT 指定項目的父項目
- TVGN_PREVIOUS 前一個同級項目
- TVGN_PREVIOUSVISIBLE 前一個可見項目,指定的項目必須可見。發送消息TVM_GETITEMRECT 來決定項目是否可見
- TVGN_ROOT 根項目
由此您可以通過發送該消息來得到項目的句柄,然后在發送消息TVM_GETITEM時在結構體變量TV_ITEM的成員變量hItem中放入該項目的句柄就可以得到關于該項目的有關信息了。
在樹型視圖中進行拖-拉操作
也就是因為這一部分我才決定寫這課教程。當我按照InPrise公司的WIN32幫助來運行例子時,發現它的幫助中缺少真正重要的信息。我只有通過自己做實驗,最后總算弄明白來個中來由。希望您不要和我一樣再去走這些彎路,下面我把我所知的在樹型視圖中進行拖-拉操作的步驟描述如下:
- 當用戶要拖動一個項目時,樹型視圖控件會給它的父窗口發送TVN_BEGINDRAG通知消息。您可以在此處創建表示項目處在拖動操作中的圖象,這可以通過發送TVM_CREATEDRAGIMAGE消息給樹型視圖,讓其為目前使用的圖象產生一副缺省的圖象來實現。樹型視圖控件將創建一個圖象列表,其中僅包含一副在拖動中顯示的圖象,圖象列表創建后,您可以得到它的句柄。
- 在拖拉的圖象生成后,您可以通過調用ImageList_BeginDrag來指定拖動圖象的熱點位置。
- BOOL ImageList_BeginDrag(
HIMAGELIST himlTrack, int iTrack, int dxHotspot, int dyHotspot );
himlTrack 是包含了拖拉時顯示的圖象的圖象列表的句柄
iTrack 是選中的圖象在圖象列表中的索引號。
dxHotspot 因為在拖動中該圖象被用來取代光標,所以我們必須指定圖象中的哪一點是光標的左上角的位置。dxHotspot是水平相對位置。
dyHotspot 是垂直相對位置。
iTrack等于0。如果您要想光標的熱點在拖拉中顯示的圖象的左上角,把dxHotspot和dyHotspot都設成0。 - 當拖拉的圖象要顯示時,我們調用ImageList_DragEnter 在樹型視圖中顯示該圖象。
- BOOL ImageList_DragEnter(
HWND hwndLock, int x, int y );
hwndLock 是進行拖拉中的窗口的句柄,拖拉的動作限制在該窗口中。
x 和 y是在拖拉時顯示圖象的初始位置的坐標值。這些值是相對于窗口的左上角而不是客戶區的左上角。 - 既然可以顯示拖動中的圖象了,我們就要處理拖動操作了。在這里有一個小問題。我們監視拖動是通過監視鼠標光標的移動來實現的,譬如在移動時我們通過捕獲WM_MOUSEMOVE消息來得到移動中的坐標位置,通過捕獲WM_LBUTTONUP消息來獲知用戶的放下操作。但這時如果鼠標光標移過子窗口時父窗口就無法再得到鼠標光標的移動以及鼠標的按鍵消息了。解決辦法是調用SetCapture函數了鎖定鼠標事件,這樣無論鼠標移到那里和有什么動作,我們的窗口都可以知道了。
- 在處理WM_MOUSEMOVE消息時,您可以調用ImageList_DragMove來更新圖象移動的軌跡。該函數可以移動拖放操作中的圖象位置。另外,如果您想讓移動中的圖象經過某些項目時高量度顯示,可以調用TVM_HITTEST 來確定是否經過某個項目的上面。如果是的話,您可以發送TVM_SELECTITEM消息并設置 TVGN_DROPHILITE標志位使得那個項目高亮度顯示。注意:在發送消息TVM_SELECTITEM前,您必須先隱藏圖象列表,否則會留下非常難看的軌跡。要隱藏拖動中的圖象可以調用ImageList_DragShowNolock,在顯示完高亮度的圖象后再調用該函數以讓拖動中的圖象再正常顯示。
- 當用戶釋放主鍵后,您必須做幾件事。如果您在高亮度顯示的時候釋放鼠標主鍵(表示您想把該項目加到此處),您必須使該項目變成正常地顯示,這可以通過發送消息TVM_SELECTITEM消息并設置標志位TVGN_DROPHILITE來實現,只是這時lParam必須為0。如果您不讓高亮度顯示的項目恢復正常,那就會發生一個奇怪的現象:當您再選擇另外的項目時,那個項目的圖象會包含在一個正方形中,當時高亮度顯示的項目依舊是上一個項目。接下來必須調用ImageList_EndDrag和ImageList_DragLeave。還有調用ReleaseCapture來釋放捕獲的鼠標。如果您創建了一個圖象列表,那還要調用calling ImageList來將它銷毀,在拖放操作結束后您可以進行另外其它的操作。
例子代碼:見光盤FirstWindow17
#include "Windows.h"
#include "tchar.h"
#include "commctrl.h"
#pragma comment(lib,"comctl32.lib")
#define IDB_TREE 4006
TCHAR ClassName[] = _T("TreeViewWinClass");
TCHAR AppName[] = _T("Tree View Demo");
TCHAR TreeViewClass[] = _T("SysTreeView32");
TCHAR Parent[] = _T("Parent Item");
TCHAR Child1[] = _T("Child1");
TCHAR Child2[] = _T("Child2");
BOOL DragMode = FALSE;
HINSTANCE g_hInstance;
HWND hwndTreeView;
HTREEITEM hParent;
HIMAGELIST hImageList;
HIMAGELIST hDragImageList;
INT_PTR CALLBACK ProcWinMain( HWND hWnd,
UINT Msg,
WPARAM wParam,
LPARAM lParam
)
{
TV_INSERTSTRUCT tvinsert;
HBITMAP hBitmap;
TV_HITTESTINFO tvhit;
switch(Msg)
{
case WM_CREATE:
hwndTreeView = CreateWindowEx(NULL,TreeViewClass,NULL,WS_CHILD|WS_VISIBLE|TVS_HASLINES|TVS_HASBUTTONS|TVS_LINESATROOT,0,0,200,400,hWnd,NULL,
g_hInstance,NULL);
hImageList = ImageList_Create(16,16,ILC_COLOR16,2,10);
hBitmap = LoadBitmap(g_hInstance,MAKEINTRESOURCE(IDB_TREE));
ImageList_Add(hImageList,hBitmap,NULL);
DeleteObject(hBitmap);
SendMessage(hwndTreeView,TVM_SETIMAGELIST,0,(LPARAM)hImageList);
tvinsert.hParent = NULL;
tvinsert.hInsertAfter = TVI_ROOT;
tvinsert.item.mask = TVIF_TEXT|TCIF_IMAGE|TVIF_SELECTEDIMAGE;
tvinsert.item.pszText = Parent;
tvinsert.item.iImage = 0;
tvinsert.item.iSelectedImage = 1;
hParent = (HTREEITEM) SendMessage(hwndTreeView,TVM_INSERTITEM,0,(LPARAM)&tvinsert);
tvinsert.hParent = hParent;
tvinsert.hInsertAfter = TVI_LAST;
tvinsert.item.pszText = Child1;
SendMessage(hwndTreeView,TVM_INSERTITEM,0 ,(LPARAM)&tvinsert);
tvinsert.item.pszText = Child2;
SendMessage(hwndTreeView,TVM_INSERTITEM,0,(LPARAM)&tvinsert);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_MOUSEMOVE:
if(DragMode)
{
tvhit.pt.x = LOWORD(lParam);
tvhit.pt.y = HIWORD(lParam);
ImageList_DragMove(tvhit.pt.x,tvhit.pt.y);
ImageList_DragShowNolock(FALSE);
HTREEITEM hTmp = (HTREEITEM)SendMessage(hwndTreeView,TVM_HITTEST,NULL,(LPARAM)&tvhit);
if(hTmp != NULL)
SendMessage(hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,(LPARAM)hTmp);
ImageList_DragShowNolock(TRUE);
}
break;
case WM_LBUTTONDOWN:
if(DragMode)
{
ImageList_DragLeave(hwndTreeView);
ImageList_EndDrag();
ImageList_Destroy(hDragImageList);
HTREEITEM hTmp = (HTREEITEM)SendMessage(hwndTreeView,TVM_GETNEXTITEM,TVGN_DROPHILITE,0);
SendMessage(hwndTreeView,TVM_SELECTITEM,TVGN_CARET,(LPARAM)hTmp);
SendMessage(hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,0);
ReleaseCapture();
DragMode = FALSE;
}
break;
case WM_NOTIFY:
{
NM_TREEVIEW *pTree = (NM_TREEVIEW *)lParam;
if(pTree->hdr.code == TVN_BEGINDRAG)
{
hDragImageList = (HIMAGELIST ) SendMessage(hwndTreeView,TVM_CREATEDRAGIMAGE,0,(LPARAM)pTree->itemNew.hItem);
ImageList_BeginDrag(hDragImageList,0,0,0);
ImageList_DragEnter(hwndTreeView,pTree->ptDrag.x,pTree->ptDrag.y);
SetCapture(hWnd);
DragMode = TRUE;
}
}
break;
default:
return DefWindowProc(hWnd,Msg,wParam,lParam);
}
return 0;
}
int WINAPI WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
)
{
WNDCLASSEX wc;
MSG msg;
HWND hWnd;
g_hInstance = hInstance;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = ProcWinMain;
wc.cbClsExtra = NULL;
wc.cbWndExtra = NULL;
wc.hInstance = hInstance;
wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE);
wc.lpszMenuName = NULL;
wc.lpszClassName = ClassName;
wc.hIcon = wc.hIconSm = LoadIcon(NULL,IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL,IDC_ARROW);
RegisterClassEx(&wc);
hWnd = CreateWindowEx(WS_EX_CLIENTEDGE,ClassName,AppName,WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,CW_USEDEFAULT,200,400,NULL,NULL,hInstance,NULL);
ShowWindow(hWnd,SW_SHOWNORMAL);
UpdateWindow(hWnd);
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
InitCommonControls();
}