Click here to Skip to main content
Click here to Skip to main content

MFC Extension library : Enhanced print preview plug-in

, 24 May 2004 CPOL
Rate this:
Please Sign up or sign in to vote.
MFC Extension library : Enhanced print preview plug-in

Enhanced print preview running in example application

Table of content

Introduction

This article is a follow on to the article An MFC extension library to enable DLL plug-in technology for your application using MESSAGE_MAPs. If you are not familiar with the library itself I would recommend that you look at the main article first.

This plug-in demonstrates a replacement of the standard MFC print preview window with an enhanced version that supports:

  • Multipage display : 1, 2, 3, 4, 6 or 9 pages at a time
  • Switching the page layout between Portrait and Landscape
  • Print setup available in preview mode
  • The mouse wheel will work for scrolling pages
  • Once again I will review how I went about designing the plug-in and implementing its features. Along the way I will also cover problems encountered and how they were solved.

    When I look at the plug-ins I can write, it seems to me that I will be able to take most of my existing article series and convert each as I go. It has been a good starting point for upgrading this library. If users of the class wish to see a specific plug-in created, they should visit my blog page and post a suggesstion there.

    Other articles in the series

    Developing the plug-in

    As a starting point, I created an MFC extension DLL as described in the main article. From there I got hold of the source from my MultiPage PrintPreview enhancements for MFC Doc/View applications article and started combining the two into a working project.

    The first step in this procedure was to replace the existing CView::OnFilePrintPreview() function with our own. So, I created a plug-in to handle this:

    I added a message map entry to process the existing ID_FILE_PRINT_PREVIEW message.

    BEGIN_MESSAGE_MAP(CEnhancedPrintPreview, CPlugInMap)
        //{{AFX_MSG_MAP(CEnhancedPrintPreview)
        ON_COMMAND(ID_FILE_PRINT_PREVIEW, OnFilePrintPreview)
        //}}AFX_MSG_MAP
    END_MESSAGE_MAP()
    

    With the V1.2 upgrade to the library, I was able to return true for all CView derived classes so that we can replace their existing print preview functionality.

    bool CEnhancedPrintPreview::IsPlugInFor(CRuntimeClass *pClass)
    {
        if (pClass-&gtIsDerivedFrom(RUNTIME_CLASS(CView)))
        {
            // we are only a plug-in for any view class
            // as the view classes support the preview option
            return true;
        }
        return false;
    }
    

    For the Pre call for the message we suppress the standard implementation of it using SuppressThisMessage(), as we do not want multiple preview windows being displayed.

    void CEnhancedPrintPreview::OnFilePrintPreview()
    {
        if (IsPreCall())
        {
            // were replacing the standard MFC print preview and replacing it with
            // our enhanced version.
    
            SuppressThisMessage();
            // replace the default print preview with ours!
    
            // In derived classes, implement special window handling here
            // Be sure to Unhook Frame Window close if hooked.
            
            // must not create this on the frame.  Must outlive this function
            CPrintPreviewState* pState = new CPrintPreviewState;
            
            // DoPrintPreview's return value does not necessarily indicate that
            // Print preview succeeded or failed, but rather what actions are necessary
            // at this point.  If DoPrintPreview returns TRUE, it means that
            // OnEndPrintPreview will be (or has already been) called and the
            // pState structure will be/has been deleted.
            // If DoPrintPreview returns FALSE, it means that OnEndPrintPreview
            // WILL NOT be called and that cleanup, including deleting pState
            // must be done here.
            CView *pView = static_cast&ltCView*&gt(m_pPlugInFor);
            
            if (!pView-&gtDoPrintPreview(IDD_PREVIEW, pView, 
               RUNTIME_CLASS(CMultiPagePreviewView), pState))
            {
                // In derived classes, reverse special window handling here for
                // Preview failure case
                
                TRACE0("Error: DoPrintPreview failed.\n");
                AfxMessageBox(AFX_IDP_COMMAND_FAILURE);
                delete pState;      // preview failed to initialize, delete State now
                pState = NULL;
            }
        }
    }
    

    Well, once I had this and the standard code from the enhanced print preview code merged sufficiently to get this far, I ran some tests to see how well it worked.

    And it failed very badly. Frown | :( In fact I could not immediately see why it was failing, so I placed judicious TRACE statements throughout the code. One of the symptoms of the failure was I was getting to see the standard MFC print preview dialog bar:

    The standard MFC print preview toolbar, which we are trying to replace

    Now during the print preview initialisation, this standard dialog bar is being destroyed and replaced with our own, which enabled the mouse wheel messages, so why is it being shown? This was the first clue to my problem. It seems that even though I was trying to suppress the ID_FILE_PRINT_PREVIEW message in the standard MFC code, I was failing to, and two preview views were being created, mine and MFCs. This was the main source of the problem. So how do I fix it?

    Message suppression in the library

    In the MFC extension library that handles the plug-ins, each plug-in had a bool m_bSuppressThisMessage variable which when we needed to suppress the message was being set to true, so I checked the state of the flag in the ProcessCommandMessageMaps() function after my plug-in message had just been processed.

    And it was false!

    After some thought it occurred to me that when we create the preview view and we go about displaying it, other messages may be being processed by the library in the background, probably due to a message pump being done due to a SendMessage() call, and as we only have a single flag for all passes through the library, any additional messages that do get processed before we return from our plug-in function will reset the suppressed message flag. This is not what we want to happen.

    Now that we have identified the flaw in the library we need to fix it.

    Fixing the problem

    Well the problem we have here is in the library, and not our plug-in, so I went back to the library source and decided that we needed a stack of suppression flags, one gets added for every message being processed, and is removed when that message gets completed. This led to some changes in the CPlugInMap class as follows:

        // changes from a single variable into a vector
        std::vector&ltbool&gt    m_bSuppressThisMessage;
    
    // pre/post calls push a new entry onto the end of the vector
    void CPlugInMap::SetPreCall()
    {
        m_bPreCall = true;
        m_bPostCall = false;
        m_bSuppressThisMessage.push_back(false);
    }
    
    void CPlugInMap::SetPostCall()
    {
        m_bPreCall = false;
        m_bPostCall = true;
        m_bSuppressThisMessage.push_back(false);
    }
    
    // checking to see whether the message was suppressed pops an entry of the vector
    bool CPlugInMap::IsSuppressed()
    {
        bool bSuppressed = m_bSuppressThisMessage[m_bSuppressThisMessage.size() - 1];
        m_bSuppressThisMessage.pop_back();
        return bSuppressed;
    }
    
    // an additional query function can return the supressed state without
    // removing the entry from the queue
    bool CPlugInMap::GetSuppressedState() const
    {
        bool bSuppressed = m_bSuppressThisMessage[m_bSuppressThisMessage.size() - 1];
        return bSuppressed;
    }
    

    Well this also made me think about a problem that was originally experienced by early users of the plug-in library, that is that when WM_NCDESTROY messages were processed, some objects called delete this on themselves, causing Post message handler to fail with a GPF when trying to access the deleted map objects. I did not want to re-introduce the same problem, so had to also change the ProcessCommandMessageMaps() and ProcessWindowMessageMaps() functions to check the CPIState object related to the plug-in object.

    // one of the updated functions
    BOOL CPlugInApp::ProcessWindowMessageMaps(
        CPIState& state,    // new parameter added to the function
        bool pre, 
        bool* pbSuppress, 
        CPlugInMap** pMaps, 
        int count, 
        UINT message, 
        WPARAM wParam, 
        LPARAM lParam, 
        LRESULT* pResult)
    {
        BOOL    ret = FALSE;
        BOOL    local_ret = FALSE;
    
        *pbSuppress = false;
        
        for (int i = 0; i &lt count; ++i)
        {
            if (pre)
            {
                pMaps[i]-&gtSetPreCall();
            }
            else
            {
                pMaps[i]-&gtSetPostCall();
            }
            local_ret = CallWindowMessageMap(pMaps[i], 
                 message, wParam, lParam, pResult);
            if (local_ret)
            {
                ret = TRUE;   // show that it was handled somewhere
            }
            // works due to the order of evaluation
            if (!state.IsDestroyed() && pMaps[i]-&gtIsSuppressed())
            {
                *pbSuppress = true;
            }
        }
        return ret;
    }
    

    Once all this had been done, I rebuilt the project and the problem had been fixed.

    This new version of the plug-in library is now V1.3 - It seems every time I create a new plug-in, I discover another flaw in it. Hopefully there will be less and less of these in the future.

    Making the preview view plug-in enabled

    I also wanted the preview view to make use of any other plug-ins that may enhance its appearance. One such is the owner-drawn menu plug-in. So I added versions of the OnCmdMsg and OnWndMsg functions along with the additional support varaibles:

    // header file
        virtual BOOL OnCmdMsg(UINT nID, int nCode, void* pExtra, 
            AFX_CMDHANDLERINFO* pHandlerInfo);
        virtual BOOL OnWndMsg(UINT message, WPARAM wParam, 
             LPARAM lParam, LRESULT* pResult);
    private:
        CPlugInMap**    m_pMaps;
        int                m_MapCount;
        bool            m_bSuppressThisMessage;
        CPIState        m_state;
    
    // C++ file
    CMultiPagePreviewView::CMultiPagePreviewView()
    {
        // replace the PAGE_INFO array with our one to make sure its large enough
        m_pPageInfo = m_pageInfoArray2;
        m_Across = 2;           // default number of pages across the screen
        m_Down = 1;            // default number of pages down the screen
        m_nPages = 2;
        
        // NOTE : If you change m_nPages outside of this 
        // code you will also need to modify
        // the m_Across and m_Down member vars to get the pages to show correctly
        m_pMaps = NULL;
        m_MapCount = 0;
    
        CPlugInApp *pApp = static_cast&ltCPlugInApp*&gt(AfxGetApp());
        // get our pointers to any plug in maps
        m_pMaps = pApp-&gtGetMessageMaps(this, m_MapCount);
    }
    
    CMultiPagePreviewView::~CMultiPagePreviewView()
    {
        // release message map pointers
        for (int i = 0; i < m_MapCount; ++i)
        {
            delete m_pMaps[i];
            m_pMaps[i] = NULL;
        }
        delete []m_pMaps;
        m_pMaps = NULL;
        m_state.SetDestroyed();
    }
    
    BOOL CMultiPagePreviewView::OnCmdMsg(
            UINT nID, 
            int nCode, 
            void* pExtra, 
            AFX_CMDHANDLERINFO* pHandlerInfo) 
    {
        // check to see whether any of the plug-in DLL's
        // want to intercept this message and process it before the application class
        CPlugInApp *pApp = static_cast&ltCPlugInApp*&gt(AfxGetApp());
        CPIState    stateCopy(m_state);
    
        ASSERT(pApp);
        BOOL ret_pre = FALSE;
        BOOL ret_app = FALSE;
        BOOL ret_post = FALSE;
        m_bSuppressThisMessage = false;
        
        // allow DLL's to process this message before the exe
        ret_pre = pApp-&gtProcessCommandMessageMaps(
                stateCopy, 
                true, 
                &m_bSuppressThisMessage, 
                m_pMaps, 
                m_MapCount, 
                nID, 
                nCode, 
                pExtra, 
                pHandlerInfo);
        if (!m_bSuppressThisMessage)
            {
            // allow the exe to process the message is it hasn't been suppressed by a DLL
            ret_app = CPreviewView::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
            }
        // allow DLL to process the message after the exe
        // note that DLLs get to do this whether the message has been supressed or not
        if (!stateCopy.IsDestroyed())
        {
            ret_post = pApp-&gtProcessCommandMessageMaps(
                    stateCopy, 
                    false, 
                    &m_bSuppressThisMessage, 
                    m_pMaps, 
                    m_MapCount, 
                    nID, 
                    nCode, 
                    pExtra, 
                    pHandlerInfo);
        }
    
        if (ret_pre || ret_app || ret_post)
            return  TRUE;
        return FALSE;
    }
    
    BOOL CMultiPagePreviewView::OnWndMsg(
            UINT message, 
            WPARAM wParam, 
            LPARAM lParam, 
            LRESULT* pResult)
    {
        LRESULT    lResult;
        // special case for commands
        if (message == WM_COMMAND)
            {
            if (OnCommand(wParam, lParam))
                {
                lResult = 1;
                if (pResult != NULL)
                    *pResult = lResult;
                return TRUE;
                }
            return FALSE;
            }
    
        // special case for notifies
        if (message == WM_NOTIFY)
            {
            NMHDR* pNMHDR = (NMHDR*)lParam;
            if (pNMHDR-&gthwndFrom != NULL && OnNotify(wParam, lParam, &lResult))
                {
                if (pResult != NULL)
                    *pResult = lResult;
                return TRUE;
                }
            return FALSE;
            }
    
        // check to see whether any of the plug-in DLL's
        // want to intercept this message and process it before the application class
        CPlugInApp *pApp = static_cast&ltCPlugInApp*&gt(AfxGetApp());
        ASSERT(pApp);
        CPIState    stateCopy(m_state);
        BOOL ret_pre = FALSE;
        BOOL ret_app = FALSE;
        BOOL ret_post = FALSE;
        m_bSuppressThisMessage = false;
        // allow DLL's to process this message before the exe
        ret_pre = pApp-&gtProcessWindowMessageMaps(
                stateCopy, 
                true, 
                &m_bSuppressThisMessage, 
                m_pMaps, 
                m_MapCount, 
                message, 
                wParam, 
                lParam, 
                pResult);
        if (!m_bSuppressThisMessage)
            {
            // allow the exe to process the message is it hasn't been suppressed by a DLL
            ret_app = CPreviewView::OnWndMsg(message, wParam, lParam, pResult);
            }
        if (!stateCopy.IsDestroyed())
        {
            // allow DLL to process the message after the exe
            // note that DLLs get to do this whether the message has been supressed or not
            ret_pre = pApp-&gtProcessWindowMessageMaps(
                    stateCopy, 
                    false, 
                    &m_bSuppressThisMessage, 
                    m_pMaps, 
                    m_MapCount, 
                    message, 
                    wParam, 
                    lParam, 
                    pResult);
        }
    
        if (ret_pre || ret_app || ret_post)
            return  TRUE;
        return FALSE;
    }
    

    This made the preview view plug-in enabled, so it makes use of the owner-drawn menu plug-in for the popup menu used to select how many pages the user wishes to preview at a time. A toolbar resource, which is loaded by the owner-drawn menu plug-in was added with the correct images.

    So how did it look?

    At this point I had all the code that changes the print orientation and printer disabled, so what we had was this:

    I Now needed to enable the printer selection and the paper orientation features.

    Paper orientation and printer selection

    In the original aritcle, I had a class which enumerates all the printers installed on the local system. Along with this class, the user had to add some functions to their CWinApp derived class to allow the page orientation and the printer to be changed. But we cannot do this in our plug-in. We have to find a different method of doing this because we cannot modify the CWinApp or CPlugInApp derived classes.

    Changing the paper orientation

    If you look in the original article, you will see that the paper orientation change is done like this:

    // code section
    class CMultiPagePrintPreviewApp : public CWinApp
    {
        CEnumPrinters        m_Printers;
    
        void        SetPrintOrientation(int mode);
    }
    
    void CMultiPagePrintPreviewApp::SetPrintOrientation(int mode)
    {
        m_Printers.SetPrintOrientation(m_hDevMode, mode);
    }
    
    bool CEnumPrinters::SetPrintOrientation(HANDLE &hDevMode, int mode)
    {
        if (hDevMode == INVALID_HANDLE_VALUE)
            return false;
    
        switch (mode)
            {
            case DMORIENT_PORTRAIT :
                    {
                    // portrait mode
                    LPDEVMODE pDevMode = (LPDEVMODE)::GlobalLock(hDevMode);
                    // set orientation to portrait
                    pDevMode->dmOrientation = DMORIENT_PORTRAIT;
                    ::GlobalUnlock(hDevMode);
                    }
                    break;
            case DMORIENT_LANDSCAPE :
                    {
                    // landscape mode
                    LPDEVMODE pDevMode = (LPDEVMODE)::GlobalLock(hDevMode);
                    // set orientation to landscape
                    pDevMode->dmOrientation = DMORIENT_LANDSCAPE;
                    ::GlobalUnlock(hDevMode);
                    }
                    break;
            default :    
                    ASSERT(FALSE);        // invalid parameter
                    return false;
            }
        return true;
    }
    

    In this version we had to call the relevant function of the CWinApp derived class as we needed access to the CWinApp::m_hDevMode global handle, which manages the currently selected printer. We need to find a different way of doing this.

    After looking through the MSDN, I discovered that calling the function CWinApp::GetPrinterDeviceDefaults() returns the required handles in a PRINTDLG structure. So we can extract the function from the CEnumPrinters class and rewrite it as follows:

    bool CMultiPagePreviewView::SetPrintOrientation(int mode) const
    {
        PRINTDLG    pd;
    
        pd.lStructSize = (DWORD)sizeof(PRINTDLG);
        BOOL bRet = AfxGetApp()-&gtGetPrinterDeviceDefaults(&pd);
    
        if (bRet)
        {
            switch (mode)
            {
            case DMORIENT_PORTRAIT :
                {
                // portrait mode
                LPDEVMODE pDevMode = (LPDEVMODE)::GlobalLock(pd.hDevMode);
                // set orientation to portrait
                pDevMode-&gtdmOrientation = DMORIENT_PORTRAIT;
                ::GlobalUnlock(pd.hDevMode);
                }
                break;
            case DMORIENT_LANDSCAPE :
                {
                // landscape mode
                LPDEVMODE pDevMode = (LPDEVMODE)::GlobalLock(pd.hDevMode);
                // set orientation to landscape
                pDevMode-&gtdmOrientation = DMORIENT_LANDSCAPE;
                ::GlobalUnlock(pd.hDevMode);
                }
                break;
            default :    
                ASSERT(FALSE);        // invalid parameter
                return false;
            }
            return true;
        }
        return false;
    }
    

    So we can hook up the paper orientation, but how do we switch printers?

    Switching printers

    Well again we need to avoid using functions added to the CWinApp derived class so we need to make use of existing MFC code. The most obvious of which was CWinApp::SelectPrinter(), but after playing with this for a while, I could still only get GPFs!

    So I went back to my original article and took a look at the comments added by users of the code, and found the following entry by asconaga:

    So, following his advice, I removed the printer combo box, and added a print setup button and mapped the relevant code. And hey presto, it works without any problems.

    Conclusion

    Another article in the series and another flaw in the library exposed and killed, hopefully the last of nay present.

    This plug-in demonstrates the replacement and enhancement of a standard MFC feature, again in a modular fashion, building on my previous article series to produce a single MFC upgrade path.

    I hope you have enjoyed reading (and using) this article.

    References

    Here are a list of related articles used in the making of this plug-in

    Version history

    • V1.0 21st May 2004 - Initial release

    License

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

    Share

    About the Author

    Roger Allen
    Software Developer (Senior) Sirius Analytical Instruments
    United Kingdom United Kingdom
    A research and development programmer working for a pharmaceutical instrument company for the past 17 years.
     
    I am one of those lucky people who enjoys his work and spends more time than he should either doing work or reseaching new stuff. I can also be found on playing DDO on the Cannith server (Send a tell to "Maetrim" who is my current main)
     
    I am also a keep fit fanatic, doing cross country running and am seriously into [url]http://www.ryushinkan.co.uk/[/url] Karate at this time of my life, training from 4-6 times a week and recently achieved my 1st Dan after 6 years.

    Comments and Discussions

     
    QuestionRe: PlugInLib.h Pinmembermla1545-Jan-07 6:21 
    AnswerRe: PlugInLib.h PinmemberJerry Evans5-Aug-07 16:00 
    GeneralRe: PlugInLib.h Pinmembermla15428-Jun-12 4:53 
    GeneralPrintSetup Memory leak PinmemberK.Hill17-May-06 3:30 
    GeneralFull Screen PinmemberCodeProde14-Oct-05 11:30 
    Hey,
     
    Do you happen to know whether it is easy to get a full screen printpreview (so, the preview covers the whole of the screen, like powerpoint in presentation mode)?
    Questionnay ? PinmemberIain Clarke25-May-04 7:33 
    AnswerRe: nay ? PinmemberRoger Allen25-May-04 7:43 

    General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

    Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

    | Advertise | Privacy | Terms of Use | Mobile
    Web03 | 2.8.141223.1 | Last Updated 25 May 2004
    Article Copyright 2004 by Roger Allen
    Everything else Copyright © CodeProject, 1999-2014
    Layout: fixed | fluid