MFC对话框打印功能实战:从预览到输出的完整实现

VC++ MFC对话框程序实现完整打印与预览功能实战指南 在Windows桌面应用开发中,文档打印是基础且关键的功能模块,尤其在工业控制、嵌入式上位机等专业领域。其核心原理在于通过设备上下文(CDC)进行图形绘制,并利用映射模式实现屏幕逻辑坐标到打印机物理坐标的精确转换。这项技术的工程价值在于能直接与硬件驱动交互,提供不依赖运行时库的高效本地输出。典型的应用场景包括票据打印、标签机控制及遗留系统维护。本文通过一个可复用的MFC对话框项目源码,深入解析了从分页计算、坐标映射到打印预览的全流程实现,其中涉及CPrintInfo类的状态管理与GDI资源防泄漏等关键热词, 阅读详情

1. 为什么要在对话框里折腾打印?聊聊我的踩坑经历

很多刚接触MFC的朋友,包括当年的我自己,一听到“打印”两个字就头大。文档/视图架构下,打印功能有现成的框架,向导一点,基类一继承,好像还挺简单。但一到对话框程序里,就傻眼了——菜单栏没有“文件”->“打印”,视图类那套现成的OnPreparePrinting、OnPrint也用不上。难道为了一个打印功能,要把整个程序架构改成文档/视图吗?这显然不现实。

我接手过不少老项目,清一色的对话框应用,客户的需求却一个比一个“硬核”:要打印报表、要打印凭证、要支持预览、还要能选纸张和打印机。一开始我也尝试过偷懒,比如直接用CDC画到打印机上,或者调用ShellExecute打开文档让系统打印。但前者代码又臭又长,难以维护;后者用户体验割裂,而且无法精确控制打印内容和布局。踩过几次坑之后,我明白了:在MFC对话框里实现一套专业、可控、用户体验好的打印功能,不是可选项,而是企业级应用的必选项。

所以,今天我就把自己这些年摸爬滚打总结出来的“对话框打印实战手册”分享给你。我们不搞那些虚头巴脑的理论,直接上干货,从打印预览窗口的创建,到纸张参数的动态设置,再到最终打印输出的每一行关键代码,我都会掰开揉碎了讲清楚。目标就一个:让你看完就能在自己的对话框项目里,复制粘贴,稍作修改,立刻拥有一个媲美专业软件的打印模块。

2. 核心思路:借壳生蛋,构建我们自己的打印框架

原始文章给了一个非常棒的起点,它跳出了MFC文档视图的束缚,自己搭建了一套打印框架。但它的代码更像是一个“黑盒”,直接甩给你六个文件,你可能知其然不知其所以然。我们先来把它的核心思路捋清楚,这比直接看代码更重要。

MFC的打印和预览,本质上依赖于两个核心类:CView(特别是它的OnPrint等虚函数)和CFrameWnd(作为预览的容器窗口)。在对话框程序里,我们没有CView,也没有一个现成的框架窗口来承载预览界面。那怎么办?自己造。

原始代码的思路我称之为“借壳生蛋”:

  1. 创建一个自定义的框架窗口(CPrintFrame):这个窗口不显示菜单栏、工具栏,它唯一的作用就是作为我们打印视图的“宿主”或“舞台”。当我们需要预览或打印时,就动态创建这个框架窗口。
  2. 创建一个自定义的打印视图(CPrintView):它继承自CScrollView(或CView),我们把所有打印相关的逻辑,比如设置纸张、准备打印、绘制内容,都写在这个类里。这个视图会被嵌入到上面创建的CPrintFrame中。
  3. 创建一个自定义的预览视图(CPrintPreviewView):继承自MFC的CPreviewView,主要目的是为了重写“关闭预览”和“直接打印”等按钮的行为,使其能和我们自定义的框架窗口正确交互,避免窗口句柄混乱导致程序崩溃。

简单来说,当用户在对话框上点击“打印预览”时,我们动态创建一个CPrintFrame,在里面创建一个CPrintView,然后命令这个视图进入预览模式。MFC内置的预览机制就会接管,调用我们重写的OnDraw、OnPrint来渲染每一页。整个过程对主对话框来说是透明的,预览窗口关闭后,一切恢复原状。

这个架构的巧妙之处在于,它最大程度复用了MFC内置的强大打印和预览机制,我们只需要关心“画什么”,而不用重新发明“怎么预览”、“怎么走打印对话框”这些轮子。下面,我们就一步步把这个架构搭建起来。

3. 第一步:搭建舞台——创建自定义打印框架窗口(CPrintFrame)

这个类是整个打印功能的“后台总控”。我们来看关键代码,并解释每一处设计的原因。

首先在头文件CPrintFrame.h里,我们这样定义:

#pragma once
#include "CPrintView.h" // 包含我们即将实现的打印视图
// ... 其他必要的包含

class CPrintFrame : public CFrameWnd
{
    DECLARE_DYNCREATE(CPrintFrame)
protected:
    CPrintFrame(); // 动态创建所需的默认构造
    virtual ~CPrintFrame();
public:
    // 关键构造函数!传入主对话框指针和预览标志
    CPrintFrame(CYourMainDialog* pMainDlg, BOOL bPreview);
    
    // 成员变量
    CYourMainDialog* m_pMainDlg; // 指向主对话框,用于数据获取和窗口恢复
    CPrintView* m_pCPrintView;   // 我们自定义的打印视图
    BOOL m_bPreview;             // 标志:TRUE为预览,FALSE为直接打印

protected:
    DECLARE_MESSAGE_MAP()
public:
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); // 窗口创建时初始化
    afx_msg void OnClose(); // 关闭窗口时的清理
    void SetOrientation(short orientation); // 设置纸张方向(横向/纵向)
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs); // 窗口创建前的样式设置
};

重点在实现文件CPrintFrame.cpp的构造函数和OnCreate函数:

CPrintFrame::CPrintFrame(CYourMainDialog* pMainDlg, BOOL bPreview)
    : m_pMainDlg(pMainDlg), m_pCPrintView(NULL), m_bPreview(bPreview)
{
    CString strTitle = bPreview ? _T("打印预览") : _T("打印");
    // 创建一个隐藏的框架窗口,不显示标准菜单/工具栏
    Create(NULL, strTitle, WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE,
           CRect(100, 100, 900, 700), NULL, NULL, 0, NULL);
}

int CPrintFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
        return -1;
    if (!m_pMainDlg) return -1; // 安全检查

    // 1. 创建打印视图(CPrintView)的上下文
    CCreateContext context;
    context.m_pNewViewClass = RUNTIME_CLASS(CPrintView); // 告诉框架要创建什么类
    context.m_pCurrentFrame = this; // 当前框架就是自己
    context.m_pCurrentDoc = NULL;   // 对话框程序没有文档,设为NULL
    context.m_pLastView = NULL;

    // 2. 动态创建CPrintView对象,并嵌入到本框架中
    m_pCPrintView = (CPrintView*)CreateView(&context);
    if (!m_pCPrintView) return -1;

    // 3. 将新建的视图设为活动视图
    SetActiveView(m_pCPrintView);
    m_pCPrintView->ShowWindow(SW_SHOW);
    m_pCPrintView->m_pCPrintFrame = this; // 让视图知道它的框架窗口是谁

    // 4. 关键一步:将应用程序的主窗口指针暂时指向自己
    // 这样,打印预览的工具栏、菜单命令才能正确路由到这个新框架
    CWinApp* pApp = AfxGetApp();
    pApp->m_pMainWnd = this;

    // 5. 根据是预览还是打印,触发不同的流程
    if (m_bPreview) {
        // 隐藏主对话框,避免用户误操作
        m_pMainDlg->ShowWindow(SW_HIDE);
        // 命令打印视图开始预览模式
        m_pCPrintView->OnFilePrintPreview();
    } else {
        // 直接发送打印命令
        m_pCPrintView->SendMessage(WM_COMMAND, ID_FILE_PRINT);
    }
    return 0;
}

这里有个巨坑我当年踩了好久:AfxGetApp()->m_pMainWnd的切换。MFC的消息路由、命令更新机制都依赖于这个全局主窗口指针。如果不把它临时改成我们的CPrintFrame,预览窗口的“下一页”、“放大”等按钮可能根本没反应,或者消息发错了对象。记住,在OnClose时一定要把它改回去!

void CPrintFrame::OnClose()
{
    // 恢复主窗口指针到原来的对话框!!!
    CWinApp* pApp = AfxGetApp();
    pApp->m_pMainWnd = m_pMainDlg;
    // 显示主对话框
    if (m_pMainDlg) m_pMainDlg->ShowWindow(SW_SHOW);
    // 销毁自己
    DestroyWindow();
}

SetOrientation函数展示了如何动态修改打印机设置,这在用户需要切换横版/竖版打印时非常有用:

void CPrintFrame::SetOrientation(short orientation) // DMORIENT_PORTRAIT 或 DMORIENT_LANDSCAPE
{
    PRINTDLG pd = {0};
    pd.lStructSize = sizeof(PRINTDLG);
    // 获取当前打印机的默认设置
    if (AfxGetApp()->GetPrinterDeviceDefaults(&pd)) {
        if (pd.hDevMode != NULL) {
            LPDEVMODE lpDevMode = (LPDEVMODE)::GlobalLock(pd.hDevMode);
            if (lpDevMode) {
                lpDevMode->dmOrientation = orientation; // 修改方向
                // 同样,你可以在这里修改 dmPaperSize (纸张大小,如DMPAPER_A4)
                // lpDevMode->dmPaperSize = DMPAPER_A4;
                ::GlobalUnlock(pd.hDevMode);
            }
        }
    }
}

4. 第二步:灵魂画手——实现自定义打印视图(CPrintView)

这个类是真正干活的,负责告诉打印机“画什么”。它继承自CScrollView,这样还能顺便支持滚动视图(虽然打印预览时不一定需要)。我们重点关注几个必须重写的虚函数。

在CPrintView.h中,我们声明核心函数:

class CPrintView : public CScrollView
{
    // ... 其他宏和构造函数
public:
    CPrintFrame* m_pCPrintFrame; // 回指框架窗口

    // 核心打印相关函数
    virtual BOOL OnPreparePrinting(CPrintInfo* pInfo); // 打印初始化
    virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo); // 开始打印(可创建GDI资源)
    virtual void OnPrint(CDC* pDC, CPrintInfo* pInfo); // 绘制每一页!重中之重
    virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo); // 结束打印(清理资源)
    virtual void OnEndPrintPreview(CDC* pDC, CPrintInfo* pInfo, POINT point, CPreviewView* pView); // 结束预览

    // 工具函数
    void SetPaper(short dmOrientation, short dmPaperSize); // 设置纸张
    void OnFilePrintPreview(); // 触发预览
    void OnFilePrint(); // 触发打印

    // 你的实际绘制函数
    void DrawMyReport(CDC* pDC, CPrintInfo* pInfo); // 例如,绘制报表
    void DrawMyCertificate(CDC* pDC, CPrintInfo* pInfo); // 例如,绘制证书
};

OnPreparePrinting是打印流程的起点,在这里我们可以设置打印对话框的默认参数,比如最大页数:

BOOL CPrintView::OnPreparePrinting(CPrintInfo* pInfo)
{
    // 默认最大页数为1,如果你有多页,就在这里计算并设置
    pInfo->SetMaxPage(1);
    // 调用DoPreparePrinting会弹出系统的打印设置对话框
    // 如果不想弹出对话框直接打印,可以设置 pInfo->m_bDirect = TRUE;
    pInfo->m_bDirect = FALSE;
    return DoPreparePrinting(pInfo);
}

OnPrint是最核心的函数,每一页的内容都在这里绘制。pDC是打印机或预览的设备上下文,你像在屏幕上画图一样操作它就行,但必须注意坐标转换。

void CPrintView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
{
    // 千万别调用基类的OnPrint,我们要自己控制一切
    // CScrollView::OnPrint(pDC, pInfo); 

    // 示例:根据不同的打印任务,调用不同的绘制函数
    // 可以从主对话框获取一个任务标识
    int nPrintTask = m_pCPrintFrame->m_pMainDlg->GetCurrentPrintTask();
    
    switch(nPrintTask) {
        case TASK_REPORT:
            DrawMyReport(pDC, pInfo);
            break;
        case TASK_CERTIFICATE:
            DrawMyCertificate(pDC, pInfo);
            break;
        default:
            // 画个默认内容或断言
            break;
    }
}

现在,让我们深入DrawMyReport这个函数,看看在打印机DC上绘图的关键技巧:

void CPrintView::DrawMyReport(CDC* pDC, CPrintInfo* pInfo)
{
    // 1. 获取可打印区域(单位:设备像素)
    int nHorzRes = pDC->GetDeviceCaps(HORZRES); // 可打印宽度
    int nVertRes = pDC->GetDeviceCaps(VERTRES); // 可打印高度
    CRect rectPrintable(0, 0, nHorzRes, nVertRes);

    // 2. 设置映射模式(关键!)
    // MM_TEXT模式下,1逻辑单位=1设备像素,但不同打印机DPI不同,直接画会很小。
    // 推荐使用 MM_ANISOTROPIC 或 MM_HIENGLISH 等物理映射模式。
    pDC->SetMapMode(MM_HIMETRIC); // 1逻辑单位 = 0.01毫米
    // 设置窗口范围和视口范围,进行缩放
    pDC->SetWindowExt(10000, 10000); // 逻辑窗口大小为 100 x 100 毫米
    pDC->SetViewportExt(nHorzRes, nVertRes); // 视口大小就是可打印区域像素

    // 3. 计算边距(例如,四边各留2厘米)
    const double dMarginCm = 2.0;
    // 将厘米转换为当前映射模式下的逻辑单位(MM_HIMETRIC下,1厘米=1000逻辑单位)
    int nMargin = (int)(dMarginCm * 1000); 
    CRect rectContent(nMargin, -nMargin, 10000 - nMargin, -(10000 - nMargin)); // 注意Y轴向上为正

    // 4. 开始绘制你的内容
    // 画一个边框
    pDC->Rectangle(rectContent);
    
    // 设置字体(注意:打印机字体需要创建,最好在OnBeginPrinting中创建并缓存)
    CFont fontPrint;
    fontPrint.CreatePointFont(200, _T("宋体"), pDC); // 20点字体
    CFont* pOldFont = pDC->SelectObject(&fontPrint);
    
    // 绘制文本
    CString strTitle = _T("销售报表");
    pDC->TextOut(rectContent.left + 1000, rectContent.top - 1500, strTitle); // 标题位置
    
    // 5. 绘制表格、数据等...这里就是你的业务逻辑了
    // 例如,从 m_pCPrintFrame->m_pMainDlg 获取数据,然后循环绘制
    
    // 6. 恢复旧的GDI对象
    pDC->SelectObject(pOldFont);
    // fontPrint 会在析构时自动删除
}

关于OnEndPrintPreview,这里有个大坑需要处理:预览窗口关闭时,必须正确清理并恢复主窗口,否则程序可能会崩溃或失去响应。

void CPrintView::OnEndPrintPreview(CDC* pDC, CPrintInfo* pInfo, POINT point, CPreviewView* pView)
{
    // 1. 调用基类,完成MFC内部的预览清理
    CScrollView::OnEndPrintPreview(pDC, pInfo, point, pView);
    
    // 2. 恢复主窗口指针到原始对话框(这里和CPrintFrame::OnClose作用类似,双重保险)
    if (m_pCPrintFrame && m_pCPrintFrame->m_pMainDlg) {
        CWinApp* pApp = AfxGetApp();
        pApp->m_pMainWnd = m_pCPrintFrame->m_pMainDlg;
        m_pCPrintFrame->m_pMainDlg->ShowWindow(SW_SHOW);
    }
    
    // 3. 销毁预览框架窗口
    if (m_pCPrintFrame) {
        // 注意:不要直接delete,通过发送消息或DestroyWindow
        m_pCPrintFrame->PostMessage(WM_CLOSE);
    }
}

5. 第三步:搞定预览——定制预览视图(CPrintPreviewView)

原始代码中的CPrintPreviewView主要是为了重写预览窗口工具栏上两个按钮的行为:“关闭”和“直接打印”。因为默认的CPreviewView会认为主框架窗口是它自己,关闭时可能会错误地结束整个应用。我们需要把它“拉回”到我们的自定义框架。

这个类相对简单,继承CPreviewView并重写两个命令处理函数即可:

// CPrintPreviewView.cpp
void CPrintPreviewView::OnPreviewClose()
{
    // 获取我们自定义的框架窗口
    CPrintFrame* pFrame = (CPrintFrame*)GetParentFrame();
    if (pFrame) {
        // 将应用主窗口恢复为原来的对话框
        CWinApp* pApp = AfxGetApp();
        pApp->m_pMainWnd = pFrame->m_pMainDlg;
        // 显示主对话框
        if (pFrame->m_pMainDlg) pFrame->m_pMainDlg->ShowWindow(SW_SHOW);
        // 销毁预览框架
        pFrame->DestroyWindow();
    }
}

void CPrintPreviewView::OnPreviewPrint()
{
    // 直接转发打印命令给我们的打印视图
    if (m_pPrintView) {
        m_pPrintView->SendMessage(WM_COMMAND, ID_FILE_PRINT);
    }
    // 打印完成后,同样需要关闭预览窗口
    OnPreviewClose();
}

6. 第四步:串联一切——在对话框里触发打印和预览

最后,我们需要在原来的主对话框里,添加两个按钮:“打印预览”和“直接打印”。并为它们添加事件处理函数。

假设你的对话框类叫CMyDialog,上面有两个按钮IDC_BTN_PREVIEW和IDC_BTN_PRINT。

// 在 MyDialog.cpp 中
#include "CPrintFrame.h" // 包含我们的打印框架头文件

void CMyDialog::OnBnClickedBtnPreview()
{
    // 禁用按钮,防止重复点击(可选)
    GetDlgItem(IDC_BTN_PREVIEW)->EnableWindow(FALSE);
    
    // 创建打印预览框架窗口,传入this指针(主对话框)和TRUE表示预览
    // 注意:这里new出来的对象,会在其OnClose中自行销毁,我们不用手动delete。
    CPrintFrame* pPrintFrame = new CPrintFrame(this, TRUE);
    if (pPrintFrame && pPrintFrame->m_hWnd) {
        // 窗口创建成功,流程会由CPrintFrame::OnCreate接管
    } else {
        // 创建失败,清理资源
        delete pPrintFrame;
        AfxMessageBox(_T("创建打印预览窗口失败!"));
        GetDlgItem(IDC_BTN_PREVIEW)->EnableWindow(TRUE);
    }
}

void CMyDialog::OnBnClickedBtnPrint()
{
    // 直接打印的逻辑类似,但传入FALSE
    CPrintFrame* pPrintFrame = new CPrintFrame(this, FALSE);
    if (!pPrintFrame || !pPrintFrame->m_hWnd) {
        delete pPrintFrame;
        AfxMessageBox(_T("启动打印失败!"));
    }
    // 直接打印窗口会自动销毁,无需其他操作
}

7. 实战进阶:动态纸张配置与多页打印技巧

掌握了基础框架后,我们再来解决两个实际开发中高频出现的问题:如何让用户动态选择纸张,以及如何打印多页内容。

动态纸张配置的关键在于修改DEVMODE结构体,并在调用DoPreparePrinting之前或之后应用它。一个更好的做法是重写CPrintView的OnPreparePrinting,在调用DoPreparePrinting之后修改设备模式,因为用户可能在打印对话框中更改了设置。

BOOL CPrintView::OnPreparePrinting(CPrintInfo* pInfo)
{
    // 先调用标准对话框,获取用户可能的初始选择
    if (!DoPreparePrinting(pInfo)) {
        return FALSE;
    }
    
    // 获取打印机的DEVMODE
    CDC printDC;
    printDC.Attach(pInfo->m_pPD->GetPrinterDC());
    LPDEVMODE lpDevMode = (LPDEVMODE)::GlobalLock(pInfo->m_pPD->m_pd.hDevMode);
    
    if (lpDevMode) {
        // 这里可以根据你的业务逻辑,强制或修改纸张设置
        // 例如,从对话框获取用户选择的纸张类型
        if (m_pCPrintFrame->m_pMainDlg->GetUserSelectedPaper() == PAPER_A5) {
            lpDevMode->dmPaperSize = DMPAPER_A5;
            lpDevMode->dmOrientation = DMORIENT_PORTRAIT;
        } else if (m_pCPrintFrame->m_pMainDlg->GetUserSelectedOrientation() == LANDSCAPE) {
            lpDevMode->dmOrientation = DMORIENT_LANDSCAPE;
        }
        // 重要:修改后需要更新设备上下文
        ::GlobalUnlock(pInfo->m_pPD->m_pd.hDevMode);
        printDC.ResetDC(lpDevMode); // 重置DC使设置生效
    }
    printDC.Detach();
    return TRUE;
}

多页打印的核心是准确计算页数,并在OnPrint中根据pInfo->m_nCurPage绘制不同的页。计算页数通常在OnBeginPrinting中进行,因为这里你已经获得了打印机DC,可以精确计算内容高度。

void CPrintView::OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo)
{
    // 假设我们有一个函数 CalculateTotalPages,根据数据行数和每页行数计算总页数
    int nTotalRows = m_pCPrintFrame->m_pMainDlg->GetDataRowCount();
    int nRowsPerPage = CalculateRowsPerPage(pDC); // 根据字体和页面高度计算每页行数
    int nTotalPages = (nTotalRows + nRowsPerPage - 1) / nRowsPerPage; // 向上取整
    
    pInfo->SetMaxPage(nTotalPages); // 设置最大页数
}

void CPrintView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
{
    int nCurrentPage = pInfo->m_nCurPage;
    int nRowsPerPage = ...; // 需要和OnBeginPrinting中计算的一致
    
    // 计算当前页要显示的数据范围
    int nStartRow = (nCurrentPage - 1) * nRowsPerPage;
    int nEndRow = min(nStartRow + nRowsPerPage, GetTotalRows()) - 1;
    
    // 绘制页眉(如页码“第X页/共Y页”)
    DrawPageHeader(pDC, nCurrentPage, pInfo->GetMaxPage());
    
    // 循环绘制当前页的数据行
    for (int i = nStartRow; i <= nEndRow; ++i) {
        CString strRowData = GetRowData(i);
        // 计算这一行在页面上的Y坐标
        int yPos = /* 根据i, nStartRow, 行高计算 */;
        pDC->TextOut(marginLeft, yPos, strRowData);
    }
    
    // 绘制页脚
    DrawPageFooter(pDC);
}

8. 避坑指南与性能优化建议

走通整个流程后,还有一些细节和坑点需要注意,这些往往是决定功能是否稳健、体验是否流畅的关键。

第一个大坑:GDI资源泄漏。 在OnPrint里频繁创建字体、画笔、画刷,如果页面很多,会导致GDI对象耗尽,程序崩溃。最佳实践是在OnBeginPrinting中创建所有打印需要的GDI对象并缓存起来,在OnEndPrinting中统一删除。

class CPrintView {
    CFont m_fontTitle, m_fontBody;
    CPen m_penGrid;
    // ...
};

void CPrintView::OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo)
{
    // 创建打印专用字体,使用点单位更精确
    m_fontTitle.CreatePointFont(240, _T("黑体"), pDC); // 24点
    m_fontBody.CreatePointFont(180, _T("宋体"), pDC);  // 18点
    m_penGrid.CreatePen(PS_SOLID, 5, RGB(200, 200, 200)); // 0.5点宽的灰色网格线
}

void CPrintView::OnEndPrinting(CDC* pDC, CPrintInfo* pInfo)
{
    // 删除GDI对象
    m_fontTitle.DeleteObject();
    m_fontBody.DeleteObject();
    m_penGrid.DeleteObject();
}

void CPrintView::DrawMyReport(CDC* pDC, CPrintInfo* pInfo)
{
    // 直接使用成员变量,无需每次创建
    CFont* pOldFont = pDC->SelectObject(&m_fontTitle);
    // ... 绘制
    pDC->SelectObject(pOldFont);
}

第二个坑:坐标系统混乱。 屏幕DC和打印机DC的DPI(每英寸像素数)通常不同。如果你用像素坐标硬编码,在屏幕上预览正常的布局,打印出来可能很小或很大。务必使用映射模式(如MM_HIMETRIC, MM_LOENGLISH)或根据GetDeviceCaps(LOGPIXELSX/Y)进行比例换算,来确保打印尺寸符合物理尺寸(厘米/英寸)。

第三个坑:预览模式下的刷新问题。 有时在预览窗口缩放或翻页时,内容会显示不全或残留。这通常是因为在OnPrint或OnDraw中没有正确处理背景擦除或视图区域。确保在绘制前调用pDC->FillSolidRect(rectClip, RGB(255,255,255))填充背景,并且你的绘制代码能够正确响应pInfo->m_rectDraw这个裁剪区域。

性能建议:对于数据量非常大的报表,不要在OnBeginPrinting时就计算所有页面的布局,这可能导致界面卡顿。可以改为在OnPrint中动态计算当前页的内容。更高级的做法是使用后台线程准备打印数据,主线程只负责绘制。另外,对于复杂的图形(如图表),可以考虑在OnBeginPrinting中预先渲染到位图上,在OnPrint中直接贴图,这会快很多。

最后,记得在各种边界条件下测试:没有默认打印机时、用户取消打印对话框时、打印过程中拔掉打印机时、预览窗口被异常关闭时。良好的错误处理和资源清理,是企业级应用稳定性的基石。这套框架我已经在多个生产环境中验证过,只要你理解了每个环节的作用,并根据自己的业务数据填充DrawMyReport这样的函数,就能构建出强大且稳定的对话框打印功能。

MFC Windows 程序设计[180]之打印预览与打印设置(附源码) MFC是微软公司提供的一个类库(class libraries),用以减少应用程序开发人员的工作量。其中包含大量Windows句柄封装类和很多Windows的内建控件和组件的封装类。有很多初学MFC的小伙伴们都觉得MFC比较难学,看书吧,看了就忘了,网上的一些教程又是写的含糊不清,故而学习起来很吃力...... 故此,本专栏亲自研究,实践,由易到难,层层深入,将调试过程中所遭所遇进行详细讲解,注意事项进行一一列举,希望能够帮助到各位初学MFC的小伙伴,避免走弯路,费时费力。...... 阅读详情

相关推荐

VC6环境下MFC打印预览功能实现代码详解

尽管 VC6 + MFC 已显老旧,但在工业自动化、医疗设备、金融终端等领域仍有广泛应用。掌握其打印预览机制,不仅能修复历史遗留问题,更能为现代化迁移提供坚实基础。核心要点回顾GDI 是跨设备绘图的基石,DC 是状态容器;映射模式统一了屏幕与打印机的坐标体系;文档/视图架构实现了数据与展示的解耦;利用虚拟 DC 实现无损预览;分页计算与导航控制决定了用户体验流畅度;💬 “技术没有新旧,只有是否被正确理解和运用。

weixin_28681379的博客 698

MFC 对话框打印和打印预览知识总结

1、准备知识 VS2017 MFC对话框程序打印及打印预览的实现_微澜111的博客-优快云博客_mfc打印预览MFC文档/视图的应用程序,向导给了打印及打印预览的标准支持。使这类应用程序打印及打印预览工作得以简化。另一类对话框程序却没有相应支持,从MFC打印及打印预览的标准支持入手,可以在对话框程序中,增加三个类以支持打印及打印预览,本文介绍了这三个类的实现。 打印及打印预览是编写应用程序经常要解决的问题,为了理解VC++对话框程序的打印及打印预览实现,要先掌握基于文档/视图的应用程序打印及打印预览的.

tajon1226的专栏 2143

VC++对话框程序实现打印功能完整源码解析

在Windows应用程序开发中,将打印功能集成到对话框界面是提升用户体验和增强软件实用性的关键环节。本章从整体架构出发,阐述对话框与打印系统之间的交互逻辑,剖析为何需要在MFC或Win32框架下实现本地化打印支持。重点介绍VC++环境下GDI(图形设备接口)在打印任务中的核心地位,以及用户如何通过对话框控件触发并配置打印流程。// 示例:通过按钮消息映射触发打印该函数将启动打印对话框并初始化打印机DC,为后续输出做准备。同时,分析传统打印方案的局限性,并引出基于设备上下文(DC)的高效打印机制。

weixin_42514750的博客 1085

MFC在对话框中实现打印和打印预览

mfc 对话框没有打印预览功能,自己使用创建单文档,利用单文档进行打印和打印预览

qq_36626674的博客 927

MFC调用打印机 自定义排版

MFC调用打印机,实现自定义排版,本程序使用的vs2010集成开发环境,能按照自己的习惯来排版内容,支持打印多页,排版多页

使用MFC编写打印程序.pdf

使用MFC编写打印程序.pdf

MFC打印 打印预览

这两篇文章是我看了那么多写MFC打印功能中介绍的相对较好的,所以转载了过来,以方便自己以后学习使用 前段时间需要将日志信息通过打印机打印出来。 总结了一下其中的一些用法: 1:用CPrintDialog 可以呼出窗口让用户选择打印机或者采用默认的打印机直接打印。     StartPage…..EndPage  完成一页文档的打印。  如需改变DC设置,需要调用ReSetDC()

renyongwang1988的专栏 6573

泛微Ecology流程数据表结构解析:workflow_requestbase和workflow_currentoperator到底怎么关联查询?

本文深入解析泛微Ecology系统中workflow_requestbase和workflow_currentoperator表的结构与关联机制,提供SQL查询语句示例,帮助开发者高效查询流程数据。涵盖待办事项、已办事项、办结事项和流程监控等典型场景,并分享性能优化技巧和常见问题排查指南。

weixin_33738555的博客 815

VS2017 MFC对话框程序打印及打印预览的实现

MFC文档/视图的应用程序,向导给了打印及打印预览的标准支持。使这类应用程序打印及打印预览工作得以简化。另一类对话框程序却没有相应支持,从MFC打印及打印预览的标准支持入手,可以在对话框程序中,增加三个类以支持打印及打印预览,本文介绍了这三个类的实现。 打印及打印预览是编写应用程序经常要解决的问题,为了理解VC++对话框程序的打印及打印预览实现,要先掌握基于文档/视图的应用程序打印及打印预览的基本原理。所以分为两部分介绍。

weixin_42403113的博客 4729

生物信息学实战:如何用STRING数据库和R语言搞定蛋白互作网络分析(附完整代码)

本文提供了一份完整的生物信息学实战指南,详细介绍了如何利用STRING数据库和R语言进行蛋白互作网络分析。从差异表达基因列表出发,涵盖数据准备、数据库连接、网络构建到高级可视化与模块识别的全流程,并附有可复现的完整R代码,帮助研究人员深入解析转录组数据背后的蛋白互作关系。

milk5的博客 950

程序化广告行业(23/89):深度剖析广告效果评估指标

大家好,我一直希望能和大家一起在技术领域不断探索、共同进步。在之前的文章中,我们了解了程序化广告中腾讯的业务布局和广告效果评估的基础概念,今天让我们进一步深入剖析广告效果评估的各项指标,这对理解程序化广告的运作机制至关重要

yyy173611的博客 4866

MFC对话框打印与打印预览:让应用程序打印自如

MFC对话框打印与打印预览:让应用程序打印自如 【下载地址】MFC对话框打印与打印预览 在基于对话框的MFC应用程序中实现打印及打印预览功能从未如此简单。通过这份资源文件,您将轻松掌握对话框打印和打印预览的核心技术,显著提升应用程序的功能性和用户体验。资源文件详细介绍了如何实现对话框打印功能、打印预览功能,以及与打印相关...

gitblog_06717的博客 1068

Python实现支付宝转账接口

由于工作需要使用python开发一个自动转账接口,记录一下开发过程。 首先需要在蚂蚁金服上申请开通开发者账户,有了开发者账户就可以使用沙箱进行开发了。 在开发之前我们需要在沙箱应用中填写密钥,密钥的获取可以使用阿里提供的工具包自动生成。 前期准备工作完成了,接下来是编写代码部分。主要用到了python-alipay-sdk库,使用pip安装即可,如果安装的过程中遇到问题推荐使用Anaconda...

FanMLei的博客 8517

MFC对话框打印及预览功能完美版:提升开发效率的利器

MFC对话框打印及预览功能完美版:提升开发效率的利器 去发现同类优质开源项目:https://gitcode.com/ 项目介绍 在MFC(Microsoft Foundation Class)开发中,对话框的打印及预览功能是许多项目中不可或缺的一部分。然而,实现这一功能往往需要开发者投入大量的时间和精力,尤其是在处理各种边界情况和稳定性问题时。为了解决这一痛点,我们推出了“MFC对话框打印及预览...

gitblog_09713的博客 781

Cisco Packet Tracert 无线路由器配置

Cisco Packet Tracert 无线路由器配置

yushan_caoyu的博客 2万+

共享单车数据抓取与分析:使用Python爬虫抓取共享单车使用数据与地图信息

通过本篇博客,我们展示了如何使用Python爬虫抓取共享单车的使用数据和地图信息,并通过简单的数据分析得出有价值的见解。在实际应用中,数据的收集与分析不仅能帮助城市管理者更好地规划单车站点,还能为用户提供更精准的单车使用体验。未来,我们可以将此分析与机器学习模型相结合,进一步预测单车的使用趋势,优化调度算法,实现更加智能的共享单车管理。

2201_76125261的博客 2206

MFC对话框的打印预览实现

1.创建MFC对话框工程,选择在共享库中调用。 2.添加文件: MainFrm.h, MyPreviewView.h, WrapperView.h, MainFrm.cpp, WrapperView.cpp 分别为: MainFrm.h 1 // MainFrm.h : interface of the CMainFrame class 2 // 3 ///////////...

weixin_34247155的博客 563

排水模型1:CAD格式的管网数据处理

本文介绍了将CAD排水管网数据转换为GIS格式的技术方法。首先明确了CAD中管段绘制的6项规范要求,包括图层管理、流向表示和节点标识等。其次提出了利用CAD现有属性字段(如图层名、线型比例等)存储管网断面尺寸、上下游标高等关键信息的具体方案。最后详细说明了将CAD数据导入ArcGIS数据库的操作流程,包括创建个人数据库、设置坐标系、筛选导入管段数据等步骤。该方法为排水管网建模提供了标准化的数据预处理方案,为后续节点自动生成等操作奠定了基础。

jinxi1978的博客 587

MFC对话框打印功能实战:手把手教你实现打印预览与直接打印(附完整代码)

本文详细解析了MFC对话框程序中实现打印预览与直接打印功能的完整方案。通过创建临时框架窗口、自定义打印视图类以及处理设备上下文等关键技术,解决了对话框程序中的打印挑战,并提供了优化DPI适配、多页打印等实用技巧,附有完整代码示例。

weixin_29015051的博客 231

STM32 使用IQmath实现SVPWM

文章目录SVPWMIQMATHIQmathLib测试部分程序附件 SVPWM SVPWM是空间矢量脉宽调制(Space Vector Pulse Width Modulation)的简称,通常由三相逆变器的六个功率开关管组成,经过特定的时序和换相所所产生的脉冲宽度调制波,最终输出的波形可能会十分接近理想的正弦波形。 具体如下图所示;左侧为复平面,即空间矢量,右侧为时域的正弦波形; 关于SVPWM原理的文章非常多,这里可以推荐一下网上一个非常不错的教程《SVPWM的原理及法则推导和控制算法详解第五修改版》,

一名热爱技术的工程师的博客。分享技术干货,记录美好生活。永远相信美好的事情即将发生。 2万+

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

VS2017 MFC对话框程序打印及打印预览实现程序:功能强大,操作简便

VS2017 MFC对话框程序打印及打印预览实现程序:功能强大,操作简便 去发现同类优质开源项目:https://gitcode.com/ 项目介绍 在现代软件开发中,打印功能是许多桌面应用程序不可或缺的一部分。VS2017 MFC对话框程序打印及打印预览实现程序正是为满足这一需求而设计。该项目利用MFC框架,在Visual Studio 2017环境下开发,实现了打印及打印预览的核心功能,让开发...

gitblog_06781的博客 408

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

开源精品:MFC对话框打印预览——开发者的打印利器

开源精品:MFC对话框打印预览——开发者的打印利器 去发现同类优质开源项目:https://gitcode.com/ 在追求高效办公与高质量软件开发的今天,一个强大且稳定的打印及预览功能几乎是必备项。对于MFC开发者而言,【MFC对话框打印及预览功能完美版】无疑是一个巨大的福音。本文旨在深度解析这一开源项目,展示其独特魅力,引导您探索并利用它提升您的应用体验。 项目简介 针对MFC对话框程序的开发...

gitblog_09701的博客 586

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

vc++打印及打印预览功能的实现

VC++对话框程序打印及打印预览的实现 发布单位:湖北三峡职业技术学院本站原创 提交日期:2006-8-1 16:43:32 阅读次数:4840 摘要: MFC文档/视图的应用程序,向导给了打印及打印预览的标准支持。使这类应用程序打印及打印预览工作得以简化。另一类对话框程序却没有相应支持,从MFC打印及打印预览的标准支持入手,可以在对话框程序中,增加三个类以支持打印及打印预览,本文

dailygrace的专栏 4553

cprintdialog预览_mfc对话框上添加打印预览和打印

以下的内容是照着优快云上下载的一份代码写的,如有不对望指正打印预览和打印流程在对话框类建立一个框架类的对象指针成员,框架类可以派生自 CFrame,当要打印的时候,现创建出框架对象,可以将当前的对话框的窗口对象传到框架对象中保存,为什么?下面会说到.然后框架对象调用 Create 创建自己的窗口,在框架的OnCreate函数中创建view, view是派生自 CScrollView 的,并且设置...

weixin_29792607的博客 682

MFC 打印功能,无打印预览

对话框打印,网上一搜一大堆,基本分2类: A类: CPrintDialog.DoModal,然后在模态对话框里选打印机、打印配置; B类:GetPrinterDeviceDefaults,调用默认打印机。 我的工作内容是理解以上2类后,再根据MSDN,实现MDF对话框后台指定打印机打印。 废话不多说,上菜~ 功能:基于对话框的MFC打印(非文档视图结构),指定打印机,后台打印(不弹出对话框) 思路...

南巷近海不见云 757
上一篇: 【实战分享】联想Z50-70升级WiFi6网卡:绕过BIOS白名单的硬核操作指南
下一篇: 【Vue3实战】一站式Office文件预览方案:从零集成PPTX、Word与Excel
寂静夜空35
博客等级 码龄3年 60粉丝 1797原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值