Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Printing ActiveX Controls

0.00/5 (No votes)
3 May 2004 1  
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:

  1. The IViewObject interface.
  2. The IDataObject interface.
  3. 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.


// get the IUnknown pointer for the control object 

// (note, don't release this one per MSDN)

LPUNKNOWN pUnk = pWnd->GetControlUnknown();
if (!pUnk)
{
    AfxMessageBox(_T("Not an ActiveX Control")); 
    return; 
}

// ... 


// query for the IViewObject interface for printing 

// (note, some don't support this, like lite controls)

IViewObjectPtr spViewObj; 
hRes = pUnk->QueryInterface(__uuidof(spViewObj), 
  (void **) &spViewObj);     
if (FAILED(hRes)) _com_issue_error(hRes); 

// draw the object into the printer device context

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.

// create a metafile to draw into 

CMetaFileDC dcMeta; 
if (!dcMeta.Create())
    _com_issue_error(HRESULT_FROM_WIN32(::GetLastError())); 

// query for the IViewObject interface for printing 

// (note, some don't support this, like lite controls)

IViewObjectPtr spViewObj; 
hRes = pUnk->QueryInterface(__uuidof(spViewObj), 
  (void **) &spViewObj);     
if (FAILED(hRes)) _com_issue_error(hRes); 

// draw the object into the metafile device context

hRes = spViewObj->Draw(DVASPECT_CONTENT, -1, NULL, NULL, 
  NULL, dcMeta, &rectPrn, NULL, NULL, 0); 
if (FAILED(hRes)) _com_issue_error(hRes); 

// get the completed meta file handle

HMETAFILE hMeta = dcMeta.Close(); 
if (!hMeta) 
    _com_issue_error(HRESULT_FROM_WIN32(::GetLastError())); 

// play the meta file into the printer device context

pDC->PlayMetaFile(hMeta); 

// free up the metafile memory 

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.


// query for the IDataObject interface for GetData 

// (some controls don't support this)

IDataObjectPtr spDataObj; 
hRes = pUnk->QueryInterface(__uuidof(spDataObj), 
  (void **) &spDataObj);     
if (FAILED(hRes)) _com_issue_error(hRes); 

// setup the structures for retrieving the results 

FORMATETC Formatetc;
Formatetc.cfFormat = CF_METAFILEPICT; 
Formatetc.ptd = NULL; 
Formatetc.dwAspect = DVASPECT_CONTENT; 
Formatetc.lindex = -1; 
Formatetc.tymed = TYMED_MFPICT; 
STGMEDIUM Medium = { 0 }; 

// draw the control into a metafile 

hRes = spDataObj->GetData(&Formatetc, &Medium); 
if (FAILED(hRes)) _com_issue_error(hRes); 

// the returned type should be a metafile since 

// that's what we requested

if (Medium.tymed & TYMED_MFPICT)
{
    // get the metafile picture pointer so we 

    // can get the metafile handle

    METAFILEPICT *pMetaPict = (METAFILEPICT *) 
        GlobalLock(Medium.hMetaFilePict); 

    // scale appropriately 

    pDC->SetMapMode(pMetaPict->mm); 
    pDC->SetViewportOrg(rectPrn.left, rectPrn.top);
    pDC->SetViewportExt(rectPrn.right - rectPrn.left + 1, 
        rectPrn.bottom - rectPrn.top + 1);

    // play the meta file into the printer device context

    pDC->PlayMetaFile(pMetaPict->hMF); 

    // unlock the metafile picture handle

    GlobalUnlock(Medium.hMetaFilePict); 

    // release the results 

    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.


// send the message to print the control 

// (could use pWnd->Print directly)

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.

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.

A list of licenses authors might use can be found here