YOU CAN CODE!

 

With The Case Of UCanCode.net  Release The Power OF  Visual C++ !   HomeProducts | PurchaseSupport | Downloads  
Download Evaluation
Pricing & Purchase?
E-XD++Visual C++/ MFC Products
Overview
Features Tour 
Electronic Form Solution
Visualization & HMI Solution
Power system HMI Solution
CAD Drawing and Printing Solution

Bar code labeling Solution
Workflow Solution

Coal industry HMI Solution
Instrumentation Gauge Solution

Report Printing Solution
Graphical modeling Solution
GIS mapping solution

Visio graphics solution
Industrial control SCADA &HMI Solution
BPM business process Solution

Industrial monitoring Solution
Flowchart and diagramming Solution
Organization Diagram Solution

Graphic editor Source Code
UML drawing editor Source Code
Map Diagramming Solution

Architectural Graphic Drawing Solution
Request Evaluation
Purchase
ActiveX COM Products
Overview
Download
Purchase
Technical Support
  General Q & A
Discussion Board
Contact Us

Links

Get Ready to Unleash the Power of UCanCode .NET


UCanCode Software focuses on general application software development. We provide complete solution for developers. No matter you want to develop a simple database workflow application, or an large flow/diagram based system, our product will provide a complete solution for you. Our product had been used by hundreds of top companies around the world!

"100% source code provided! Free you from not daring to use components because of unable to master the key technology of components!"


MFC Multiple Page Print preview Article and CPrintPreviewState, it with CPrintPreview and DoPrintPreview, LPDEVMODE and GetPrinterDeviceDefaults

 
 

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

    • An MFC extension library to enable DLL plug-in technology for your application using MESSAGE_MAPs - This is the main plug-in article, with an MDI Tab bar example
    • MFC extension library - A plugin to handle owner drawn menus - A plug-in article which adds owner drawn menu support.

    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 Multi Page Print Preview 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.

    Collapse
    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(CView*)(m_pPlugInFor);
            
            if (!pView->DoPrintPreview(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. :( 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:

    Collapse
        // 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.

    Collapse
    // 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:

    Collapse
    // 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:

    Collapse
    // 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:

    Collapse
    bool CMultiPagePreviewView::SetPrintOrientation(int mode) const
    {
        PRINTDLG    pd;
    
        pd.lStructSize = (DWORD)sizeof(PRINTDLG);
        BOOL bRet = AfxGetApp()->GetPrinterDeviceDefaults(&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, 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.

     

  •  

     

    Copyright ?1998-2022 UCanCode.Net Software , all rights reserved.
    Other product and company names herein may be the trademarks of their respective owners.

    Please direct your questions or comments to webmaster@ucancode.net