在 Windows 程式設計中,所謂「客戶區」是指視窗內部可供應用程式繪圖的區域,不包含標題列、選單、捲軸等非客戶區元素。當使用 CreateWindow 建立視窗時,並不需要預先指定客戶區矩形;系統會在需要得知客戶區大小時,傳送 WM_NCCALCSIZE 訊息。在 MFC 中,對應的處理函式為 OnNcCalcSize:
void OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS* lpncsp);
此函式透過 NCCALCSIZE_PARAMS 結構中的矩形陣列來調整客戶區範圍。典型實作模式如下:
void CMainFrame::OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS* lpncsp)
{
// 必須呼叫基類預設處理,取得初始客戶區矩形
CFrameWnd::OnNcCalcSize(bCalcValidRects, lpncsp);
CRect& rc = (CRect&)lpncsp->rgrc[0];
// 調整客戶區,例如向內縮減 7 像素
rc.DeflateRect(7, 7);
}
重寫 WM_NCCALCSIZE 時,務必呼叫基類實作以取得預設客戶區,再行修改。同理,在 OnNcPaint 中也應呼叫基類,確保邊框、捲軸等非客戶區元素正確繪製。若自行實作完整的非客戶區繪製(如自訂工具列),則可不呼叫基類。
關於 bCalcValidRects 參數:文件說明其用於指示調整視窗大小時哪部分客戶區內容為有效,但實務上鮮少使用。測試發現視窗初建立時會以 FALSE 呼叫一次,之後調整大小皆為 TRUE。只要在兩種情況下都正確設定第一個矩形(rgrc[0]),即可正常運作。
MFC 未內建列表框間拖曳功能,雖可使用 COM 的 IDropTarget、IDropSource、IDataObject 介面,但代碼龐大且屬大材小用。針對單一應用程式內的控件拖曳,自行實作更為簡潔。
本文提供 CDragDropMgr 類別,封裝拖曳邏輯,支援來源/目標視窗的動態設定。使用步驟如下:
CDragDropMgr 實例。static DRAGDROPWND MyDragDropWindows[] = {
{ IDC_LIST1, DDW_SOURCE },
{ IDC_LIST2, DDW_SOURCE | DDW_TARGET },
{ IDC_EDIT1, DDW_TARGET },
{ 0, 0 },
};
m_ddm.Install(this, MyDragDropWindows);
PreTranslateMessage 將訊息轉交給管理器:BOOL CMyDlg::PreTranslateMessage(MSG* pMsg)
{
return m_ddm.PreTranslateMessage(pMsg) ? TRUE :
CDialog::PreTranslateMessage(pMsg);
}
管理器會偵測滑鼠動作,並透過四個自訂訊息通知應用程式:WM_DD_DRAGENTER、WM_DD_DRAGOVER、WM_DD_DRAGDROP、WM_DD_DRAGABORT。開發者需處理 WM_DD_DRAGENTER(決定是否允許拖曳並提供資料物件)與 WM_DD_DRAGDROP(執行放置動作)。
資料物件繼承自 CDragDropData,需實作三個虛擬函式:OnGetDragSize(取得拖曳影像大小)、OnDrawData(繪製影像)、OnGetData(取得實際資料)。範例提供 CDragDropText 處理文字拖曳:
void CDragDropText::OnDrawData(CDC& dc, CRect& rc)
{
dc.DrawText(m_text, &rc, DT_LEFT | DT_END_ELLIPSIS);
}
拖曳過程中,管理器利用 CImageList 的 BeginDrag、DragMove、EndDrag 等函式實現平滑的半透明影像移動,開發者只需專注於資料物件的繪製邏輯。
CDragDropMgr 採用有限狀態機(NONE、CAPTURED、DRAGGING)追蹤滑鼠狀態。在 PreTranslateMessage 中攔截來源視窗的滑鼠訊息,避免逐一子類化每個控件。拖曳影像建立流程:
WM_DD_DRAGENTER 中建立資料物件並指派給 DRAGDROPINFO::data。CDragDropData::CreateDragImage,內部依據 OnGetDragSize 建立影像清單,並以 OnDrawData 繪製內容。CImageList::DragMove 更新影像位置。此架構具有高度通用性,新增資料型別僅需衍生 CDragDropData 並實作繪製與尺寸函式即可。
圖:DDTest 示範程式執行畫面,展示兩個列表框與編輯框之間的拖曳操作。