技術演進中的開發沉思-38 MFC系列:關于打印

打印程序也是MFC開發中不能忽視的一個環節,現在做打印開發so easy。但當年做打印開發還是挺麻煩。在當年的桌面程序里就像拼圖的最后一塊,看著簡單,實則要把屏幕上的像素世界,準確映射到打印機的物理紙張上。而MFC 的打印機制就像老照相館的暗房:你不用懂顯影液的配方(打印機驅動),但得知道怎么調整光圈(設備上下文),才能讓照片(打印效果)和底片(屏幕顯示)一致。

一、打印

MFC 用 “設備上下文”(DC)機制把這套流程封裝得嚴絲合縫。CDC這個類就像個萬能畫板 —— 往pDC->m_hDC里塞屏幕設備句柄,它就是能顯示彩色像素的電子畫布;換成打印機句柄,就變成了能輸出墨點的物理畫布。我第一次掉坑里,是發現屏幕上清晰的折線圖,打印出來竟縮成了左上角的小方塊。對著調試器看了半天才明白:屏幕用的是 “像素坐標”,100 像素在 17 寸顯示器上大概 1.5 厘米;而打印機認的是 “邏輯單位”,同樣 100 單位在 A4 紙上能占 3 厘米。就像用慣了厘米尺的人突然換成英寸尺,比例沒換算對,畫出的東西自然走樣。

解決這個問題時,我在OnPrepareDC里加了段坐標轉換代碼:


void CMyView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo){CView::OnPrepareDC(pDC, pInfo);if (pDC->IsPrinting()) {// 把打印機邏輯單位設為毫米pDC->SetMapMode(MM_LOMETRIC);// 調整原點到左上角(打印機默認原點在左下角)pDC->SetWindowOrg(0, -2970); // A4紙高度297毫米,轉換為0.1毫米單位}}

這段代碼讓打印機突然 “看懂” 了屏幕坐標 —— 就像給兩個說不同方言的人配了翻譯。改完那天,李教授來試打印,看著紙張緩緩吐出時,他手指在圖表邊緣比了比:“這下對齊了,比我用尺子量著畫強多了。”

二、MFC 的 “默認菜譜”

MFC 最貼心的地方,是把打印的基礎流程做成了 “半成品菜”。就像超市里切好的凈菜,你不用自己洗菜切菜(寫設備初始化代碼),只要按口味加點調料(重寫繪圖函數)就行。默認機制下,CView類的OnDraw函數是個多面手 —— 屏幕刷新時它被調用,打印時 MFC 會自動把打印機 DC 傳進來,讓同一段繪圖代碼在兩種設備上生效。

早期做實驗報告打印時,我就靠這個機制省了不少事。OnDraw里先畫標題,再畫數據表格,最后加實驗員簽名欄:


void CMyView::OnDraw(CDC* pDC){CMyDoc* pDoc = GetDocument();ASSERT_VALID(pDoc);if (!pDoc) return;// 標題用粗體CFont font, *pOldFont;font.CreatePointFont(160, _T("宋體"), pDC); // 16pt字體pOldFont = pDC->SelectObject(&font);pDC->TextOut(100, 50, _T("材料抗壓實驗報告"));pDC->SelectObject(pOldFont);// 畫表格線CPen pen(PS_SOLID, 1, RGB(0,0,0)), *pOldPen;pOldPen = pDC->SelectObject(&pen);DrawTableBorder(pDC); // 自定義畫邊框函數pDC->SelectObject(pOldPen);// 填充數據DrawExperimentData(pDC, pDoc->m_dataList);}

但這套默認機制有個明顯短板:它不知道 “一頁該裝多少內容”。有次李教授導入了三十組實驗數據,結果打印機把所有表格擠在一頁紙上,字小得要用放大鏡看。這就像寫文章忘了分段,用戶看著費勁。后來我在OnPreparePrinting里加了分頁邏輯:


BOOL CMyView::OnPreparePrinting(CPrintInfo* pInfo){if (!DoPreparePrinting(pInfo))return FALSE;// 計算總頁數:每頁最多顯示8組數據CMyDoc* pDoc = GetDocument();int nTotalData = pDoc->m_dataList.GetCount();int nMaxPage = (nTotalData + 7) / 8; // 向上取整pInfo->SetMaxPage(nMaxPage > 0 ? nMaxPage : 1);return TRUE;}

加了這段代碼后,數據會自動分到多頁,每頁底部還能加上頁碼。客戶拿著打印好的報告翻了兩頁,突然說:“這比我當年用打字機方便多了 —— 那時候換頁得手動卷紙。”

三、給 Scribble “加分頁”

微軟的 Scribble 示例程序,是我們那代程序員的 “技術啟蒙教材”。這個能隨手涂鴉的小程序,默認打印時卻像把整本速寫本強行壓成一頁紙 —— 如果用戶畫了條橫貫三屏的曲線,打印出來就會變成細得像頭發絲的線條。我們做項目時,給它加了三個 “增強包”,讓它從 “便簽紙” 變成能裝訂的 “畫冊”。

頁設置對話框是第一個要加的。就像去打印店先選紙張,我用CPrintDialog做了個設置界面,讓用戶能選 A4 還是 B5,橫版還是豎版。調試時發現個有趣的細節:當用戶選 “橫向”,MFC 會自動調整CPrintInfo里的紙張尺寸,這時候繪圖坐標得跟著變。我在OnBeginPrinting里加了判斷:


void CMyScribbleView::OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo){CView::OnBeginPrinting(pDC, pInfo);// 記錄紙張方向和尺寸m_bLandscape = (pInfo->m_pPD->m_pd.Flags & PD_LANDSCAPE) != 0;m_sizePaper = pInfo->m_sizePaper; // 單位是0.01毫米}

有次測試時,我選了橫向打印,曲線突然跑到紙外面去了。查了半天才發現,他畫的曲線坐標是按豎版紙張算的。后來我在繪圖前加了坐標適配:如果是橫向,就把 X 和 Y 軸的繪圖范圍對調,就像把畫布旋轉 90 度再下筆。

坐標映射是第二個重點。屏幕上用像素定位,打印機用物理單位,這中間得有個 “換算器”。SetMapMode函數就像把尺子刻度從 “像素” 換成 “毫米”,但實際用的時候要注意:打印機的原點默認在紙張左下角,而屏幕原點在左上角,不調整的話,畫出來的東西會上下顛倒。我通常這么處理:


void CMyScribbleView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo){CView::OnPrepareDC(pDC, pInfo);if (pDC->IsPrinting()) {pDC->SetMapMode(MM_LOMETRIC); // 1單位=0.1毫米// 把原點移到左上角,Y軸向下為正pDC->SetWindowOrg(0, -m_sizePaper.cy);}}

這么一改,屏幕上從左到右畫的線條,打印出來也是從左到右,不會變成 “鏡像效果” 了。

智能分頁是最費腦筋的。我給每個涂鴉線段加了 “高度標記”,就像在文章里標上段落長度。打印時程序會累計高度,超過紙張高度就自動分頁。代碼里用OnPrint代替OnDraw,根據當前頁碼決定畫哪些內容:


void CMyScribbleView::OnPrint(CDC* pDC, CPrintInfo* pInfo){int nCurPage = pInfo->m_nCurPage;int nStartIndex = (nCurPage - 1) * m_nMaxLinesPerPage;int nEndIndex = min(nStartIndex + m_nMaxLinesPerPage, m_lines.GetSize());// 只繪制當前頁包含的線段for (int i = nStartIndex; i < nEndIndex; i++) {DrawLine(pDC, m_lines[i]);}}

改完這個功能那天,我畫了條從北京到上海的曲線(模擬鐵路樞紐線路),程序自動分成 3 頁打印,拼接起來剛好是完整的路線。這種 “技術實現想法” 的瞬間,大概就是程序員的快樂時刻。

四、打印預覽

打印預覽絕對是 MFC 里的 “溫柔設計”。早年沒這功能時,我們常為了調格式浪費半盒 A4 紙 —— 有時候差一毫米就偏出邊框,得改一行代碼重新編譯,再打印出來看效果。預覽功能就像寄信前先看一眼信封,在屏幕上就能看到最終效果。

MFC 的預覽原理其實很巧妙:它先創建個內存 DC 當 “虛擬打印機”,把內容畫到內存里,再縮放顯示到屏幕窗口。CPrintPreviewState這個類會管理預覽窗口的縮放比例,你點 “放大”,它就把內存里的圖像按 1:1 顯示;點 “縮小”,就按比例縮小,像用放大鏡看地圖。

我做報表系統時,加了個 “實時預覽” 功能 —— 用戶改了表格樣式,預覽窗口會立刻刷新。實現時用了OnUpdate消息:


void CMyReportView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint){if (IsPreviewMode()) {// 如果在預覽模式,強制刷新Invalidate();}CView::OnUpdate(pSender, lHint, pHint);}

這種方法實現后,客戶調整表頭格式,就只需要拖動鼠標調整列寬了,在沒有打印功能的時候,很多圖表,都是客戶用尺子量著畫表格的,?那時候改一次報表得重新抄一遍。而當年我們開發的應用改變了他們的工作方式,其實技術進步的意義,有時就是把人從機械勞動里解放出來。

最后小結:

現在想想,MFC 的打印機制,其實藏著一套產品思維:默認機制解決 80% 的基礎需求(不用重復造輪子);增強功能應對個性化場景(給高手留發揮空間);預覽功能則照顧到用戶的 “安全感”(避免失誤)。MFC 打印的可貴之處,在于它用類封裝了復雜的設備交互邏輯,讓我們能專注于業務需求 —— 這和現在的云打印 API 的設計理念不謀而合。當年調試OnPrint函數時領悟的 “設備無關性” 原則,現在依然適用于云打印開發:無論底層是激光還是噴墨,代碼只需要關心 “打印什么”,而不是 “怎么打印”。技術會迭代,但解決問題的本質從未改變。就像從 MFC 到云打印,變的是實現手段,不變的是讓用戶 “用著順手” 的追求。未完待續..........

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/92411.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/92411.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/92411.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Apache Ignite 長事務終止機制

這段內容講的是 Apache Ignite 中長事務終止機制&#xff08;Long Running Transactions Termination&#xff09;&#xff0c;特別是關于分區映射交換&#xff08;Partition Map Exchange&#xff09;與事務超時設置&#xff08;Transaction Timeout&#xff09;之間的關系。下…

網絡編程---TCP協議

TCP協議基礎知識TCP&#xff08;Transmission Control Protocol&#xff0c;傳輸控制協議&#xff09;是互聯網核心協議之一&#xff0c;位于傳輸層&#xff08;OSI第4層&#xff09;&#xff0c;為應用層提供可靠的、面向連接的、基于字節流的數據傳輸服務。它與IP協議共同構成…

K 近鄰算法(K-Nearest Neighbors, KNN)詳解及案例

K近鄰算法&#xff08;K-Nearest Neighbors, KNN&#xff09;詳解及案例 一、基本原理 K近鄰算法是一種監督學習算法&#xff0c;核心思想是“物以類聚&#xff0c;人以群分”&#xff1a;對于一個新樣本&#xff0c;通過計算它與訓練集中所有樣本的“距離”&#xff0c;找出距…

深入理解 Redis 集群化看門狗機制:原理、實踐與風險

在分布式系統中&#xff0c;我們常常需要執行一些關鍵任務&#xff0c;這些任務要么必須成功執行&#xff0c;要么失敗后需要明確的狀態&#xff08;如回滾&#xff09;&#xff0c;并且它們的執行時間可能難以精確預測。如何確保這些任務不會被意外中斷&#xff0c;或者在長時…

Python機器學習:從零基礎到項目實戰

目錄第一部分&#xff1a;思想與基石——萬法歸宗&#xff0c;筑基問道第1章&#xff1a;初探智慧之境——機器學習世界觀1.1 何為學習&#xff1f;從人類學習到機器智能1.2 機器學習的“前世今生”&#xff1a;一部思想與技術的演進史1.3 為何是Python&#xff1f;——數據科學…

數據庫:庫的操作

1&#xff1a;查看所有數據庫SHOW DATABASES;2&#xff1a;創建數據庫CREATE DATABASE [ IF NOT EXISTS ] 數據庫名 [ CHARACTER SET 字符集編碼 | COLLATE 字符集校驗規則 | ENCRYPTION { Y | N } ];[]&#xff1a;可寫可不寫{}&#xff1a;必選一個|&#xff1a;n 選 1ENCR…

AngularJS 動畫

AngularJS 動畫 引言 AngularJS 是一個流行的JavaScript框架,它為開發者提供了一種構建動態Web應用的方式。在AngularJS中,動畫是一個強大的功能,可以幫助我們創建出更加生動和引人注目的用戶界面。本文將詳細介紹AngularJS動畫的原理、用法以及最佳實踐。 AngularJS 動畫…

SonarQube 代碼分析工具

??親愛的技術愛好者們,熱烈歡迎來到 Kant2048 的博客!我是 Thomas Kant,很開心能在CSDN上與你們相遇~?? 本博客的精華專欄: 【自動化測試】 【測試經驗】 【人工智能】 【Python】 ??全面掌握 SonarQube:企業代碼質量保障的利器 ?? 在當今 DevOps 流水線中,代碼…

vmware vsphere esxi6.5 使用工具導出鏡像

注&#xff1a;為什么使用這個工具&#xff0c;我這邊主要因為esxi6.5自身bug導致web導出鏡像會失敗一、下載VMware-ovftool到本地系統&#xff08;根據你的操作系統版本到官網下載安裝&#xff0c;此處略&#xff09;以下內容默認將VMware-ovftool安裝到windows 本地系統為例。…

ES 踩坑記:Set Processor 字段更新引發的 _source 污染

問題背景 社區的一個伙伴想對一個 integer 的字段類型添加一個 keyword 類型的子字段&#xff0c;然后進行精確匹配的查詢優化&#xff0c;提高查詢的速度。 整個索引數據量不大&#xff0c;并不想進行 reindex 這樣的復雜操作&#xff0c;就想到了使用 update_by_query 的存量…

如何徹底搞定 PyCharm 中 pip install 報錯 ModuleNotFoundError: No module named ‘requests’ 的問題

如何徹底搞定 PyCharm 中 pip install 報錯 ModuleNotFoundError: No module named ‘requests’ 的問題 在使用 PyCharm 開發 Python 項目時&#xff0c;ModuleNotFoundError: No module named requests 是一個常見但令人頭疼的問題。本篇博文將從環境配置、原因分析到多種解…

powerquery如何實現表的拼接主鍵

在做表過程中&#xff0c;有時候沒有基表&#xff0c;這個時候就要構造完整的主鍵&#xff0c;這樣才可以使之后匹配的數據不會因為主鍵不全而丟失數據 我的處理方法是吧多個表的主鍵拼在一起然后去重&#xff0c;構造一個單單之后之間的表作為基表去匹配數據 所以就喲啊用到自…

今日Github熱門倉庫推薦 第八期

今日Github熱門倉庫推薦2025-07-22 如果讓AI分別扮演 后端開發人員和前端開發人員&#xff0c;然后看看他們分別對github每天的trending倉庫感興趣的有哪些&#xff0c;并且給出他感興趣的理由&#xff0c;那會發生什么呢&#xff1f; 本內容通過Python AI生成&#xff0c;項…

Dify-13: 文本生成API端點

本文檔提供了有關 Dify 中與文本生成相關的 API 端點的全面信息。文本生成 API 支持無會話持久性的單次請求文本生成&#xff0c;使其適用于翻譯、摘要、文章寫作等非對話式人工智能應用場景。 概述 文本生成 API 端點允許開發人員將 Dify 的文本生成功能集成到不需要維護對話上…

Leetcode 3620. Network Recovery Pathways

Leetcode 3620. Network Recovery Pathways 1. 解題思路2. 代碼實現 題目鏈接&#xff1a;3620. Network Recovery Pathways 1. 解題思路 這一題我最開始想的是遍歷一下所有的網絡路徑&#xff0c;不過遇到了超時的情況。因此后來調整了一下處理思路&#xff0c;使用二分法的…

鏈路備份技術(鏈路聚合、RSTP)

一、鏈路聚合&#xff01;鏈路備份技術之一-----鏈路聚合&#xff08;Link Aggregation&#xff09;被視為鏈路備份技術&#xff0c;核心原因在于它能通過多條物理鏈路的捆綁&#xff0c;實現 “一條鏈路故障時&#xff0c;其他鏈路自動接管流量” 的冗余備份效果&#xff0c;同…

PyTorch新手實操 安裝

PyTorch簡介 PyTorch 是一個基于 Python 的開源深度學習框架&#xff0c;由 Meta AI&#xff08;原 Facebook AI&#xff09;主導開發&#xff0c;以動態計算圖&#xff08;Define-by-Run&#xff09;為核心&#xff0c;支持靈活構建和訓練神經網絡模型。其設計理念高度契合科…

Element Plus Table 組件擴展:表尾合計功能詳解

前言在現代數據驅動的社會中&#xff0c;數據分析和統計成為了非常重要的任務。為了更有效地分析數據和展示統計結果&#xff0c;前端開發人員可以使用Vue框架和Element Plus組件庫來實現數據的統計和分析功能。以下是一個關于如何在 Element Plus 的 el-table 組件中實現行匯總…

神經網絡 非線性激活層 正則化層 線性層

神經網絡 非線性激活層 作用&#xff1a;增強模型的非線性擬合能力 非線性激活層網絡&#xff1a; class activateNet(nn.Module):def __init__(self):super(activateNet,self).__init__()self.relu nn.ReLU()self.sigmoid nn.Sigmoid()def forward(self,input):#output sel…

【Vue進階學習筆記】組件通信專題精講

目錄前言props 父傳子原理說明使用場景代碼示例父組件 PropsTest.vue子組件 Child.vue自定義事件 $emit 子傳父原理說明使用場景代碼示例父組件 EventTest.vue子組件 Event2.vueEvent Bus 兄弟/跨層通信原理說明使用場景代碼示例事件總線 bus/index.ts兄弟組件通信示例Child2.v…