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)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, OnFilePrintPreview)
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)))
{
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())
{
SuppressThisMessage();
CPrintPreviewState* pState = new CPrintPreviewState;
CView *pView = static_cast(CView*)(m_pPlugInFor);
if (!pView->DoPrintPreview(IDD_PREVIEW, pView,
RUNTIME_CLASS(CMultiPagePreviewView), pState))
{
TRACE0("Error: DoPrintPreview failed.\n");
AfxMessageBox(AFX_IDP_COMMAND_FAILURE);
delete pState; 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:
Collapse
std::vector<bool> m_bSuppressThisMessage;
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);
}
bool CPlugInMap::IsSuppressed()
{
bool bSuppressed = m_bSuppressThisMessage[m_bSuppressThisMessage.size() - 1];
m_bSuppressThisMessage.pop_back();
return bSuppressed;
}
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
BOOL CPlugInApp::ProcessWindowMessageMaps(
CPIState& state, 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; }
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:
Collapse
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;
CMultiPagePreviewView::CMultiPagePreviewView()
{
m_pPageInfo = m_pageInfoArray2;
m_Across = 2; m_Down = 1; m_nPages = 2;
m_pMaps = NULL;
m_MapCount = 0;
CPlugInApp *pApp = static_cast<CPlugInApp*>(AfxGetApp());
m_pMaps = pApp->GetMessageMaps(this, m_MapCount);
}
CMultiPagePreviewView::~CMultiPagePreviewView()
{
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)
{
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;
ret_pre = pApp->ProcessCommandMessageMaps(
stateCopy,
true,
&m_bSuppressThisMessage,
m_pMaps,
m_MapCount,
nID,
nCode,
pExtra,
pHandlerInfo);
if (!m_bSuppressThisMessage)
{
ret_app = CPreviewView::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}
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;
if (message == WM_COMMAND)
{
if (OnCommand(wParam, lParam))
{
lResult = 1;
if (pResult != NULL)
*pResult = lResult;
return TRUE;
}
return FALSE;
}
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;
}
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;
ret_pre = pApp->ProcessWindowMessageMaps(
stateCopy,
true,
&m_bSuppressThisMessage,
m_pMaps,
m_MapCount,
message,
wParam,
lParam,
pResult);
if (!m_bSuppressThisMessage)
{
ret_app = CPreviewView::OnWndMsg(message, wParam, lParam, pResult);
}
if (!stateCopy.IsDestroyed())
{
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:
Collapse
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 :
{
LPDEVMODE pDevMode = (LPDEVMODE)::GlobalLock(hDevMode);
pDevMode->dmOrientation = DMORIENT_PORTRAIT;
::GlobalUnlock(hDevMode);
}
break;
case DMORIENT_LANDSCAPE :
{
LPDEVMODE pDevMode = (LPDEVMODE)::GlobalLock(hDevMode);
pDevMode->dmOrientation = DMORIENT_LANDSCAPE;
::GlobalUnlock(hDevMode);
}
break;
default :
ASSERT(FALSE); 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 :
{
LPDEVMODE pDevMode = (LPDEVMODE)::GlobalLock(pd.hDevMode);
pDevMode->dmOrientation = DMORIENT_PORTRAIT;
::GlobalUnlock(pd.hDevMode);
}
break;
case DMORIENT_LANDSCAPE :
{
LPDEVMODE pDevMode = (LPDEVMODE)::GlobalLock(pd.hDevMode);
pDevMode->dmOrientation = DMORIENT_LANDSCAPE;
::GlobalUnlock(pd.hDevMode);
}
break;
default :
ASSERT(FALSE); 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.