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

QQ 界面程式設計深度剖析:SetWindowRgn 時機修正與無邊框視窗拖曳縮放自實現

     編輯:關於VC++

前言

QQ 2006 風格視窗採用不規則造型(圓角、特殊標題列),且無標準邊框仍支援任意拖曳縮放。許多仿製專案在 SetWindowRgn 呼叫時機、無邊框拖曳邏輯上存在缺陷(如拖曳時出現紫色遮罩殘影、吸附頂端異常)。本文針對這兩大痛點提供最佳實踐方案。

一、SetWindowRgn 的正確呼叫時機:避免紫色遮罩閃爍

錯誤做法:在 OnInitDialog 或 OnCreate 中呼叫,視窗尚未繪製,區域計算不準;或在 WM_NCPAINT 中重複呼叫導致閃爍。

正確做法:在 OnPaint 內存繪製完成、即將 BitBlt 到螢幕前 呼叫,確保區域與最終像素完美對齊。

方案 A:在 OnPaint 中配合雙緩衝繪製

void CMyDlg::OnPaint()
{
    if (IsIconic()) { /* 圖示繪製 */ return; }

    CPaintDC dc(this);
    CRect rcClient; GetClientRect(&rcClient);

    // 1. 建立記憶體 DC 進行離屏繪製
    CDC memDC; memDC.CreateCompatibleDC(&dc);
    CBitmap bmp; bmp.CreateCompatibleBitmap(&dc, rcClient.Width(), rcClient.Height());
    CBitmap* pOldBmp = memDC.SelectObject(&bmp);

    // 2. 在記憶體 DC 上繪製整個視窗外觀(背景、標題、按鈕、內容...)
    DrawEntireWindow(&memDC, rcClient);

    // 3. ★ 關鍵:根據記憶體位圖建立不規則區域並套用
    ChangeWindowRgn(&memDC); // 內部呼叫 SetWindowRgn

    // 4. 一次性輸出到螢幕
    dc.BitBlt(0, 0, rcClient.Width(), rcClient.Height(), &memDC, 0, 0, SRCCOPY);

    memDC.SelectObject(pOldBmp);
    bmp.DeleteObject(); memDC.DeleteDC();
}

void CMyDlg::ChangeWindowRgn(CDC* pDC)
{
    COLORREF clrMask = RGB(255, 0, 255); // 魔術粉紅色作為透明遮罩
    CRect rc; GetClientRect(&rc);
    CRgn rgn; rgn.CreateRectRgn(0, 0, rc.Width(), rc.Height());

    // 掃描位圖,排除 clrMask 色像素,建立複合區域
    // ... 略:逐行掃描 CreateRectRgn + CombineRgn(RGN_OR) ...

    SetWindowRgn(rgn, TRUE); // TRUE = 重繪視窗
    rgn.Detach(); // 所有權轉給系統,不可 DeleteObject
}

方案 B:在 OnShowWindow 首次顯示時一次性建立

適用於固定形狀視窗(如圓角矩形),效能更高:

void CMyDlg::OnShowWindow(BOOL bShow, UINT nStatus)
{
    CDialog::OnShowWindow(bShow, nStatus);
    if (bShow) {
        CRect rc; GetClientRect(&rc);
        CRgn rgn;
        rgn.CreateRoundRectRgn(0, 0, rc.Width(), rc.Height(), 10, 10); // 圓角半徑 10
        // 可進一步排除標題列按鈕區域等
        SetWindowRgn(rgn, TRUE);
        rgn.Detach();
    }
}

二、無 WS_THICKFRAME 視窗的拖曳移動與縮放自實現

傳統做法:PostMessage(WM_NCLBUTTONDOWN, HTCAPTION) 模擬標題列拖曳。缺點:拖至螢幕頂端超出螢幕範圍時,系統會強制對齊,無法實現 QQ 式「自由拖曳至螢幕外」。

改良方案:SetTimer + MoveWindow 實現自由拖曳

void CMyDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
    CRect rcBtn(/* 可拖曳區域矩形 */);
    if (rcBtn.PtInRect(point)) {
        SetTimer(1, 20, NULL); // 50fps 輪詢滑鼠
        GetCursorPos(&m_ptLastMouse);
        GetWindowRect(&m_rcLastWnd);
        SetCapture();
        return;
    }
    CDialog::OnLButtonDown(nFlags, point);
}

void CMyDlg::OnTimer(UINT nIDEvent)
{
    if (nIDEvent == 1) {
        POINT pt; GetCursorPos(&pt);
        CPoint delta(pt.x - m_ptLastMouse.x, pt.y - m_ptLastMouse.y);

        // 判斷是移動還是縮放(根據點擊區域:標題列=移動,右下角=縮放)
        if (m_bDraggingTitle) { // 移動
            m_rcLastWnd.OffsetRect(delta);
        } else { // 右下角縮放
            m_rcLastWnd.right += delta.x;
            m_rcLastWnd.bottom += delta.y;
            // 限制最小尺寸
            if (m_rcLastWnd.Width() < MIN_W) m_rcLastWnd.right = m_rcLastWnd.left + MIN_W;
            if (m_rcLastWnd.Height() < MIN_H) m_rcLastWnd.bottom = m_rcLastWnd.top + MIN_H;
        }
        MoveWindow(&m_rcLastWnd);
        m_ptLastMouse = pt;
        Invalidate(); // 觸發 OnPaint 重繪(含 SetWindowRgn)
    }
    CDialog::OnTimer(nIDEvent);
}

void CMyDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
    if (m_bDragging) { KillTimer(1); ReleaseCapture(); m_bDragging = FALSE; }
    CDialog::OnLButtonUp(nFlags, point);
}

關鍵細節:指定可拖曳/縮放區域

QQ 換膚後,標準邊框消失,需在 OnLButtonDown 中判斷點擊座標是否落在「標題列區域」(移動)或「右下角 20x20 區域」(縮放),而非全客戶區皆可拖曳。

三、完整效果預覽

QQ2006 風格不規則視窗效果

四、總結與建議

  1. SetWindowRgn 必須在像素內容確定後(OnPaint 記憶體 DC 繪製完、BitBlt 前)呼叫,區域才與視覺完美吻合,杜絕紫色遮罩殘留。
  2. 無邊框視窗拖曳/縮放,**Timer + MoveWindow** 比模擬 WM_NCLBUTTONDOWN 更靈活,可突破螢幕邊界限制,實現真正的「自由拖曳」。
  3. 配合 WM_NCHITTEST 回傳 HTCAPTION/HTBOTTOMRIGHT 等,系統仍能提供標準游標回饋(雙向箭頭等)。
  4. 建議將不規則區域計算封裝為 CRegionBuilder 工具類,支援圓角矩形、多邊形、位圖透明色掃描等多種建模方式。

結語

QQ 界面的精髓在於「像素級控制」與「突破系統預設行為」。掌握 OnPaint 區域建立時機與 Timer+MoveWindow 自由拖曳技巧,是實現高品質非標準視窗的關鍵基石。

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