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)
{
m_chart.EditCopy();
if(IsClipboardFormatAvailable(CF_BITMAP))
{
if(OpenClipboard())
{
m_hbitmap = (HBITMAP)::GetClipboardData(CF_BITMAP);
CloseClipboard();
}
}
return DoPreparePrinting(pInfo);
}
void CPrintMyChartView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
{
CString sTitleHeader=_T("My First Chart");
CRect rectPage = pInfo->m_rectDraw;
TEXTMETRIC tm;
CFont font;
CSize textSize;
int cyChar;
font.CreatePointFont(240, "Arial", pDC);
CFont *pOldFont=pDC->SelectObject(&font);
rectPage.top+=rectPage.bottom/48;
rectPage.bottom-=rectPage.bottom/48;
rectPage.left+=200;
rectPage.right-=200;
pDC->GetTextMetrics(&tm);
textSize=pDC->GetTextExtent(sTitleHeader);
cyChar = tm.tmHeight;
pDC->TextOut(((rectPage.right+rectPage.left)/2)-(textSize.cx/2),
rectPage.top, sTitleHeader);
rectPage.top += cyChar + cyChar / 4;
pDC->MoveTo(rectPage.left, rectPage.top);
pDC->LineTo(rectPage.right, rectPage.top);
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));
dcMemory.CreateCompatibleDC(&dcScreen);
dcMemory.SelectObject(m_hbitmap);
CSize printSize;
printSize.cx=(int)(rectPage.right*.85);
printSize.cy=printSize.cx/chartSize.cx*chartSize.cy;
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();
}
pDC->SelectObject(pOldFont);
font.DeleteObject();
}
Alrighty then. You're done! Now go and design that killer application report
you've been dying to do! :)