Day 6 核心主題:映射模式
為什麼需要映射模式?
預設 MM_TEXT:1 邏輯單位 = 1 像素 (設備座標)。
**痛點**:相同邏輯座標在不同解析度、不同 DPI、印表機上對應的物理尺寸完全不同。
**目標**:用**邏輯座標**描述圖形幾何屬性 (如「畫一個 4cm x 4cm 的圓」),由映射模式自動轉換為設備座標,實現**「所見即所得」跨設備一致性**。
八大標準映射模式
| 模式 | X 方向 | Y 方向 | 單位/比例 | 可改原點 | 典型用途 |
| MM_TEXT | 右增 | 下增 | 1 像素 | 是 | 像素級精確控制、遊戲繪圖 |
| MM_LOENGLISH | 右增 | 上增 | 0.01 英吋 | 否 | 英制工程圖 |
| MM_HIENGLISH | 右增 | 上增 | 0.001 英吋 | 否 | 高精度英制 |
| MM_LOMETRIC | 右增 | 上增 | 0.1 mm | 否 | 公制工程圖 |
| MM_HIMETRIC | 右增 | 上增 | 0.01 mm (10 微米) | 否 | **高精度公制、列印預覽** |
| MM_TWIPS | 右增 | 上增 | 1/1440 英吋 (1 twip = 1/20 pt) | 否 | 文字排版、RichEdit 內部單位 |
| MM_ISOTROPIC | 右增 | 上增 | **可變,但 X/Y 比例固定 1:1** | 是 | 保持圖形不變形的縮放 (正圓永遠是圓) |
| MM_ANISOTROPIC | 右增 | 上增 | **可變,X/Y 比例獨立** | 是 | **任意縮放、填滿客戶區、非等比變形** |
**關鍵差異**:
- 固定比例模式 (LO/HI ENGLISH/METRIC/TWIPS):**Y 軸向上增**,不可改。
- 可變比例模式 (ISO/ANISO):**Y 軸向上增**,但可透過 SetViewportExt 設定負值翻轉 Y 軸。
核心 API:設定映射模式與比例
// 1. 選擇模式
pDC->SetMapMode(MM_ANISOTROPIC);
// 2. 設定視窗範圍 (邏輯單位) — 代表「整個文檔邏輯大小」
pDC->SetWindowExt(1000, 1000); // 邏輯寬高各 1000 單位
// 3. 設定視口範圍 (設備單位/像素) — 代表「客戶區像素大小」
CRect rectClient;
GetClientRect(&rectClient);
pDC->SetViewportExt(rectClient.Width(), -rectClient.Height()); // 負號翻轉 Y 軸
// 4. 設定視口原點 (設備座標) — 邏輯 (0,0) 映射到視口中心
pDC->SetViewportOrg(rectClient.Width()/2, rectClient.Height()/2);
// 5. 繪製:邏輯座標 (-500,-500) 到 (500,500) 自動填滿整個客戶區
pDC->Ellipse(CRect(-500, -500, 500, 500)); // 完美橢圓隨視窗縮放
座標轉換函數 (CDC 成員)
LPtoDP(LPPOINT/ LPRECT/ LPSIZE):邏輯 → 設備 (如滑鼠點擊座標轉為邏輯座標做碰撞檢測)。
DPtoLP(...):設備 → 邏輯 (如將邏輯圖形邊界轉為設備座標做 PtInRect 命中測試)。
座標系使用鐵律 (必背)
| 場景 | 必用座標系 | 原因 |
| CDC 所有繪圖函數參數 | 邏輯座標 | 映射模式自動轉換 |
| CWnd 所有函數參數 (MoveWindow, SetWindowPos, GetClientRect...) | 設備座標 | 直接對應螢幕/客戶區像素 |
| 區域 (CRgn) 定義 | 設備座標 | 區域操作在設備空間進行 |
| 命中測試 | 設備座標 | 滑鼠座標本身是設備座標 |
| 長期儲存幾何數據 (文檔序列化) | 邏輯座標 或 物理座標 (mm/inch) | 設備座標會隨滾動、縮放、DPI 改變 |
實戰專案:滾動視圖 CScrollView 完整實現
需求
- 文檔邏輯大小 20000 x 30000 (單位 0.01mm, 即 2m x 3m 超大圖紙)。
- 視窗可滾動瀏覽,滾動頁面/行步長自定義。
- 畫一個固定邏輯位置的 4cm 圓,滑鼠點擊圓內變色,鍵盤方向鍵控制滾動。
關鍵代碼節錄
// 1. 專案類型:Single Document + Base Class = CScrollView
// 2. 視圖類成員
private:
int m_nColor; // GRAY_BRUSH / WHITE_BRUSH
CRect m_rectEllipse; // 邏輯座標: (0,0,4000,-4000) = 4cm x 4cm
// 3. OnInitialUpdate — 設定滾動參數 (最佳時機)
void CMyApp6View::OnInitialUpdate() {
CScrollView::OnInitialUpdate();
CSize sizeTotal(20000, 30000); // 文檔總邏輯大小
CSize sizePage(sizeTotal.cx/2, sizeTotal.cy/2); // PageUp/Down 步長
CSize sizeLine(sizeTotal.cx/50, sizeTotal.cy/50); // 方向鍵步長
SetScrollSizes(MM_HIMETRIC, sizeTotal, sizePage, sizeLine);
}
// 4. 構造函數 — 成員初始化列表 (效率+正確性)
CMyApp6View::CMyApp6View() : m_rectEllipse(0, 0, 4000, -4000) {
m_nColor = GRAY_BRUSH;
}
// 5. OnDraw — 純邏輯座標繪製
void CMyApp6View::OnDraw(CDC* pDC) {
pDC->SelectStockObject(m_nColor);
pDC->Ellipse(m_rectEllipse);
}
// 6. OnLButtonDown — 設備座標命中測試
void CMyApp6View::OnLButtonDown(UINT nFlags, CPoint point) {
CClientDC dc(this);
OnPrepareDC(&dc); // 套用映射模式
CRect rectDev = m_rectEllipse;
dc.LPtoDP(rectDev); // 邏輯矩形 → 設備矩形
if (rectDev.PtInRect(point)) {
m_nColor = (m_nColor == GRAY_BRUSH) ? WHITE_BRUSH : GRAY_BRUSH;
InvalidateRect(rectDev); // 只重繪該矩形區域
}
}
// 7. OnKeyDown — 鍵盤控制滾動
void CMyApp6View::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) {
switch (nChar) {
case VK_HOME: OnVScroll(SB_TOP, 0, NULL); OnHScroll(SB_LEFT, 0, NULL); break;
case VK_END: OnVScroll(SB_BOTTOM, 0, NULL); OnHScroll(SB_RIGHT, 0, NULL); break;
case VK_UP: OnVScroll(SB_LINEUP, 0, NULL); break;
case VK_DOWN: OnVScroll(SB_LINEDOWN, 0, NULL); break;
case VK_PRIOR: OnVScroll(SB_PAGEUP, 0, NULL); break;
case VK_NEXT: OnVScroll(SB_PAGEDOWN, 0, NULL); break;
case VK_LEFT: OnHScroll(SB_LINELEFT, 0, NULL); break;
case VK_RIGHT: OnHScroll(SB_LINERIGHT, 0, NULL); break;
}
}
構造函數成員初始化列表深度解析
CMyApp6View::CMyApp6View() : m_rectEllipse(0,0,4000,-4000) { m_nColor = GRAY_BRUSH; }
**為何不用函數體賦值?**
1. **必要性**:成員為類類型且無默認構造函數 (如 CMember(int)) 時,**只能**用初始化列表。
2. **效率**:避免「默認構造 → 再賦值」兩次函數調用。對 CString, std::vector 等大型類節省記憶體分配/拷貝。
3. **順序陷阱**:初始化順序**按聲明順序**,而非列表順序。聲明順序與初始化列表順序保持一致可避免未定義行為。
雷神心法:映射模式選擇決策樹
- 需要**物理尺寸精確** (列印、CAD、報表) →
MM_HIMETRIC / MM_LOMETRIC / MM_TWIPS。
- 需要**填滿客戶區、保持比例** (圖表、預覽) →
MM_ISOTROPIC。
- 需要**任意拉伸、非等比** (遊戲地圖、圖片瀏覽) →
MM_ANISOTROPIC。
- 像素級遊戲/特效/自定義渲染管線 →
MM_TEXT (或直接用 Direct2D/OpenGL)。
下一篇預告
Day 7:GDI 核心 — CDC 設備環境類體系、CPen/CBrush/CFont/CBitmap/CPalette/CRgn 六大 GDI 物件、庫存物件、SelectObject 保存/恢復模式、句柄持久化技巧。
- 上一篇文章: 《VC++技術內幕》學習筆記(五):視窗生命周期五大關鍵消息深度解析 (WM_CREATE 到 WM_NCDESTROY)
- 下一篇文章: 《VC++技術內幕》學習筆記(七):GDI 核心 — CDC 設備環境、六大 GDI 物件、庫存物件、SelectObject 保存恢復模式
Copyright ©
程式碼之家 All Rights Reserved