Introduction
When I finished my last
project that required some
research into
ActiveX
control
printing, I
thought it would be a good topic
to create a demo program.
This project demonstrates
several different ways to
print an
ActiveX
control. I've
tried to show some of the most
common alternatives, and in
turn, describe some of the
problems that you might
encounter when
printing.
This is by no means an
exhaustive coverage of
printing.
Background
This article assumes that you
have basic familiarity with
ActiveX
controls in
C++.
In addition, it is assumed that
you understand how to use
QueryInterface
on a COM
object.
The demo program was created
as a simple "Single document"
MFC application using the
AppWizard. CFormView
was selected to implement the
view since it provided a place
to put dialog
controls. I decided to
use this approach because it
simplifies the details of
printing, and
the reader can focus on the MFC
OnPrint
function and the functions that
it calls.
To show how a "standard"
ActiveX
control
operates, the demo app uses the
MS Chart
control and the MS
Calendar
control. These
controls show all of the
basic concepts and they appear
to ship with Windows XP. The
picture
control is thrown in to
show one alternative (non COM)
method of
printing (WM_PRINT
).
The picture
control is a standard
Win32
control (not
ActiveX).
Printing
Alternatives
There are several ways to
print an
ActiveX
control. This
article demonstrates the
following methods:
- The
IViewObject
interface.
- The
IDataObject
interface.
- Using
WM_PRINT
messages.
Each of these methods has its
advantages and disadvantages. In
fact, not all
ActiveX
controls will
support all of these methods,
and even when they do, the
results might be less than
ideal.
The sections that follow
describe how to use each method.
The demo program can be used to
play with the different
alternatives and see how the
ActiveX
controls react
to the different methods.
The IViewObject Interface
(Screen or
Printer Device Context)
Many
ActiveX
controls
support this interface. This
interface allows an object to
place a representation of itself
onto a device context. The
IViewObject::Draw
method
is used for this purpose.
Since the caller is
responsible for setting up the
device context to draw into, the
caller has several choices. The
printer device context
can be used directly, or an
off-screen memory device context
can be used. Internet Explorer
4.0 uses this method to
print, passing the device
context of the
printer.
During my own testing with a
third party
control, I tried to use
the IViewObject::Draw
method with the
printer device context
directly. My goal was to get a
very high resolution
printout of the
control with a minimum of
work. Unfortunately, after
several tries, I could not get
the
control to
print itself correctly
using this method. After
examining some of the underlying
source
code for the
control, I found that it
was making some assumptions
about the size (in pixels) of
the drawing area. As a result,
the
control did not
print out correctly to
the
printer which had a much
higher resolution.
The alternative to
printing
directly into the
printer device context
was to create an off-screen
memory area that I passed into
the IViewObject::Draw
method. I sized this memory area
(a device dependent bitmap) to
match the size of the
control so that the
drawing would behave exactly the
same as on screen. This resulted
in a reasonably good
printout of the
control, although you can
see some problems when the image
is scaled up.
Printing to
the off-screen memory is very
similar to doing a "screen
capture" of the
control. However, you
don't have to worry about
whether the
control is hidden or
obscured, etc.
The demo program will allow
you to test the
IViewObject::Draw
method
for these two alternatives. The
first two
print options demonstrate
how this is done. In both cases,
the code queries the
control for the
IViewObject
interface and
then calls the Draw method with
the appropriate device context
as shown below.
Collapse |
Copy Code
LPUNKNOWN pUnk = pWnd->GetControlUnknown();
if (!pUnk)
{
AfxMessageBox(_T("Not an ActiveX Control"));
return;
}
IViewObjectPtr spViewObj;
hRes = pUnk->QueryInterface(__uuidof(spViewObj),
(void **) &spViewObj);
if (FAILED(hRes)) _com_issue_error(hRes);
hRes = spViewObj->Draw(DVASPECT_CONTENT, -1, NULL, NULL,
NULL, pDC->m_hDC, &rectPrn, NULL, NULL, 0);
if (FAILED(hRes)) _com_issue_error(hRes);
Note: if you attempt use the
IViewObject
method
on the picture
control, the GetControlUnknown
call will fail because the
picture
control is not an
ActiveX
control.
One additional note. When I
was attempting to transfer the
off-screen memory onto the
printer device context, I
found that on some computers/printers
the
printout would not show
up. It would show up blank and
there did not appear to be any
errors. After doing some
research on this, I found the
MSDN article Q195830 - "INFO:
Blitting Between DCs for
Different Devices Is
Unsupported". This explained why
my direct use of
StretchBlt
from the
memory bitmap to the
printer did not work. As
a result, I changed the routine
so that it converted the device
dependent bitmap to a device
independent bitmap and then
transferred the results using
StretchDIBits
instead of StretchBlt
.
This can be seen in function
PrintControlUsingScreen
.
The IViewObject Interface
(Metafile)
This method uses the same
interface as described in the
previous section, however in
this case a metafile device
context is passed in. The
control then draws into
the metafile and the client can
use the metafile to draw into
the
printer device context.
This is the method used by
Visual Basic 6.0. An excerpt
from the code is shown below.
Collapse |
Copy Code
CMetaFileDC dcMeta;
if (!dcMeta.Create())
_com_issue_error(HRESULT_FROM_WIN32(::GetLastError()));
IViewObjectPtr spViewObj;
hRes = pUnk->QueryInterface(__uuidof(spViewObj),
(void **) &spViewObj);
if (FAILED(hRes)) _com_issue_error(hRes);
hRes = spViewObj->Draw(DVASPECT_CONTENT, -1, NULL, NULL,
NULL, dcMeta, &rectPrn, NULL, NULL, 0);
if (FAILED(hRes)) _com_issue_error(hRes);
HMETAFILE hMeta = dcMeta.Close();
if (!hMeta)
_com_issue_error(HRESULT_FROM_WIN32(::GetLastError()));
pDC->PlayMetaFile(hMeta);
DeleteMetaFile(hMeta);
The IDataObject Interface
(Metafile)
This interface is also
supported by many
ActiveX
controls. The
interface is used for data
transfer. The
IDataObject::GetData
method is used for this purpose.
The GetData
method
is called with a value of
TYMED_MFPICT
in the
FORMATETC
parameter. This
is the way Microsoft Word 97
works.
The code excerpt below shows
the basic technique. In this
case, the client queries for the
IDataObject
interface, and then calls the
GetData
routine to
retrieve a metafile
representation of the
control. The
control itself is
responsible for allocating the
metafile, so the client frees it
with the ReleseStgMedium
call.
Collapse |
Copy Code
IDataObjectPtr spDataObj;
hRes = pUnk->QueryInterface(__uuidof(spDataObj),
(void **) &spDataObj);
if (FAILED(hRes)) _com_issue_error(hRes);
FORMATETC Formatetc;
Formatetc.cfFormat = CF_METAFILEPICT;
Formatetc.ptd = NULL;
Formatetc.dwAspect = DVASPECT_CONTENT;
Formatetc.lindex = -1;
Formatetc.tymed = TYMED_MFPICT;
STGMEDIUM Medium = { 0 };
hRes = spDataObj->GetData(&Formatetc, &Medium);
if (FAILED(hRes)) _com_issue_error(hRes);
if (Medium.tymed & TYMED_MFPICT)
{
METAFILEPICT *pMetaPict = (METAFILEPICT *)
GlobalLock(Medium.hMetaFilePict);
pDC->SetMapMode(pMetaPict->mm);
pDC->SetViewportOrg(rectPrn.left, rectPrn.top);
pDC->SetViewportExt(rectPrn.right - rectPrn.left + 1,
rectPrn.bottom - rectPrn.top + 1);
pDC->PlayMetaFile(pMetaPict->hMF);
GlobalUnlock(Medium.hMetaFilePict);
ReleaseStgMedium(&Medium);
}
Not all
controls support the
IDataObject
interface. In fact, the MS Chart
control does not support
it. If you attempt to
print the chart
control using this
method, you will get a "No such
interface supported" error.
Obviously, the picture
control does not support
this method. The picture
control is not an
ActiveX
control.
The WM_PRINT
Message
For some types of
controls, you can use the
WM_PRINT
or WM_PRINTCLIENT
message to pass a device context
for drawing. Unfortunately,
during my testing, I did not
find any
ActiveX
controls that handled
this message very well. For a
standard Win32
control (like the Picture
control), this worked
very well. I included it in my
demo application just to be
thorough. Using this method, I
did not get acceptable results
from either the MS Chart
control or the MS
Calendar
control. For the Chart
control, the
printout did not show
anything. For the Calendar
control, it only rendered
a portion of the combo boxes.
The code used to test this
method is shown below.
Collapse |
Copy Code
LRESULT lRes = pWnd->SendMessage(WM_PRINT, (WPARAM) pDC->GetSafeHdc(),
PRF_CLIENT | PRF_CHILDREN | PRF_OWNED);
Examining the code
The demo program can be used
to test each of the
print
techniques. All of the code of
interest is in the CPrintControlView
class. Specifically, there is
one function for each of the
print options.
There are a couple of other
support routines included; to
print a title and output
an error message. The content of
these routines should be self
explanatory. All of the other
code in the application was
generated using the AppWizard.
Conclusion
My goal was to show a few
different methods of
printing
ActiveX
controls. My
intent was not to show every
possible way to
print, but to present a
few concepts that may be helpful
to others.
Hopefully these simple code
snippets will be of use to
others facing the same problems
that I did.