Introduction
Just recently, I was
given a tucancode.net: "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.
Collapse
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! :)