Click here to Skip to main content
Click here to Skip to main content

How to print an ActiveX MSChart control

By , 4 Aug 2002
 

Sample Image - printchart.jpg

Introduction

Just recently, I was given a task: "Create a report with a chart." Sounds simple, right? That was what I though too, at first. My first reaction was to use a 3rd party plug-in of some sort, such as Crystal Report. "Sorry, we don't have budget to use a third party item which requires royalty or additional fee." All righty then, how about exporting the data to an excel template? "Sorry, exporting a 10x30000 spread sheet on a Pentium 2-300 would take about an hour before printing." How about I code the report onto the printer's device context? "Great! We'll need that in 3 days. And make sure it has some flexibility for us do some custom configuration and filtering"

*Gasp* Okay, this doesn't leave me with too many choices, so I looked into the ActiveX component, MsChart. I've seen postings of people talking about MsChart in VC++ forums, but they were mostly questions without answers. This lead me to believe that it might not be a very good component. Furthermore, I've never been a very big far for ActiveX. Frankly, I find ActiveX pretty ugly; Just another nightmare for C++ coders like other COM objects. Usually, I am pretty good at avoiding ActiveX when I can, but this time I'm cornered. Searching my favorite VC++ forums came up either empty or unanswered questions when I entered "print + MsChart". Oh boy, this is going to take me forever to figure out myself, right?

Not really. Surprisingly, it only took 5 hours of my Saturday afternoon.

Getting Started

I have to admit, MsChart isn't as difficult to use as I though. Actually, it is fairly easy to incorporate it into your project or report.

First thing you must do is add the MsChart Component into your project. For this, you can refer to JL Colson's article Passing an Array of Values to the Visual C++ MSChart OCX. It's a really nice article to get you started with adding the chart control. Either that, or you can can try to compile the sample source code. However, you might have to run the .reg file enclosed in the zip file so that everything that must be in the registry is there. The only thing you must make sure is that MsChart must reside in a form, so it's easier to create a SDI project with a CFormView as your view base.

How to Print MsChart

Alright. Now let's get down to the code. First thing you would need to do is go into your resource's toolbar and change the printer icon from ID_FILE_PRINT to ID_FILE_PRINT_PREVIEW. This makes it easier for us to access the print preview without always having to go to the File Menu. Next, use the ClassWizard and add OnPrint to your CFormView.

In the header of your view class (i.e. PrintMyChart.h) add this private member:

    protected:
        HBITMAP m_hbitmap;

Also be sure to use the ClassWizard to add a member variable m_chart to your MsChart component.

The actual logic to print the chart is actually quite simple. First, you tell the chart component to copy itself into the clipboard using m_chart.EditCopy(). Once it is in memory, create a DC and past the clipboard bitmap onto it and then transfer it to the printer's DC (pDC). Now, go to your view class and edit the two functions as shown below.

BOOL CPrintMyChartView::OnPreparePrinting(CPrintInfo* pInfo)
{
	// make preparation to print

	// order chart to copy itself onto clipboard
	m_chart.EditCopy();

	// make sure the item in clipboard is a bitmap
	if(IsClipboardFormatAvailable(CF_BITMAP))
	{
		if(OpenClipboard())
		{
			m_hbitmap = (HBITMAP)::GetClipboardData(CF_BITMAP);
			CloseClipboard();
		}
	}

	return DoPreparePrinting(pInfo);
}

void CPrintMyChartView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
{
	// Create a text for the title
	CString sTitleHeader=_T("My First Chart");

	// Get the page size and boundaries
	CRect rectPage = pInfo->m_rectDraw;

	TEXTMETRIC tm;
	CFont font;
	CSize textSize;
	int cyChar;

	// Create the font we will be using
	font.CreatePointFont(240, "Arial", pDC);
	CFont *pOldFont=pDC->SelectObject(&font);

	//Set Margin
	rectPage.top+=rectPage.bottom/48;
	rectPage.bottom-=rectPage.bottom/48;
	rectPage.left+=200;
	rectPage.right-=200;

	// Get Text size in order to center
	pDC->GetTextMetrics(&tm);
	textSize=pDC->GetTextExtent(sTitleHeader);
	cyChar = tm.tmHeight;


	// Draw Text (centered)
	pDC->TextOut(((rectPage.right+rectPage.left)/2)-(textSize.cx/2),
	                rectPage.top, sTitleHeader);
	rectPage.top += cyChar + cyChar / 4;

	// Draw header line divider
	pDC->MoveTo(rectPage.left, rectPage.top);
	pDC->LineTo(rectPage.right, rectPage.top);

	// Go to next line
	rectPage.top += cyChar / 4;

	if(m_hbitmap)
	{
		BITMAP bm;
		::GetObject(m_hbitmap, sizeof(BITMAP), &bm);
		CSize chartSize(bm.bmWidth, bm.bmHeight);

		CDC dcMemory,dcScreen;
		dcScreen.Attach(::GetDC(NULL));

		// create "from" device context and select the
		// loaded bitmap into it
		dcMemory.CreateCompatibleDC(&dcScreen);
		dcMemory.SelectObject(m_hbitmap);

		// Print at 85% size within left/right margin 
		CSize printSize;
		printSize.cx=(int)(rectPage.right*.85);
		printSize.cy=printSize.cx/chartSize.cx*chartSize.cy;

		// Print chart centered
		pDC->StretchBlt( ((rectPage.right+rectPage.left)/2)-
		         (printSize.cx/2), 
				  rectPage.top,
				  printSize.cx,
				  printSize.cy,
				  &dcMemory, 
				  0, 0, chartSize.cx, chartSize.cy, SRCCOPY);
		dcMemory.DeleteDC();
	}

	// Revert and Destroy
	pDC->SelectObject(pOldFont);
	font.DeleteObject();

}

Alrighty then. You're done! Now go and design that killer application report you've been dying to do! :)

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

About the Author

Victor Porter
Web Developer Shohero Technologies Ltd
Taiwan Taiwan
Member
Victor Porter is a software engineer who is in Taiwan learning martial arts in his virtually little free time. He also enjoys playing golf occasionally at 5:30 in the morning by sacrificing his dearly needed sleep. He is constantly racking his brains trying to decided if his next project should use his near-decade-experienced-C++ or his near-half-year-C# skills. An American from Pennsylvania, he is currently living in Taipei, Taiwan with his wife and lovely 6 months old daughter, Christine.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Generalconvert any file into tiff formattgroupmohan.iton2 Nov '09 - 19:43 
hey plz helpany body how to convert a any file formatt into tiff using vc++
QuestionIs it possible to copy Chartmemberchris17527 Jun '06 - 8:22 
I would like to copy one CMSChart into another CMSChart which is created dynamically. I attempt to do so and I get an error... I would like to copy one CMSChart into a very large CMSChart. Then when I get the HBITMAP from the very large CMSChart it will look really good when it is printed out.
 
I am using the CMSChart inside a dll, inside a template class, inside a private function....I dont want this chart to be visible because I just want it to hold the data.
CMSChart MSChart;
	if(!MSChart.Create("MSChart",WS_CHILD,rectPage,m_pMSChart->GetParent(),10))
	{
		//-rectPage is the rectangle of the page the
		// chart is to be printed on. 
		//-m_pMSChart is a pointer to the chart 
		// which is displayed on the screen.
		//
		// Error;
	}
 
Error = error C2668: 'Create' : ambiguous call to overloaded function
 
Chris
AnswerRe: Is it possible to copy ChartmemberAnanth.tm13 Oct '06 - 9:54 
Since error is ambiguous call to overloaded function, why don't you try calling CreateControl directly instead of the wrapped Create function and see if it works?
Look at your main IDispatch Wrapper for MSChart OCX
 
virtual BOOL Create(LPCTSTR lpszClassName,
LPCTSTR lpszWindowName, DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd, UINT nID,
CCreateContext* pContext = NULL)
{ return CreateControl(GetClsid(), lpszWindowName, dwStyle, rect, pParentWnd, nID); }
 
BOOL Create(LPCTSTR lpszWindowName, DWORD dwStyle,
const RECT& rect, CWnd* pParentWnd, UINT nID,
CFile* pPersist = NULL, BOOL bStorage = FALSE,
BSTR bstrLicKey = NULL)
{ return CreateControl(GetClsid(), lpszWindowName, dwStyle, rect, pParentWnd, nID,
pPersist, bStorage, bstrLicKey); }
 

And WS_CHILD has no effect here, see CWnd::CreateControl help in MSDN
QuestionGood Works But ,a Bug Exitting in it....membermrxwh2 Mar '06 - 19:24 
I run the example code,preview the mschart...runs ok...
then i minimize the preview's window,and copy/paste some others code ...restore the
preview's window...i find the preview window's graph dispears...HBITMAP'S handle not exit ---bcz i do other paste/copy operation...
How to prevent this?

I try to solve it by following code,but i failded...failure 1> 2>
 
BOOL CPrintMyChartView::OnPreparePrinting(CPrintInfo* pInfo)
{
// order chart to copy itself onto clipboard
m_pMSChart->EditCopy();
// make sure the item in clipboard is a bitmap
BOOL bGetGraph=IsClipboardFormatAvailable(CF_BITMAP);
if(!bGetGraph) return FALSE;
// open
bGetGraph=::OpenClipboard( GetSafeHwnd() );
if(!bGetGraph) return FALSE;
 
//copy Clipboard's HBITMAP to another HBITMAP
// clipboard's data
HANDLE hbitmap=GetClipboardData(CF_BITMAP);
//failure 1):dwSize=0
DWORD dwSize=GlobalSize(hbitmap);
//failure 2>:pOld=0x00000000
LPBYTE pOld=(LPBYTE)::GlobalLock( hbitmap );
// new rem area
m_hbitmap = (HBITMAP) ::GlobalAlloc(GMEM_MOVEABLE|GMEM_ZEROINIT,dwSize);
if ( m_hbitmap == NULL) return FALSE;
LPBYTE pNew = (LPBYTE)::GlobalLock( (HGLOBAL)m_hbitmap );
::memcpy(pNew, pOld, dwSize);
::GlobalUnlock(hbitmap);
::GlobalUnlock(m_hbitmap);
 
//close
CloseClipboard();
}

 
please help me....tks...
 

 


 
mrxwh@eyou.com from china
AnswerRe: Good Works But ,a Bug Exitting in it....membermrxwh7 Mar '06 - 20:46 
Smile | :) Big Grin | :-D
I found the solutions:
To Get the pic Data of m_pMSChart->EditCopy(),you must call GetClipboardData(CF_DIB) instead of GetClipboardData(CF_BITMAP).
then copy the DIB HANDLE'S DATA TO ANOTHER HANDLE...use the new handle's data as printoutLaugh | :laugh: Smile | :) Big Grin | :-D
 

GeneralMuch better way to print MSChart ControlmemberVictor Ricklefs9 Jan '06 - 3:35 
The much better way to print MSChart control (and any other ActiveX control) is to use IViewObject interface to draw the control directly to printer device context (as has been suggested by Andrew Wirger (http://www.codeproject.com/com/WirgerPrintArticle.asp[^]). Below is the code that I used to get a bitmap from MSChart control:
 
bool GetBMP(CMSChart* pChart, CRect* rect, CBitmap* bmp, CDC* dc)
{
	LPUNKNOWN pUnk = pChart->GetControlUnknown();
	
	if (!pUnk) return false;
	
	RECTL rectl;
	rectl.bottom=rect->bottom;
	rectl.top=rect->top;
	rectl.left=rect->left;
	rectl.right=rect->right;
	
	// do the drawing 
	HRESULT hRes = S_OK; 
	CDC dcMem;
	
	if (bmp->m_hObject) bmp->Detach();
 
	try 
	{
		// create a memory device context for offscreen drawing
		if (!dcMem.CreateCompatibleDC(dc))
			_com_issue_error(HRESULT_FROM_WIN32(::GetLastError())); 
		if (!bmp->CreateCompatibleBitmap(dc, rect->Width(), rect->Height()))
			_com_issue_error(HRESULT_FROM_WIN32(::GetLastError())); 
		CBitmap* oldbm = dcMem.SelectObject(bmp); 
 
		// query for the IViewObject interface for printing 
		IViewObjectPtr spViewObj; 
 
		hRes = pUnk->QueryInterface(__uuidof(spViewObj), (void **) &spViewObj); 	
		if (FAILED(hRes)) _com_issue_error(hRes); 
 
		// draw the object into our screen device context
		hRes = spViewObj->Draw(DVASPECT_CONTENT, -1, NULL, NULL, 
		NULL, dcMem, &rectl, NULL, NULL, 0); 
		if (FAILED(hRes)) _com_issue_error(hRes); 
 
		// delesect the device dependent bitmap from the offscreen device context 
		dcMem.SelectObject(oldbm); 
 
	}
	catch (_com_error & e)
	{
		AfxMessageBox(e.Error() + (LPCTSTR) e.Description()); 
		return false;
	}
	return true;
} 
 
Victor Ricklefs

QuestionRe: Much better way to print MSChart Controlmemberchris17527 Jun '06 - 8:05 
I would really like to use this function but I have been very unsuccessful in doing so. Is there anyway to make function with parameters CMSChart* pChart, CRect* rect, HBITMAP* hbitmap?
 
Chris
Generalmschart labelmemberedge22 Jan '06 - 2:57 
How I do modify text label for axis ? I wish to display JAN FEB and not R1 R2...
Thanks
 
PS: sorry for my English Smile | :)
AnswerRe: mschart labelmemberchris17527 Jun '06 - 8:08 
Look into the GetDataGrid function and use the SetRowLabel.
 
Chris
Generaltext over or above the columnsmembermarcdev2 Aug '05 - 4:55 
How can I add the row value over or above the bar/colum?
 
Marc Soleda.

 
... she said you are the perfect stranger she said baby let's keep it like this... Tunnel of Love, Dire Straits.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 5 Aug 2002
Article Copyright 2002 by Victor Porter
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid