在繪圖/編輯類應用中,滑鼠移動時於游標旁即時顯示座標 (如 [123, 456]) 是基本需求。簡單做法在 OnDraw 繪製,但會導致:
Invalidate 觸發重繪,整個視圖閃爍。本文實作「離屏備份 + BitBlt 還原」技術,達到零閃爍、零殘影、不觸發視圖重繪的完美效果。
CBitmap + CDC)。BitBlt 回視圖 DC,抹除舊文字,再進入下一輪備份-繪製。
// 常數定義 (依系統字體微調)
#define CHAR_HEIGHT 18
#define CHAR_WIDTH 7
#define OFFSET_X 15 // 文字相對滑鼠 X 偏移
#define OFFSET_Y 10 // 文字相對滑鼠 Y 偏移
#define TEXT_COLOR RGB(255, 0, 0) // 紅色文字
// CDrawMouseView 成員變數
CBitmap m_bmpBackup; // 備份位圖
CDC m_dcBackup; // 備份 DC
int m_nTextWidth; // 文字像素寬度
CPoint m_ptLastMouse; // 上一次滑鼠位置
BOOL m_bFirstMove = TRUE; // 是否為第一次移動
void CDrawMouseView::BackupRect(CDC* pDC, CPoint ptMouse)
{
// 計算文字寬度
CString str;
str.Format(_T("[%d,%d]"), ptMouse.x, ptMouse.y);
m_nTextWidth = str.GetLength() * CHAR_WIDTH;
// 目標矩形 (螢幕座標 -> 用戶區座標)
CRect rcTarget(ptMouse.x + OFFSET_X, ptMouse.y + OFFSET_Y,
ptMouse.x + OFFSET_X + m_nTextWidth, ptMouse.y + OFFSET_Y + CHAR_HEIGHT);
// 清理舊備份資源
if (m_bmpBackup.GetSafeHandle())
{
m_dcBackup.SelectObject(m_bmpBackup); // 確保位圖已選入
m_bmpBackup.DeleteObject();
m_dcBackup.DeleteDC();
}
// 建立相容 DC 與位圖
if (m_dcBackup.CreateCompatibleDC(pDC))
{
if (m_bmpBackup.CreateCompatibleBitmap(pDC, m_nTextWidth, CHAR_HEIGHT))
{
CBitmap* pOldBmp = m_dcBackup.SelectObject(&m_bmpBackup);
// 關鍵:從視圖 DC 複製目標區域到備份 DC
m_dcBackup.BitBlt(0, 0, m_nTextWidth, CHAR_HEIGHT,
pDC, rcTarget.left, rcTarget.top, SRCCOPY);
m_dcBackup.SelectObject(pOldBmp);
}
else
{
m_dcBackup.DeleteDC();
}
}
}
void CDrawMouseView::DrawCoordText(CDC* pDC, CPoint ptMouse)
{
if (!m_bmpBackup.GetSafeHandle()) return; // 無備份不繪製
CString str;
str.Format(_T("[%d,%d]"), ptMouse.x, ptMouse.y);
CRect rcText(ptMouse.x + OFFSET_X, ptMouse.y + OFFSET_Y,
ptMouse.x + OFFSET_X + m_nTextWidth, ptMouse.y + OFFSET_Y + CHAR_HEIGHT);
pDC->SetBkMode(TRANSPARENT);
COLORREF oldColor = pDC->SetTextColor(TEXT_COLOR);
pDC->DrawText(str, &rcText, DT_LEFT | DT_TOP | DT_SINGLELINE);
pDC->SetTextColor(oldColor);
}
void CDrawMouseView::RestorePrev(CDC* pDC)
{
if (!m_bmpBackup.GetSafeHandle()) return;
CRect rcPrev(m_ptLastMouse.x + OFFSET_X, m_ptLastMouse.y + OFFSET_Y,
m_ptLastMouse.x + OFFSET_X + m_nTextWidth, m_ptLastMouse.y + OFFSET_Y + CHAR_HEIGHT);
// 將備份位圖複製回視圖 DC
CBitmap* pOldBmp = m_dcBackup.SelectObject(&m_bmpBackup);
pDC->BitBlt(rcPrev.left, rcPrev.top, m_nTextWidth, CHAR_HEIGHT,
&m_dcBackup, 0, 0, SRCCOPY);
m_dcBackup.SelectObject(pOldBmp);
// 釋放備份資源 (下一輪會重建)
m_dcBackup.DeleteDC();
m_bmpBackup.DeleteObject();
}
void CDrawMouseView::OnMouseMove(UINT nFlags, CPoint point)
{
CDC* pDC = GetDC();
if (!pDC) return;
if (!m_bFirstMove)
{
RestorePrev(pDC); // 非第一次:先還原上一幀
}
else
{
m_bFirstMove = FALSE;
}
// 備份當前位置畫面
BackupRect(pDC, point);
// 繪製新座標
DrawCoordText(pDC, point);
m_ptLastMouse = point; // 記錄供下一次還原用
ReleaseDC(pDC);
CView::OnMouseMove(nFlags, point);
}
void CDrawMouseView::OnDestroy()
{
if (m_bmpBackup.GetSafeHandle())
{
m_bmpBackup.DeleteObject();
m_dcBackup.DeleteDC();
}
CView::OnDestroy();
}
| 指標 | 傳統 Invalidate 重繪 | 本文 BitBlt 雙緩衝 |
|---|---|---|
| 閃爍 | 明顯 (整視圖重繪) | 零閃爍 (局部像素操作) |
| 殘影 | 無 (背景重繪覆蓋) | 無 (精確還原備份像素) |
| 效能 | 低 (觸發完整 OnDraw) | 極高 (僅 2 次 BitBlt + 1 次 DrawText) |
| 適用場景 | 靜態內容、低頻更新 | 高頻滑鼠追蹤、即時標註、游標十字線 |
掌握 CDC::BitBlt、CBitmap、CDC::CreateCompatibleDC 的組合運用,是 MFC/GDI 繪圖進階的關鍵技能。本技術不僅解決座標顯示,更是所有「非破壞性疊加繪製」場景的通用解法。下次遇到需在現有畫面上疊加動態資訊時,請優先考慮此方案。