首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

Windows 無標題列頂層視窗客戶區拖曳技術完整實作

     編輯:關於C++

前言:高階視窗拖曳的必要性

一般視窗擁有標題列,拖曳由系統自動處理。但 **無標題列、常駐頂層** 的高階視窗 (如輸入法候選視窗、浮動工具列、桌面小工具) 必須**自行實作客戶區拖曳**。本文系統解析拖曳啟動判斷、滑鼠游標動態提示、虛線框繪製、三階段拖曳流程 (啟動/移動/結束),提供可直接套用的 Win32 API 實作架構。

核心技術架構

1. 拖曳命令區域判斷

  • 特定區域:定義 RECT DragRect (如標題列模擬區、拖曳把手圖示),WM_LBUTTONDOWN 時用 PtInRect(&DragRect, pt) 判斷
  • 非特定區域:除特定按鈕外的客戶區,遍歷所有按鈕 PtInRect,均不命中即視為拖曳區

2. 滑鼠游標動態提示 (關鍵 UX)

  • 自訂游標資源 (.cur,32x32,熱點置中心):DEBUG 修改偏移位元組 (位移 10h/12h 設為 10,0)
  • 資源檔定義:MYDRAG CURSOR drag.cur
  • WM_MOUSEMOVE 時判斷滑鼠是否在拖曳區:SetCursor(hDragCursor) + SetCapture(hWnd) 取得輸入焦點;離開區域 SetCursor(LoadCursor(NULL,IDC_ARROW)) + ReleaseCapture()

3. 虛線框繪製與清除 (XOR 模式)

  • 取得全螢幕 DC:GetDC(NULL)
  • 設定 ROP2 模式: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 專案。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved