Printing ActiveX Controls

Introduction

I have been using Code Project for several years now, and I've always felt that I should give back
something to help my fellow software developers. Therefore, 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.

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.

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.

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.

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.

History

01-May-2004 Original

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

Comments and Discussions

The error message I'm getting is "Debug Assertion Failed! Program: c:\PrintControl\PrintControl\Debug\PrintControl.exe File: f:\sp\vctools\vc7libs\ship\atlmfc\src\mfc\occcont.cpp Line:926
I did some digging, and it looks like the necessary .ocx files aren't registered. However, I downloaded mschart.ocx and mscal.ocx, put them in C:\windows\system32, and registered them, and the problem did not go away. Any idea how I can make this work?

Apparently you need mschrt20.ocx installed and registered, which no longer comes with Microsoft Office. You can download it at http://www.ocxdump.com/download-ocx-files_new.php/ocxfiles/M/mschrt20.ocx/6.00.81774/download.html. After downloading this and registering it, it worked. I figure I'm not the only one who runs into this problem, so I hope this helps.