製作網頁/多媒體素材時常需量取螢幕區域寬高。《電子尺》利用 透明全螢幕視窗 (WS_EX_TOPMOST | WS_EX_TRANSPARENT | WS_EX_LAYERED) 鎖定螢幕,繪製十字線輔助定位,雙擊兩點自動計算距離並換算單位。本文完整解析核心類別 CTarget 設計與繪圖邏輯。
void CTarget::CreateTarget(LPCTSTR title, CWnd* pParent) {
m_cxScreen = GetSystemMetrics(SM_CXSCREEN);
m_cyScreen = GetSystemMetrics(SM_CYSCREEN);
// WS_EX_TRANSPARENT 讓滑鼠事件穿透 (但本工具需捕捉滑鼠,故不加此樣式)
// 改用 WS_EX_TOPMOST 置頂 + WS_EX_LAYERED 透明度控制
CreateEx(WS_EX_TOPMOST,
AfxRegisterWndClass(0, AfxGetApp()->LoadStandardCursor(IDC_ARROW)),
title, WS_POPUP, 0, 0, m_cxScreen, m_cyScreen,
NULL, NULL, NULL);
m_pDC = GetDC(); // 整個螢幕 DC
// 建立記憶體 DC 備份原始螢幕內容,供重繪時還原
m_memDC.CreateCompatibleDC(m_pDC);
CBitmap bmp; bmp.CreateCompatibleBitmap(m_pDC, m_cxScreen, m_cyScreen);
m_memDC.SelectObject(&bmp);
m_memDC.BitBlt(0,0,m_cxScreen,m_cyScreen, m_pDC,0,0, SRCCOPY);
DeleteObject(bmp.Detach());
m_bSecondClick = FALSE;
m_pParent = pParent;
}
void CTarget::OnMouseMove(UINT nFlags, CPoint point) {
// 先還原背景 (清除舊十字線)
m_pDC->BitBlt(0,0,m_cxScreen,m_cyScreen, &m_memDC,0,0, SRCCOPY);
// 繪製全螢幕十字線
m_pDC->MoveTo(0, point.y); m_pDC->LineTo(m_cxScreen, point.y);
m_pDC->MoveTo(point.x, 0); m_pDC->LineTo(point.x, m_cyScreen);
// 若已點選第一點,繪製連接線
if (m_bSecondClick) {
m_pDC->MoveTo(m_ptStart);
m_pDC->LineTo(point);
}
CWnd::OnMouseMove(nFlags, point);
}
void CTarget::OnLButtonDown(UINT nFlags, CPoint point) {
if (!m_bSecondClick) {
m_ptStart = point;
m_bSecondClick = TRUE;
// 還原背景
m_pDC->BitBlt(0,0,m_cxScreen,m_cyScreen, &m_memDC,0,0, SRCCOPY);
// 紅色標記第一點
CPen pen(PS_SOLID, 2, RGB(255,0,0));
CPen* pOld = m_pDC->SelectObject(&pen);
m_pDC->MoveTo(point.x-10, point.y); m_pDC->LineTo(point.x+10, point.y);
m_pDC->MoveTo(point.x, point.y-10); m_pDC->LineTo(point.x, point.y+10);
m_pDC->SelectObject(pOld);
DeleteObject(pen.Detach());
// 更新記憶體 DC 保留紅標記
m_memDC.BitBlt(0,0,m_cxScreen,m_cyScreen, m_pDC,0,0, SRCCOPY);
} else {
m_ptEnd = point;
CalculateDistance();
// 清理資源
m_pDC->DeleteDC(); m_memDC.DeleteDC();
DestroyWindow();
// 通知父視窗顯示結果
m_pParent->PostMessage(WM_MEASURE_DONE);
}
CWnd::OnLButtonDown(nFlags, point);
}
void CTarget::OnRButtonUp(UINT nFlags, CPoint point) {
m_pDC->DeleteDC(); m_memDC.DeleteDC();
DestroyWindow();
m_pParent->PostMessage(WM_MEASURE_CANCEL);
}
void CTarget::CalculateDistance() {
double dx = m_ptEnd.x - m_ptStart.x;
double dy = m_ptEnd.y - m_ptStart.y;
m_nPixelDist = (int)sqrt(dx*dx + dy*dy); // 像素
CDC* pDC = GetDC();
// 公制:MM_LOMETRIC = 0.1 mm
pDC->SetMapMode(MM_LOMETRIC);
POINT ls = m_ptStart, le = m_ptEnd;
pDC->DPtoLP(&ls); pDC->DPtoLP(&le);
dx = le.x - ls.x; dy = le.y - ls.y;
m_dMetricDist = sqrt(dx*dx + dy*dy) / 10.0; // mm
// 英制:MM_LOENGLISH = 0.01 inch
pDC->SetMapMode(MM_LOENGLISH);
ls = m_ptStart; le = m_ptEnd;
pDC->DPtoLP(&ls); pDC->DPtoLP(&le);
dx = le.x - ls.x; dy = le.y - ls.y;
m_dInchDist = sqrt(dx*dx + dy*dy) / 100.0; // inch
ReleaseDC(pDC);
}
class CElecRulerDlg : public CDialog {
CTarget m_target;
// ...
void OnMeasure() {
m_target.CreateTarget(_T("Electronic Ruler"), this);
}
// 處理 WM_MEASURE_DONE 讀取 m_target.m_nPixelDist 等顯示
};
| 技術 | 用途 |
|---|---|
WS_EX_TOPMOST | 視窗永遠置頂,覆蓋所有應用 |
| 記憶體 DC 雙緩衝 | 無閃爍重繪十字線、標記 |
SetMapMode + DPtoLP | 裝置座標轉邏輯座標,自動換算物理單位 |
自訂訊息 WM_MEASURE_DONE | 解耦測量視窗與主 UI |