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

MFC 視圖類滑鼠座標即時顯示:BitBlt 雙緩衝無閃爍技術完整實作 (重構優化版)

     編輯:VC 教學

專案背景與目標

在繪圖、CAD、圖像處理等 MFC 視圖應用中,滑鼠移動時於游標旁即時顯示座標 ([x, y]) 是基本需求。傳統 Invalidate() 重繪會導致整視圖閃爍、效能低落。本文採用離屏備份 + BitBlt 還原技術,實現零閃爍、零殘影、不觸發 OnDraw的高效能解決方案,並對原始碼進行現代化重構 (RAII 資源管理、錯誤處理、可配置常數)。

核心架構設計

常數集中管理 (Header)


// CoordTracker.h
#pragma once

namespace CoordTrackerConfig
{
    constexpr int CHAR_HEIGHT = 18;      // 字元高度 (像素)
    constexpr int CHAR_WIDTH  = 7;       // 字元寬度 (像素)
    constexpr int OFFSET_X    = 15;      // 文字相對游標 X 偏移
    constexpr int OFFSET_Y    = 10;      // 文字相對游標 Y 偏移
    constexpr COLORREF TEXT_COLOR = RGB(255, 0, 0); // 紅色文字
}

RAII 備份管理類別


// CoordTracker.h (續)
class CCoordBackup
{
public:
    CCoordBackup() = default;
    ~CCoordBackup() { Release(); }

    // 禁止複製,支援移動
    CCoordBackup(const CCoordBackup&) = delete;
    CCoordBackup& operator=(const CCoordBackup&) = delete;
    CCoordBackup(CCoordBackup&& other) noexcept
        : m_dc(other.m_dc), m_bmp(other.m_bmp), m_size(other.m_size)
    {
        other.m_dc = nullptr; other.m_bmp = nullptr;
    }
    CCoordBackup& operator=(CCoordBackup&& other) noexcept
    {
        if (this != &other) { Release(); m_dc = other.m_dc; m_bmp = other.m_bmp; m_size = other.m_size; other.m_dc = nullptr; other.m_bmp = nullptr; }
        return *this;
    }

    // 建立備份:從 pDC 複製 rc 區域
    bool Create(CDC* pDC, const CRect& rc)
    {
        Release();
        m_size = rc.Size();
        if (!m_dc.CreateCompatibleDC(pDC)) return false;
        if (!m_bmp.CreateCompatibleBitmap(pDC, m_size.cx, m_size.cy)) { m_dc.DeleteDC(); return false; }
        CBitmap* pOld = m_dc.SelectObject(&m_bmp);
        m_dc.BitBlt(0, 0, m_size.cx, m_size.cy, pDC, rc.left, rc.top, SRCCOPY);
        m_dc.SelectObject(pOld);
        return true;
    }

    // 還原至 pDC 的 rc 區域
    void Restore(CDC* pDC, const CRect& rc) const
    {
        if (!m_dc || !m_bmp) return;
        CBitmap* pOld = m_dc.SelectObject(&m_bmp);
        pDC->BitBlt(rc.left, rc.top, m_size.cx, m_size.cy, &m_dc, 0, 0, SRCCOPY);
        m_dc.SelectObject(pOld);
    }

    bool IsValid() const { return m_dc && m_bmp; }
    CSize GetSize() const { return m_size; }

private:
    void Release()
    {
        if (m_bmp) { m_bmp.DeleteObject(); m_bmp = nullptr; }
        if (m_dc) { m_dc.DeleteDC(); m_dc = nullptr; }
        m_size = CSize(0,0);
    }

    CDC m_dc;
    CBitmap m_bmp;
    CSize m_size;
};

視圖類整合


// MyView.h
#include "CoordTracker.h"

class CMyView : public CView
{
    // ...
protected:
    CCoordBackup m_backup;           // 座標區域備份
    CPoint m_ptLastMouse;            // 上一次游標位置
    bool m_bFirstMove = true;        // 是否首次移動
    int m_nTextWidth = 0;            // 當前文字寬度

    // 內部輔助函式
    CRect CalcTextRect(CPoint ptMouse) const;
    void DrawCoordText(CDC* pDC, CPoint ptMouse);
    void OnMouseMove(UINT nFlags, CPoint point) override;
    void OnDestroy() override;
};

核心實作 (MyView.cpp)


#include "stdafx.h"
#include "MyView.h"
#include "CoordTracker.h"

using namespace CoordTrackerConfig;

CRect CMyView::CalcTextRect(CPoint ptMouse) const
{
    return CRect(
        ptMouse.x + OFFSET_X,
        ptMouse.y + OFFSET_Y,
        ptMouse.x + OFFSET_X + m_nTextWidth,
        ptMouse.y + OFFSET_Y + CHAR_HEIGHT
    );
}

void CMyView::DrawCoordText(CDC* pDC, CPoint ptMouse)
{
    if (!m_backup.IsValid()) return;

    CString str;
    str.Format(_T("[%d, %d]"), ptMouse.x, ptMouse.y);
    m_nTextWidth = str.GetLength() * CHAR_WIDTH;

    CRect rcText = CalcTextRect(ptMouse);
    pDC->SetBkMode(TRANSPARENT);
    COLORREF oldColor = pDC->SetTextColor(TEXT_COLOR);
    pDC->DrawText(str, &rcText, DT_LEFT | DT_TOP | DT_SINGLELINE);
    pDC->SetTextColor(oldColor);
}

void CMyView::OnMouseMove(UINT nFlags, CPoint point)
{
    CClientDC dc(this); // 自動 ReleaseDC
    if (!dc.GetSafeHdc()) return;

    CRect rcCurrent = CalcTextRect(point);
    CString str;
    str.Format(_T("[%d, %d]"), point.x, point.y);
    m_nTextWidth = str.GetLength() * CHAR_WIDTH;
    rcCurrent.right = rcCurrent.left + m_nTextWidth; // 更新寬度

    if (!m_bFirstMove && m_backup.IsValid())
    {
        // 還原上一幀
        m_backup.Restore(&dc, CalcTextRect(m_ptLastMouse));
    }
    else
    {
        m_bFirstMove = false;
    }

    // 備份當前區域
    if (!m_backup.Create(&dc, rcCurrent))
    {
        TRACE(_T("CoordTracker: Backup creation failed\n"));
        return;
    }

    // 繪製新座標
    DrawCoordText(&dc, point);

    m_ptLastMouse = point;
    CView::OnMouseMove(nFlags, point);
}

void CMyView::OnDestroy()
{
    // CCoordBackup 解構子自動釋放資源
    CView::OnDestroy();
}

效能與正確性優勢

指標傳統 Invalidate本方案 (BitBlt 雙緩衝)
繪圖呼叫完整 OnDraw (所有物件)1 次 BitBlt (還原) + 1 次 BitBlt (備份) + 1 次 DrawText
閃爍明顯 (WM_ERASEBKGND + OnDraw)無 (像素級還原)
殘影無 (背景重繪)無 (精確備份還原)
CPU 佔用 (滑鼠高速移動)高 (大量重繪)極低 (固定 3 GDI 呼叫)
記憶體無額外1 個小型位圖 (~文字區域大小,通常 < 2KB)
程式碼複雜度低中 (封裝後極簡單)

延伸應用場景

  • 十字游標線:備份整行/整列 (或交叉區域),繪製線條,移動時還原。
  • 即時測量標註:顯示兩點距離、角度、面積等動態資訊。
  • 圖層/物件資訊提示:游標懸停時顯示物件屬性、像素顏色值。
  • 橡皮筋選取框:拖曳選取時即時顯示矩形框,無殘影。

常見問題與除錯技巧

  1. 備份區域計算錯誤:使用 ClientDC 時座標已為用戶區,無需額外轉換。若用 GetDC() 需確認座標系統一致性。
  2. 文字寬度計算不準:固定 CHAR_WIDTH 為近似值。精確做法:pDC->GetTextExtent(str).cx。但會增加 GetTextExtent 呼叫開銷,視專案效能需求取捨。
  3. 多螢幕/DPI 縮放:高 DPI 環境下 CHAR_WIDTH/HEIGHT 需隨 DPI 縮放。建議在 OnInitialUpdate 以 GetDeviceCaps(LOGPIXELSX) 計算實際字元大小。
  4. 視圖捲動時座標錯位:OnMouseMove 的 point 為用戶區座標 (已扣除捲動位移)。若需邏輯座標 (文件座標),需加上 GetScrollPosition()。

結語

以 CCoordBackup RAII 類別封裝 BitBlt 備份/還原邏輯,視圖類僅需 3 行核心代碼即可實現專業級滑鼠座標跟蹤顯示。此模式適用於所有「非破壞性疊加繪製」場景,是 MFC/GDI 開發者必備的高效能繪圖技巧。建議將 CoordTracker.h/.cpp 加入專案共用庫,下次需求直接複用。

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