VC++學習:用SDK實現分隔條
[2004-6-12]
Windows中的分隔條是一種被廣泛使用的控制項,絕大多數Explorer式樣的應用程式都使用了這種控制項。然而卻很少有相關的資料對它的完整實現進行介紹,於是我自己實現了一個,希望對SDK的愛好者們有所協助。
事實上,分隔條也是一個很普通的視窗,它也擁有自己的視窗類別、自己的視窗過程——就像所有的預定義控制項一樣。也就是說,要建立一個分隔條,也需要進行視窗類別的註冊和視窗的建立。以下的範例程式碼示範了如何註冊一個分隔條的視窗類別:
WNDCLASS wc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW; // 1
wc.hCursor = LoadCursor(NULL, IDC_SIZEWE); // 2
wc.hIcon = NULL;
wc.hInstance = hInstance;
wc.lpfnWndProc = (WNDPROC)ProcSplitter; // 3
wc.lpszClassName = "MySplitter"; // 4
wc.lpszMenuName = NULL;
wc.style = 0;
RegisterClass(&wc);
這段代碼相信大家已經很熟悉了,所以在此我只簡要說明四點:1、分隔條的背景顏色,這裡我取預設的對話方塊背景色;2、分隔條的滑鼠指標,這裡我取水平的調整大小指標;3、這是分隔條的視窗過程,所有的秘密都在這個回呼函數之中;4、分隔條的視窗類別名稱,你可以隨便取一個你喜歡的名字。
在成功地註冊視窗類別之後,就可以建立分隔條了。以下是我的樣本介面,它由一個樹形視圖、一個分隔條、一個列表視圖以及一個狀態列組成,下文的所有代碼都是以這個介面為基礎的。
在編寫分隔條的視窗過程之前,我先來處理對話方塊的WM_SIZE訊息作為分隔條視窗過程的一個熱身。代碼如下(你會發現在整個的代碼中我沒有對hTree、hStatus、hSplitter以及hList做任何的聲明,那是因為對於這個簡單的樣本,我將所有的這些東西都聲明為了全域變數):
case WM_SIZE:
{
HDWP hdwp;
RECT rect, rectStatus, rectTree;
hdwp = BeginDeferWindowPos(4);
GetClientRect(hDlg, &rect);
GetClientRect(hStatus, &rectStatus);
GetWindowRect(hTree, &rectTree);
DeferWindowPos(hdwp, hStatus, NULL, 0, rect.bottom - rectStatus.bottom, rect.right, rectStatus.bottom, SWP_NOZORDER);
DeferWindowPos(hdwp, hTree, NULL, 0, 0, rectTree.right - rectTree.left, rect.bottom - rectStatus.bottom, SWP_NOMOVE | SWP_NOZORDER);
DeferWindowPos(hdwp, hSplitter, NULL, rectTree.right - rectTree.left, 0, 2, rect.bottom - rectStatus.bottom, SWP_NOZORDER);
DeferWindowPos(hdwp, hList, NULL, rectTree.right - rectTree.left + 2, 0, rect.right - rectTree.right + rectTree.left - 2, rect.bottom - rectStatus.bottom, SWP_NOZORDER);
EndDeferWindowPos(hdwp);
}
break;
你肯定已經注意到了,這段代碼的大部分篇幅都是在和矩形做遊戲。的確是這樣,因為調整視窗大小的過程就是一個改變各個子視窗的位置和大小的過程。這個過程用語言敘述就是:1、首先,將狀態列放置在對話方塊的最下方;2、第二步,不改變樹形視圖的位置和寬度,重設它的高度;3、不改變分隔條的位置和寬度,重設它的高度;4、使列表視圖佔滿剩餘的客戶區。
如果你弄懂了上面的代碼,那麼分隔條的視窗過程也就沒有任何難度了:
LRESULT CALLBACK ProcSplitter(HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch (Msg)
{
case WM_LBUTTONDOWN:
SetCapture(hwnd);
break;
case WM_LBUTTONUP:
ReleaseCapture();
break;
case WM_MOUSEMOVE:
{
if ((wParam & MK_LBUTTON) == MK_LBUTTON && GetCapture() == hwnd)
{
HDWP hdwp;
RECT rect, rectStatus, rectTree;
hdwp = BeginDeferWindowPos(3);
GetClientRect(GetParent(hwnd), &rect);
GetClientRect(hStatus, &rectStatus);
GetWindowRect(hTree, &rectTree);
DeferWindowPos(hdwp, hTree, NULL, 0, 0, rectTree.right - rectTree.left + (short)LOWORD(lParam), rect.bottom - rectStatus.bottom, SWP_NOMOVE | SWP_NOZORDER);
DeferWindowPos(hdwp, hSplitter, NULL, rectTree.right - rectTree.left + (short)LOWORD(lParam), 0, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
DeferWindowPos(hdwp, hList, NULL, rectTree.right - rectTree.left + (short)LOWORD(lParam) + 2, 0, rect.right - rectTree.right + rectTree.left - (short)LOWORD(lParam) - 2, rect.bottom - rectStatus.bottom, SWP_NOZORDER);
EndDeferWindowPos(hdwp);
}
}
break;
default:
return DefWindowProc(hwnd, Msg, wParam, lParam);
}
return 0;
}
SetCapture和ReleaseCapture是分別在滑鼠左鍵按下與釋放的時候捕獲和釋放滑鼠,這是分隔條的一般要求。這段代碼中的核心部分就是在處理滑鼠移動的事件,就是當滑鼠左鍵按下並且分隔條捕獲滑鼠的時候來改變三個相關視窗的位置和寬度。具體的矩形操作與主視窗WM_SIZE的代碼原理相似,我就不多說了。
我之所以不使用MoveWindow之類的函數來實現改變大小,就是因為這些函數會使表單的多次重繪而導致整個表單的閃爍——而事實上我並不希望狀態列也一起閃爍。