Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / MFC

Printing by MFC WITHOUT Document/View architecture

4.60/5 (3 votes)
1 Mar 2015CPOL 19.2K  

Introduction

Hello

 MFC support printing only in Doc/View architecture model. But if you don't like this model (Like me),

and you want printing, you encounter a problem. I in this article explain How to easily solve this problem.

The code originaly is by MFC but I modify it to use in our purpose.

Using the code

 First step, we must declare an abstract class and name it to "CPrintContentBase". This class has only pure members.

class CPrintContentBase : public CObject
{
public:
    virtual CString GetTitle() const = 0;

    virtual void OnPrepareDC(CDC* pDC, CPrintInfo* pInfo = NULL) = 0;

    virtual BOOL OnPreparePrinting(CPrintInfo* pInfo) = 0;

    virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo) = 0;

    virtual void OnPrint(CDC* pDC, CPrintInfo* pInfo) = 0;

    virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo) = 0;
};

Now, add following codes to the mainframe source file, (Recommended using a seperated file).

Before add this method to the mainframe class.

void DoPrint(CPrintContentBase* pProvider);

And create a header (i name it "print.h") file and add following code to it.

#pragma once

extern BOOL DoPreparePrinting(CPrintInfo* pInfo);

The source of print.cpp file

#include "stdafx.h"
#include "app.h" // include proper app header file
#include "MainFrm.h"  // include proper mainframe header file 
#include "Print.h"

#include <WinSpool.h>

BOOL CALLBACK _AbortProc(HDC, int)
{
    // TODO: Replace following line with your abort check function
    // I imagine this function is defined in the app class

    return !theApp.UserAborted(); 
}

BOOL DoPreparePrinting(CPrintInfo* pInfo)
{
    ASSERT(pInfo != NULL);
    ASSERT(pInfo->m_pPD != NULL);

    if (pInfo->m_pPD->m_pd.nMinPage > pInfo->m_pPD->m_pd.nMaxPage)
        pInfo->m_pPD->m_pd.nMaxPage = pInfo->m_pPD->m_pd.nMinPage;

    // don't prompt the user if we're doing print preview, printing directly,
    // or printing via IPrint and have been instructed not to ask

    CWinApp* pApp = AfxGetApp();
    if (pInfo->m_bPreview || pInfo->m_bDirect ||
        (pInfo->m_bDocObject && !(pInfo->m_dwFlags & PRINTFLAG_PROMPTUSER)))
    {
        if (pInfo->m_pPD->m_pd.hDC == NULL)
        {
            // if no printer set then, get default printer DC and create DC without calling
            // print dialog.
            if (!pApp->GetPrinterDeviceDefaults(&pInfo->m_pPD->m_pd))
            {
                // bring up dialog to alert the user they need to install a printer.
                if (!pInfo->m_bDocObject || (pInfo->m_dwFlags & PRINTFLAG_MAYBOTHERUSER))
                if (pApp->DoPrintDialog(pInfo->m_pPD) != IDOK)
                    return FALSE;
            }

        if (pInfo->m_pPD->m_pd.hDC == NULL)
        {
            // call CreatePrinterDC if DC was not created by above
            if (pInfo->m_pPD->CreatePrinterDC() == NULL)
                return FALSE;
        }
    }

    // set up From and To page range from Min and Max
    pInfo->m_pPD->m_pd.nFromPage = (WORD)pInfo->GetMinPage();
    pInfo->m_pPD->m_pd.nToPage = (WORD)pInfo->GetMaxPage();
    }
    else
    {
        // otherwise, bring up the print dialog and allow user to change things
        // preset From-To range same as Min-Max range
        pInfo->m_pPD->m_pd.nFromPage = (WORD)pInfo->GetMinPage();
        pInfo->m_pPD->m_pd.nToPage = (WORD)pInfo->GetMaxPage();

        if (pApp->DoPrintDialog(pInfo->m_pPD) != IDOK)
            return FALSE; // do not print
    }

    ASSERT(pInfo->m_pPD != NULL);
    ASSERT(pInfo->m_pPD->m_pd.hDC != NULL);
    if (pInfo->m_pPD->m_pd.hDC == NULL)
        return FALSE;

    pInfo->m_nNumPreviewPages = pApp->m_nNumPreviewPages;
    ENSURE(pInfo->m_strPageDesc.LoadString(IDS_PREVIEWPAGEDESC));
    return TRUE;
}

void CMainFrame::DoPrint(CPrintContentBase* pProvider)
{
    ASSERT(pProvider != NULL);

    if (pProvider == NULL)
        return;

    // get default print info
    CPrintInfo printInfo;
    ASSERT(printInfo.m_pPD != NULL); // must be set

    if (LOWORD(AfxGetMainWnd()->GetCurrentMessage()->wParam) == ID_FILE_PRINT_DIRECT)
    {
        CCommandLineInfo* pCmdInfo = AfxGetApp()->m_pCmdInfo;

        if (pCmdInfo != NULL)
        {
            if (pCmdInfo->m_nShellCommand == CCommandLineInfo::FilePrintTo)
            {
                printInfo.m_pPD->m_pd.hDC = ::CreateDC(pCmdInfo->m_strDriverName,
                pCmdInfo->m_strPrinterName, pCmdInfo->m_strPortName, NULL);
                if (printInfo.m_pPD->m_pd.hDC == NULL)
                {
                    AfxMessageBox(IDS_FAILED_TO_START_PRINT);
                    return;
                }
            }
        }

        printInfo.m_bDirect = TRUE;
    }

    if (pProvider->OnPreparePrinting(&printInfo))
    {
        // hDC must be set (did you remember to call DoPreparePrinting?)
        ASSERT(printInfo.m_pPD->m_pd.hDC != NULL);

        // gather file to print to if print-to-file selected
        CString strOutput;
        if (printInfo.m_pPD->m_pd.Flags & PD_PRINTTOFILE && !printInfo.m_bDocObject)
        {
            // construct CFileDialog for browsing
            CString strDef(MAKEINTRESOURCE(IDS_PRINTDEFAULTEXT));
            CString strPrintDef(MAKEINTRESOURCE(IDS_PRINTDEFAULT));
            CString strFilter(MAKEINTRESOURCE(IDS_PRINTFILTER));
            CString strCaption(MAKEINTRESOURCE(IDS_PRINTCAPTION));
            CFileDialog dlg(FALSE, strDef, strPrintDef,
            OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, strFilter, NULL, 0);
            dlg.m_ofn.lpstrTitle = strCaption;

            if (dlg.DoModal() != IDOK)
                return;

            // set output device to resulting path name
            strOutput = dlg.GetPathName();
        }

        // set up document info and start the document printing process
        CString strTitle;
        strTitle = pProvider->GetTitle();

        DOCINFO docInfo;
        memset(&docInfo, 0, sizeof(DOCINFO));
        docInfo.cbSize = sizeof(DOCINFO);
        docInfo.lpszDocName = strTitle;
        CString strPortName;
        if (strOutput.IsEmpty())
        {
            docInfo.lpszOutput = NULL;
            strPortName = printInfo.m_pPD->GetPortName();
        }
        else
        {
            docInfo.lpszOutput = strOutput;
            ::GetFileTitle(strOutput, strPortName.GetBuffer(_MAX_PATH), _MAX_PATH);
        }

        // setup the printing DC
        CDC dcPrint;
        if (!printInfo.m_bDocObject)
        {
            dcPrint.Attach(printInfo.m_pPD->m_pd.hDC); // attach printer dc
            dcPrint.m_bPrinting = TRUE;
        }

        printInfo.m_rectDraw.SetRect(0, 0,
        dcPrint.GetDeviceCaps(HORZRES),
        dcPrint.GetDeviceCaps(VERTRES));
        dcPrint.DPtoLP(&printInfo.m_rectDraw);

        pProvider->OnBeginPrinting(&dcPrint, &printInfo);

        if (!printInfo.m_bDocObject)
            dcPrint.SetAbortProc(_AbortProc);

        // disable main window while printing & init printing status dialog
        // Store the Handle of the Window in a temp so that it can be enabled
        // once the printing is finished
        CWnd* hwndTemp = AfxGetMainWnd();
        hwndTemp->EnableWindow(FALSE);
        //CPrintingDialog dlgPrintStatus(this);

        CString strTemp;

        // start document printing process
        if (!printInfo.m_bDocObject)
        {
            printInfo.m_nJobNumber = dcPrint.StartDoc(&docInfo);
            if (printInfo.m_nJobNumber == SP_ERROR)
            {
                // enable main window before proceeding
                hwndTemp->EnableWindow(TRUE);

                // cleanup and show error message
                pProvider->OnEndPrinting(&dcPrint, &printInfo);

                dcPrint.Detach(); // will be cleaned up by CPrintInfo destructor
                AfxMessageBox(IDS_FAILED_TO_START_PRINT);
                return;
            }
        }

        // Guarantee values are in the valid range
        UINT nEndPage = printInfo.GetToPage();
        UINT nStartPage = printInfo.GetFromPage();

        if (nEndPage < printInfo.GetMinPage())
            nEndPage = printInfo.GetMinPage();
        if (nEndPage > printInfo.GetMaxPage())
            nEndPage = printInfo.GetMaxPage();

        if (nStartPage < printInfo.GetMinPage())
            nStartPage = printInfo.GetMinPage();
        if (nStartPage > printInfo.GetMaxPage())
            nStartPage = printInfo.GetMaxPage();

        int nStep = (nEndPage >= nStartPage) ? 1 : -1;
        nEndPage = (nEndPage == 0xffff) ? 0xffff : nEndPage + nStep;

        VERIFY(strTemp.LoadString(IDS_PRINTPAGENUM));

        // If it's a doc object, we don't loop page-by-page
        // because doc objects don't support that kind of levity.

        BOOL bError = FALSE;
        if (printInfo.m_bDocObject)
        {
            pProvider->OnPrepareDC(&dcPrint, &printInfo);
            pProvider->OnPrint(&dcPrint, &printInfo);
        }
        else
        {
             // TODO: Replace following line with your operation start function 

            theApp.ProgressiveOperationBegan(TRUE, IDS_MESSAGE_PRINTING, nEndPage - nStartPage);

            // begin page printing loop
            for (printInfo.m_nCurPage = nStartPage;
            printInfo.m_nCurPage != nEndPage; printInfo.m_nCurPage += nStep)
            {
                pProvider->OnPrepareDC(&dcPrint, &printInfo);

                // check for end of print
                if (!printInfo.m_bContinuePrinting)
                break;

                // write current page
                TCHAR szBuf[80];
                ATL_CRT_ERRORCHECK_SPRINTF(_sntprintf_s(szBuf, _countof(szBuf), _countof(szBuf) - 1, strTemp, printInfo.m_nCurPage));


                 // TODO: Replace following line with your operation report function 
                theApp.SetProgressInfo(szBuf, printInfo.m_nCurPage - nStartPage);

                // set up drawing rect to entire page (in logical coordinates)
                printInfo.m_rectDraw.SetRect(0, 0,
                dcPrint.GetDeviceCaps(HORZRES),
                dcPrint.GetDeviceCaps(VERTRES));
                dcPrint.DPtoLP(&printInfo.m_rectDraw);

                // attempt to start the current page
                if (dcPrint.StartPage() < 0)
                {
                    bError = TRUE;
                    break;
                }

                // must call OnPrepareDC on newer versions of Windows because
                // StartPage now resets the device attributes.
                pProvider->OnPrepareDC(&dcPrint, &printInfo);

                ASSERT(printInfo.m_bContinuePrinting);

                // page successfully started, so now render the page
                pProvider->OnPrint(&dcPrint, &printInfo);
                if ((nStep > 0) && // pages are printed in ascending order
                    (nEndPage > printInfo.GetMaxPage() + nStep)) // out off pages
                {
                    // OnPrint may have set the last page
                    // because the end of the document was reached.
                    // The loop must not continue with the next iteration.
                    nEndPage = printInfo.GetMaxPage() + nStep;
                }

                // If the user restarts the job when it's spooling, all
                // subsequent calls to EndPage returns < 0. The first time
                // GetLastError returns ERROR_PRINT_CANCELLED
                if (dcPrint.EndPage() < 0 && (GetLastError() != ERROR_SUCCESS))
                {
                    HANDLE hPrinter;
                    if (!OpenPrinter(LPTSTR(printInfo.m_pPD->GetDeviceName().GetBuffer()), &hPrinter, NULL))
                    {
                        bError = TRUE;
                        break;
                    }

                    DWORD cBytesNeeded;
                    if (!GetJob(hPrinter, printInfo.m_nJobNumber, 1, NULL, 0, &cBytesNeeded))
                    {
                        if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
                        {
                            bError = TRUE;
                            break;
                        }
                    }

                JOB_INFO_1 *pJobInfo;
                if ((pJobInfo = (JOB_INFO_1 *)malloc(cBytesNeeded)) == NULL)
                {
                    bError = TRUE;
                    break;
                }

                DWORD cBytesUsed;

                BOOL bRet = GetJob(hPrinter, printInfo.m_nJobNumber, 1, LPBYTE(pJobInfo), cBytesNeeded, &cBytesUsed);

                DWORD dwJobStatus = pJobInfo->Status;

                free(pJobInfo);
                pJobInfo = NULL;

                // if job status is restart, just continue
                if (!bRet || !(dwJobStatus & JOB_STATUS_RESTART))
                {
                    bError = TRUE;
                    break;
                }
            }

            if (!_AbortProc(dcPrint.m_hDC, 0))
            {
                bError = TRUE;
                break;
            }
        }

         // TODO: Replace following line with your operation finish function 

        theApp.OperationFinished();
    }

    // cleanup document printing process
    if (!printInfo.m_bDocObject)
    {
        if (!bError)
            dcPrint.EndDoc();
        else
            dcPrint.AbortDoc();
    }

    hwndTemp->EnableWindow(); // enable main window

    pProvider->OnEndPrinting(&dcPrint, &printInfo); // clean up after printing

    dcPrint.Detach(); // will be cleaned up by CPrintInfo destructor*/
    }
}

Note: In above code some strings use from string table. Please add following strings with the proper id to string table.

C++
IDS_FAIL_INIT_PRINT            "Could not initalize printer."
IDS_FAILED_TO_START_PRINT      "Could not start print job."
IDS_PRINTDEFAULTEXT            "prn"
IDS_PRINTDEFAULT               "Output.prn"
IDS_PRINTFILTER                "Printer Files (*.prn)|*.prn|All Files (*.*)|*.*||"
IDS_PRINTCAPTION               "Print to File"
IDS_PREVIEWPAGEDESC            "Page %u\nPages %u-%u\n"
IDS_PRINTPAGENUM               "Page %u"

Example of use

In the mainframe code add a File->Print command handler like this. CxFrame is a MDI child frame window class. You Replace your own code. GetPrintContentProvider() method return an implemention of CPrintContentBase class.



 

void CMainFrame::OnFilePrint()
{
       CxFrame* pActiveFrame = (CxFrame*)MDIGetActive();
       if (pActiveFrame == NULL)
           return;

       CPrintContentBase* pProvider = pActiveFrame->GetPrintContentProvider();
       if (pProvider == NULL)
           return;

       DoPrint(pProvider);
}

An Implemention of CPrintContentBase class:

#include "print.h"

class CSamplePrintProvider : public CPrintContentBase
{
public:
	CSamplerPintProvider()
    {
    }

	virtual ~CSamplePrintProvider()
    {
    }

	virtual CString GetTitle() const {
		return _T("Sample Title");
	}

	virtual void OnPrepareDC(CDC* pDC, CPrintInfo* pInfo = NULL)
	{
		pDC->SetBkMode(TRANSPARENT);
		pInfo->m_bContinuePrinting = TRUE;
	}

	virtual BOOL OnPreparePrinting(CPrintInfo* pInfo)
	{
		pInfo->SetMinPage(1);
		pInfo->SetMaxPage(1);
		pInfo->m_pPD->m_pd.Flags |= PD_NOPAGENUMS;

		return DoPreparePrinting(pInfo);
	}

	virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo)
	{
	}

	virtual void OnPrint(CDC* pDC, CPrintInfo* pInfo)
	{
		pDC->Rectangle(pInfo->m_rectDraw);
	}

	virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo)
	{
	}

};

Thanks

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)