一般視窗擁有標題列,拖曳由系統自動處理。但 **無標題列、常駐頂層** 的高階視窗 (如輸入法候選視窗、浮動工具列、桌面小工具) 必須**自行實作客戶區拖曳**。本文系統解析拖曳啟動判斷、滑鼠游標動態提示、虛線框繪製、三階段拖曳流程 (啟動/移動/結束),提供可直接套用的 Win32 API 實作架構。
RECT DragRect (如標題列模擬區、拖曳把手圖示),WM_LBUTTONDOWN 時用 PtInRect(&DragRect, pt) 判斷PtInRect,均不命中即視為拖曳區.cur,32x32,熱點置中心):DEBUG 修改偏移位元組 (位移 10h/12h 設為 10,0)MYDRAG CURSOR drag.curWM_MOUSEMOVE 時判斷滑鼠是否在拖曳區:SetCursor(hDragCursor) + SetCapture(hWnd) 取得輸入焦點;離開區域 SetCursor(LoadCursor(NULL,IDC_ARROW)) + ReleaseCapture()GetDC(NULL)SetROP2(hdc, R2_XORPEN) (異或:畫兩次即還原)SelectObject(hdc, GetStockObject(NULL_BRUSH))Rectangle (實線框、效能最好)、MoveTo/LineTo (可虛線)、SetPixel (自訂虛線圖案、效能差)WM_LBUTTONDOWN → DragBegin)
BOOL bDragging = FALSE;
POINT ptStart; // 螢幕座標
RECT rcDrag; // 拖曳框矩形 (初始 = 視窗矩形)
case WM_LBUTTONDOWN:
pt = MAKEPOINTS(lParam); // 客戶區座標
ClientToScreen(hWnd, &pt); // 轉螢幕座標
if (PtInRect(&DragRect, pt)) // 或非特定區域判斷
{
DragBegin(&rcDrag, pt, hWnd, 2); // 2 = 邊框寬度
}
break;
void DragBegin(LPRECT rcWin, POINT pt, HWND hWnd, UINT border)
{
SetCapture(hWnd); // 獨佔滑鼠輸入
bDragging = TRUE;
ptStart = pt; // 記錄起始螢幕座標
rcDrag = *rcWin; // 初始化拖曳框
DrawDragRect(rcDrag.left, rcDrag.top, rcDrag.right, rcDrag.bottom, border); // 顯示初始框
}
WM_MOUSEMOVE → DragMove)
case WM_MOUSEMOVE:
if (bDragging)
{
POINT pt = MAKEPOINTS(lParam);
ClientToScreen(hWnd, &pt);
DragMove(&rcDrag, pt);
}
break;
void DragMove(LPRECT rcWin, POINT pt)
{
DrawDragRect(rcWin->left, rcWin->top, rcWin->right, rcWin->bottom, 1); // 清除舊框
int dx = pt.x - ptStart.x;
int dy = pt.y - ptStart.y;
ptStart = pt;
rcWin->left += dx; rcWin->right += dx;
rcWin->top += dy; rcWin->bottom += dy;
// 限制在螢幕可見區域
rcWin->left = max(0, min(rcWin->left, GetSystemMetrics(SM_CXSCREEN) - (rcWin->right - rcWin->left)));
rcWin->top = max(0, min(rcWin->top, GetSystemMetrics(SM_CYSCREEN) - (rcWin->bottom - rcWin->top)));
rcWin->right = rcWin->left + (rcWin->right - rcWin->left);
rcWin->bottom = rcWin->top + (rcWin->bottom - rcWin->top);
DrawDragRect(rcWin->left, rcWin->top, rcWin->right, rcWin->bottom, 1); // 畫新框
}
WM_LBUTTONUP → DragEnd)
case WM_LBUTTONUP:
if (bDragging)
{
DragEnd(&rcDrag, hWnd);
}
break;
void DragEnd(LPRECT rcWin, HWND hWnd)
{
DrawDragRect(rcWin->left, rcWin->top, rcWin->right, rcWin->bottom, 1); // 清除框
// 同步限制邏輯
rcWin->left = max(0, min(rcWin->left, GetSystemMetrics(SM_CXSCREEN) - (rcWin->right - rcWin->left)));
rcWin->top = max(0, min(rcWin->top, GetSystemMetrics(SM_CYSCREEN) - (rcWin->bottom - rcWin->top)));
MoveWindow(hWnd, rcWin->left, rcWin->top,
rcWin->right - rcWin->left, rcWin->bottom - rcWin->top, TRUE);
bDragging = FALSE;
ReleaseCapture(); // 釋放滑鼠捕捉
}
| 項目 | 要點 |
|---|---|
| 座標系 | 拖曳框、滑鼠捕捉、MoveWindow 均使用**螢幕座標** |
| XOR 繪製 | 同一位置畫兩次即還原,無需背景備份 |
| 滑鼠捕捉 | SetCapture 確保滑鼠移出視窗仍收到 WM_MOUSEMOVE |
| 游標熱點 | .cur 檔熱點必須設在圖示中心,否則游標位置偏移 |
| 多視窗支援 | 將狀態變數 (bDragging, rcDrag, ptStart) 封裝至結構體,每視窗獨立實例 |
| 相容性 | Win95/98/NT4/2000/XP/現代 Windows 皆適用 |
掌握客戶區拖曳三部曲 (啟動/移動/結束)、XOR 虛線框、游標動態提示,即可打造專業級無標題列頂層視窗。本架構已於實際輸入法平台專案驗證,穩定可靠,可直接移植至 C/C++/Delphi/C++Builder 專案。