MFC Extension library : Enhanced print preview plug-in






4.65/5 (9 votes)
MFC Extension library : Enhanced print preview plug-in
Table of content
- Introduction
- Other articles in the series
- Developing the plug-in
- Message suppression in the library
- Making the preview view plug-in enabled
- Paper orientation and printer selection
- Conclusion
- References
- Version history
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:
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 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->IsDerivedFrom(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<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:

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<bool> 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 < count; ++i) { if (pre) { pMaps[i]->SetPreCall(); } else { pMaps[i]->SetPostCall(); } 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]->IsSuppressed()) { *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<CPlugInApp*>(AfxGetApp()); // get our pointers to any plug in maps m_pMaps = pApp->GetMessageMaps(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<CPlugInApp*>(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->ProcessCommandMessageMaps( 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->ProcessCommandMessageMaps( 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->hwndFrom != 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<CPlugInApp*>(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->ProcessWindowMessageMaps( 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->ProcessWindowMessageMaps( 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()->GetPrinterDeviceDefaults(&pd); if (bRet) { switch (mode) { case DMORIENT_PORTRAIT : { // portrait mode LPDEVMODE pDevMode = (LPDEVMODE)::GlobalLock(pd.hDevMode); // set orientation to portrait pDevMode->dmOrientation = DMORIENT_PORTRAIT; ::GlobalUnlock(pd.hDevMode); } break; case DMORIENT_LANDSCAPE : { // landscape mode LPDEVMODE pDevMode = (LPDEVMODE)::GlobalLock(pd.hDevMode); // set orientation to landscape pDevMode->dmOrientation = 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
- An MFC extension library to enable DLL plug-in technology for your application using MESSAGE_MAPs
- MultiPage PrintPreview enhancements for MFC Doc/View applications which was based on:
- Robin J. Leatherbarrow - Enhanced PrintPreview
- Yasuhiko Yoshimura - An Advanced Preview within Doc/Vew architecture
Version history
- V1.0 21st May 2004 - Initial release