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

VC++ 完美仿 QQ 自動伸縮/吸附視窗:從行為觀察到像素級邏輯實現

     編輯:關於VC++

前言

QQ 主面板的「邊緣吸附自動伸縮」是其標誌性交互:拖曳至螢幕四邊自動吸附並展開,滑鼠移開自動收縮為一條細線。網上流傳的 Demo 多與真機行為有出入。本文基於對 QQ 2005/2006 真機行為的 12 點詳細觀察,實現近乎完美的仿真版本。

一、真機行為觀察與規格定義(12 關鍵點)

  1. 吸附觸發條件:滑鼠座標與螢幕邊界距離 INTERVAL(20px);底邊以工作列上緣為準。
  2. 向上移動時視窗頂端不超出螢幕上緣(top >= 0)。
  3. 視窗具備 WS_EX_TOPMOST 樣式。
  4. 吸附頂/左/右邊展開時,滑鼠移至最頂端游標變為縮放樣式(需 top = -edgeHeight 而非 0)。
  5. 底邊吸附:移動時底緣對齊工作列頂端;隱藏→顯示動畫時底緣對齊螢幕底端。
  6. 隱藏狀態僅保留 Border(無 Client 區),非 WM_MOUSELEAVE 觸發。
  7. 滑鼠進入/離開判定:進入極其靈敏(非客戶區即觸發),離開有 10px 緩衝區。
  8. 左/右邊吸附時高度自動調整為「螢幕上緣到工作列下緣」距離。
  9. 「拖動時顯示視窗內容」系統設定影響 WM_MOVING 參數語意,導致釋放滑鼠時高度不同步(非 Bug,需相容)。
  10. 工作列自動隱藏時,左/右邊吸附高度自動填滿螢幕。
  11. 展開/收縮為漸變動畫,非瞬間切換;兩種系統設定下動畫幀率不同。
  12. 工作列不顯示該視窗按鈕(WS_EX_TOOLWINDOW)。

二、核心架構設計

class CQQHideWndDlg : public CDialog
{
protected:
    void FixMoving(UINT fwSide, LPRECT pRect); // WM_MOVING 中修正位置/判定吸附模式
    void DoShow();                              // 從收縮態展開(動畫)
    void DoHide();                              // 從展開態收縮(動畫)
    BOOL SetWindowPos(const CWnd*, LPCRECT, UINT = SWP_SHOWWINDOW);

private:
    BOOL m_isSizeChanged;   // 左/右吸附時高度是否已改變
    BOOL m_isSetTimer;      // 是否啟用滑鼠偵測計時器
    int  m_oldWndHeight;    // 左/右吸附前的原始高度
    int  m_taskBarHeight;   // 工作列高度
    int  m_edgeHeight;      // 系統邊框高度 (SM_CYEDGE)
    int  m_edgeWidth;       // 系統邊框寬度 (SM_CXFRAME)
    int  m_hideMode;        // 當前吸附模式
    BOOL m_hsFinished;      // 動畫是否完成
    BOOL m_hiding;          // TRUE=收縮中, FALSE=展開中
};

吸附模式枚舉

#define HM_NONE 0
#define HM_TOP  1
#define HM_BOTTOM 2
#define HM_LEFT 3
#define HM_RIGHT 4

三、關鍵函數實現

1. OnCreate:初始化系統參數與視窗樣式

int CQQHideWndDlg::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CDialog::OnCreate(lpCreateStruct) == -1) return -1;

    // 取得工作列高度
    CWnd* pTask = FindWindow(_T("Shell_TrayWnd"), NULL);
    if (pTask) { CRect rc; pTask->GetWindowRect(&rc); m_taskBarHeight = rc.Height(); }

    // 不在工作列顯示按鈕
    ModifyStyleEx(WS_EX_APPWINDOW, WS_EX_TOOLWINDOW);

    // 系統邊框度量
    m_edgeHeight = GetSystemMetrics(SM_CYEDGE);
    m_edgeWidth  = GetSystemMetrics(SM_CXFRAME);
    return 0;
}

2. WM_NCHITTEST:滑鼠進入偵測與動畫啟動

UINT CQQHideWndDlg::OnNcHitTest(CPoint point)
{
    if (m_hideMode != HM_NONE && !m_isSetTimer &&
        point.x < GetSystemMetrics(SM_CXSCREEN) + INFALTE) // 防右邊閃爍
    {
        SetTimer(1, CM_ELAPSE, NULL); // 滑鼠離開偵測計時器 (200ms)
        m_isSetTimer = TRUE;
        m_hsFinished = FALSE;
        m_hiding = FALSE;
        SetTimer(2, HS_ELAPSE, NULL); // 動畫幀計時器 (5ms)
    }
    return CDialog::OnNcHitTest(point);
}

3. WM_TIMER:雙計時器協作

void CQQHideWndDlg::OnTimer(UINT nIDEvent)
{
    if (nIDEvent == 1) { // 滑鼠離開偵測
        POINT pt; GetCursorPos(&pt);
        CRect rc; GetWindowRect(&rc);
        rc.InflateRect(INFALTE, INFALTE); // 10px 緩衝
        if (!rc.PtInRect(pt)) {
            KillTimer(1); m_isSetTimer = FALSE;
            m_hsFinished = FALSE; m_hiding = TRUE;
            SetTimer(2, HS_ELAPSE, NULL);
        }
    }
    else if (nIDEvent == 2) { // 動畫幀
        if (m_hsFinished) KillTimer(2);
        else m_hiding ? DoHide() : DoShow();
    }
    CDialog::OnTimer(nIDEvent);
}

4. FixMoving:WM_MOVING 中的位置修正與模式判定(核心邏輯)

void CQQHideWndDlg::FixMoving(UINT fwSide, LPRECT pRect)
{
    POINT pt; GetCursorPos(&pt);
    int scrH = GetSystemMetrics(SM_CYSCREEN), scrW = GetSystemMetrics(SM_CXSCREEN);
    int h = pRect->bottom - pRect->top, w = pRect->right - pRect->left;

    if (pt.y <= INTERVAL) { // 吸附頂部
        pRect->bottom = h - m_edgeHeight; pRect->top = -m_edgeHeight;
        m_hideMode = HM_TOP;
    }
    else if (pt.y >= scrH - INTERVAL - m_taskBarHeight) { // 吸附底部
        pRect->top = scrH - m_taskBarHeight - h; pRect->bottom = scrH - m_taskBarHeight;
        m_hideMode = HM_BOTTOM;
    }
    else if (pt.x < INTERVAL) { // 吸附左側
        if (!m_isSizeChanged) { CRect rc; GetWindowRect(&rc); m_oldWndHeight = rc.Height(); }
        pRect->right = w; pRect->left = 0;
        pRect->top = -m_edgeHeight; pRect->bottom = scrH - m_taskBarHeight;
        m_isSizeChanged = TRUE; m_hideMode = HM_LEFT;
    }
    else if (pt.x >= scrW - INTERVAL) { // 吸附右側
        if (!m_isSizeChanged) { CRect rc; GetWindowRect(&rc); m_oldWndHeight = rc.Height(); }
        pRect->left = scrW - w; pRect->right = scrW;
        pRect->top = -m_edgeHeight; pRect->bottom = scrH - m_taskBarHeight;
        m_isSizeChanged = TRUE; m_hideMode = HM_RIGHT;
    }
    else { // 非吸附區
        if (m_isSizeChanged) { pRect->bottom = pRect->top + m_oldWndHeight; m_isSizeChanged = FALSE; }
        if (m_isSetTimer) { if (KillTimer(1)) m_isSetTimer = FALSE; }
        m_hideMode = HM_NONE;
    }
}

5. DoHide / DoShow:分步動畫實現「抽屜效果」

void CQQHideWndDlg::DoHide()
{
    if (m_hideMode == HM_NONE) return;
    CRect rc; GetWindowRect(&rc);
    int h = rc.Height(), w = rc.Width(), step;

    switch (m_hideMode) {
    case HM_TOP:
        step = h / HS_STEPS; rc.bottom -= step;
        if (rc.bottom <= m_edgeWidth) { rc.bottom = m_edgeWidth; m_hsFinished = TRUE; }
        rc.top = rc.bottom - h; break;
    case HM_BOTTOM:
        step = h / HS_STEPS; rc.top += step;
        if (rc.top >= scrH - m_edgeWidth) { rc.top = scrH - m_edgeWidth; m_hsFinished = TRUE; }
        rc.bottom = rc.top + h; break;
    case HM_LEFT:
        step = w / HS_STEPS; rc.right -= step;
        if (rc.right <= m_edgeWidth) { rc.right = m_edgeWidth; m_hsFinished = TRUE; }
        rc.left = rc.right - w; rc.top = -m_edgeHeight; rc.bottom = scrH - m_taskBarHeight; break;
    case HM_RIGHT:
        step = w / HS_STEPS; rc.left += step;
        if (rc.left >= scrW - m_edgeWidth) { rc.left = scrW - m_edgeWidth; m_hsFinished = TRUE; }
        rc.right = rc.left + w; rc.top = -m_edgeHeight; rc.bottom = scrH - m_taskBarHeight; break;
    }
    SetWindowPos(&wndTopMost, rc);
}

void CQQHideWndDlg::DoShow() // 邏輯對稱,step 方向相反
{
    // ... 類似實現,rc.top += step (TOP), rc.top -= step (BOTTOM) 等
    // 關鍵:展開完成時 rc.top = -m_edgeHeight (頂/左/右) 或 rc.top = scrH - h (底)
}

四、細節補充

  • WM_SIZING 處理:限制最小寬/高,防止使用者拖曳過小。
  • 動畫步數 HS_STEPS=10、幀間隔 HS_ELAPSE=5ms 可調整手感。
  • 動畫進行中滑鼠進出可即時反向(m_hiding 翻轉),極具 QQ 神韻。

五、結語

完美仿真需要「觀察細、邏輯密、實現異」。本實現覆蓋 12 大真機行為點,動畫流暢、邊界處理嚴謹。源碼已上傳知識庫,歡迎下載體驗與指正。

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