基於對話框的應用程式(無標題列、功能表),需在客戶區繪圖並支援列印。MFC 文檔/視圖架構自帶列印支援,但對話框專案需自行實作:建立印表機 DC、處理列印對話框、將客戶區內容轉印至列印 DC。
| 問題 | 解決策略 |
|---|---|
| 無功能表,如何觸發列印 | 註冊熱鍵 Ctrl+P (RegisterHotKey) 呼叫列印流程 |
不知印表機名稱,無法 CreateDC | 使用 CPrintDialog 讓使用者選擇印表機,取得 DEVMODE/DEVNAMES 建立 DC |
| 直接 BitBlt 客戶區到印表機 DC | 中介步驟:客戶區 → 記憶體 DC → 列印 DC,解決縮放與覆蓋問題 |
| 列印對話框遮蓋客戶區,導致 BitBlt 到黑框 | 關鍵技巧:在 OnPaint 中持續將客戶區內容備份至成員變數 CBitmap m_bmpMem;列印時直接使用此備份位圖 |
| 無標題列對話框拖移 | 處理 WM_NCHITTEST 回傳 HTCAPTION 實現任意處拖移 |
class CPrintDlg : public CDialog
{
CBitmap m_bmpMem; // 客戶區內容備份
CDC m_dcMem; // 對應記憶體 DC
BOOL m_bMemReady; // 備份是否就緒
// ...
};
OnPaint:繪圖並同步備份void CPrintDlg::OnPaint()
{
CPaintDC dc(this);
CRect rect; GetClientRect(&rect);
// 1. 建立/調整記憶體 DC 與位圖
if (!m_bMemReady || m_bmpMem.GetWidth() != rect.Width() || m_bmpMem.GetHeight() != rect.Height()) {
if (m_bMemReady) { m_bmpMem.DeleteObject(); m_dcMem.DeleteDC(); }
m_dcMem.CreateCompatibleDC(&dc);
m_bmpMem.CreateCompatibleBitmap(&dc, rect.Width(), rect.Height());
m_dcMem.SelectObject(&m_bmpMem);
m_bMemReady = TRUE;
}
// 2. 在記憶體 DC 上繪製實際內容(此處替換為你的繪圖邏輯)
CBrush brush(RGB(255,255,255)); // 白底
m_dcMem.FillRect(&rect, &brush);
// ... 你的繪圖代碼:m_dcMem.Ellipse(...), m_dcMem.TextOut(...) 等 ...
// 3. 將記憶體 DC 複製到螢幕(雙緩衝,無閃爍)
dc.BitBlt(0, 0, rect.Width(), rect.Height(), &m_dcMem, 0, 0, SRCCOPY);
}
OnPrintvoid CPrintDlg::OnPrint()
{
if (!m_bMemReady) return;
CPrintDialog dlg(FALSE); // FALSE = 列印對話框
if (dlg.DoModal() != IDOK) return;
// 1. 取得印表機 DC
HDC hPrinterDC = dlg.GetPrinterDC(); // MFC 封裝
if (!hPrinterDC) return;
CDC dcPrinter; dcPrinter.Attach(hPrinterDC);
// 2. 開始列印文件
DOCINFO di = { sizeof(DOCINFO), _T("對話框列印輸出") };
dcPrinter.StartDoc(&di);
dcPrinter.StartPage();
// 3. 計算縮放比例(保持長寬比置中)
CRect rectClient; GetClientRect(&rectClient);
int nPageW = dcPrinter.GetDeviceCaps(HORZRES);
int nPageH = dcPrinter.GetDeviceCaps(VERTRES);
double scale = min((double)nPageW / rectClient.Width(), (double)nPageH / rectClient.Height());
int drawW = (int)(rectClient.Width() * scale);
int drawH = (int)(rectClient.Height() * scale);
int x = (nPageW - drawW) / 2;
int y = (nPageH - drawH) / 2;
// 4. 直接使用備份位圖輸出(不受列印對話框遮蓋影響)
dcPrinter.StretchBlt(x, y, drawW, drawH, &m_dcMem, 0, 0, rectClient.Width(), rectClient.Height(), SRCCOPY);
dcPrinter.EndPage();
dcPrinter.EndDoc();
dcPrinter.Detach(); // 釋放 DC 所有權,避免 MFC 釋放時出錯
}
BOOL CPrintDlg::OnInitDialog()
{
CDialog::OnInitDialog();
RegisterHotKey(m_hWnd, 1, MOD_CONTROL, 'P'); // ID=1
return TRUE;
}
void CPrintDlg::OnDestroy()
{
UnregisterHotKey(m_hWnd, 1);
CDialog::OnDestroy();
}
LRESULT CPrintDlg::OnHotKey(WPARAM wParam, LPARAM lParam)
{
if (wParam == 1) OnPrint();
return 0;
}
// 需在訊息對映表加入 ON_MESSAGE(WM_HOTKEY, OnHotKey)
完整專案(VC6 / XP 測試通過):
https://www.vckbase.com/code/downcode.asp?id=2313
對話框列印的核心在於**記憶體 DC 持久化備份客戶區內容**,配合 CPrintDialog 取得印表機 DC,最後用 StretchBlt 處理縮放。此模式同樣適用於無 MFC 文檔/視圖支援的任何視窗列印需求。