Introduction
The code provided in this article proposes a workaround to the problem with trying to export charts created with the ActiveX control (MSChart) using the EditCopy method. The problem is, when using the EditCopy
method to copy the chart to memory, this method does not export the text in the legend. Instead, it is replaced with "C1", "C2", etc... This issue is acknowledged by Microsoft, which has a page describing the problem and a solution, however they only provide a workaround for Visual Basic 5/6. No solution has been provided for those using this ActiveX control in a MFC project using Visual C++ 6.
As the project I was working on used MSChart frequently and because MSChart is actually quite useful, it was not possible to replace all of it with another type of chart. Hence, I had to workaround this problem and develop a method of avoiding the EditCopy bug.
Background
Fundamentally, everything in Windows is, if you can imagine it, a sort of canvas. Hence, any object on the screen can be referenced to and then extracted as a bitmap. You must determine the coordinates of the object and use the BitBlt
function of the Windows API to directly copy that block to memory. It is a bit like taking a screenshot, except that you are only taking a shot of a certain area.
Using the code
As previously mentioned, one would normally use the EditCopy
method (as in the code below) to copy a chart to clipboard. It is a simple one line approach, which works well for any type of chart, with one exception. If you are using a chart which contains a legend, a bug prevents the legend from being outputted correctly. Instead, you get "C1", "C2", etc... [refer to Figure 2].
m_mschart1.EditCopy();
In order to avoid this particular problem, an alternative method must be applied in order to retrieve the chart _with_ the legend. As the code below indicates, you will need to get the device context of the MSChart and perform a BitBlt
operation of the chart, which then will be copied to the clipboard as a bitmap.
CDC* pChartDC;
pChartDC = m_mschart1.GetDC();
RECT mschartRect;
m_mschart1.GetClientRect( &mschartRect );
int mschartWidth = mschartRect.right - mschartRect.left;
int mschartHeight = mschartRect.bottom - mschartRect.top;
CBitmap myBitmap;
myBitmap.CreateCompatibleBitmap( pChartDC,
mschartWidth, mschartHeight );
CDC myCopy;
myCopy.CreateCompatibleDC( pChartDC );
myCopy.SelectObject( myBitmap );
myCopy.BitBlt( 0, 0, mschartWidth, mschartHeight, pChartDC, 0, 0, SRCCOPY );
BITMAP bits;
myBitmap.GetBitmap( &bits );
OpenClipboard();
EmptyClipboard();
SetClipboardData( CF_BITMAP, myBitmap.GetSafeHandle() );
CloseClipboard();
Points of Interest
Using the BitBlt
method is certainly interesting, but not without some shortcomings. Because BitBlt
is sort of taking a screenshot of the screen and you are providing it with the coordinates of the area it needs to copy from the screen, if the chart happens to be off screen (i.e., if your chart is on a document, which can be scrolled and it is currently hidden from view) or if it is being obscured by another window, you get an image in the clipboard, which is either black or full of artifacts. My workaround for this method is to copy the data of the MSChart using the EditCopy
method and create a new popup dialog containing another MSChart and use the PasteCopy
method (this works, since EditCopy
also copies data and not just an image to the clipboard). Make sure the dialog is of the right dimensions, and use the OnTimer()
event, along with a SetTimer()
in the OnInitDialog()
(see code below).
BOOL CPrintMyMschartLegendsDlgExportForm::OnInitDialog()
{
CDialog::OnInitDialog();
m_mschart1.EditPaste();
m_mschart1.Refresh();
SetTimer(1, 1000, 0);
return TRUE;
}
void CPrintMyMschartLegendsDlgExportForm::OnTimer(UINT nIDEvent)
{
CClientDC dc(this);
CDC* pChartDC;
pChartDC = m_mschart1.GetDC();
RECT mschartRect;
m_mschart1.GetClientRect( &mschartRect );
int mschartWidth = mschartRect.right - mschartRect.left;
int mschartHeight = mschartRect.bottom - mschartRect.top;
CBitmap myBitmap;
myBitmap.CreateCompatibleBitmap( pChartDC,
mschartWidth, mschartHeight);
CDC myCopy;
myCopy.CreateCompatibleDC( pChartDC );
myCopy.SelectObject( myBitmap );
myCopy.BitBlt( 0, 0, mschartWidth, mschartHeight,
pChartDC, 0, 0, SRCCOPY );
BITMAP bits;
myBitmap.GetBitmap( &bits );
OpenClipboard();
EmptyClipboard();
SetClipboardData( CF_BITMAP, myBitmap.GetSafeHandle() );
CloseClipboard();
CDialog::OnTimer(nIDEvent);
KillTimer(nIDEvent);
CDialog::OnOK();
}
What this does is it pastes the chart into another MSChart, which is set up in your dialog. Then, from this, it creates a timer, which waits x amount of milliseconds before capturing the MSChart to clipboard and the kills the time, while closing the dialog automatically.
This way, what you get is a popup flashing up on screen, which copies the chart to clipboard and closes by itself [refer to Figure 3].
This is one way I worked around this issue. It is all in the source code which is provided with this article.
History
Current version: version 1.1.
- Version 1.1 - Added the popup dialog method.
- Version 1.0 -
BitBlt
workaround to the EditCopy bug.