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

Avoiding the MSChart EditCopy / Legend bug in MFC

0.00/5 (No votes)
1 Feb 2005 1  
A workaround to the ActiveX MSChart control EditCopy / Legend bug in MFC.

Sample Image

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();

Sample Image

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;

    // Get device context from MSChart

    pChartDC = m_mschart1.GetDC();

    // Get dimensions of MSChart

    RECT mschartRect;
    m_mschart1.GetClientRect( &mschartRect );
    int mschartWidth = mschartRect.right - mschartRect.left;
    int mschartHeight = mschartRect.bottom - mschartRect.top;

    // Create CBitmap

    CBitmap myBitmap;

    // Create Compatible Bitmap for MSChart

    myBitmap.CreateCompatibleBitmap( pChartDC, 
               mschartWidth, mschartHeight );

    // Define device-context object

    CDC myCopy;
    myCopy.CreateCompatibleDC( pChartDC );

    // Get pointer to object being replaced

    myCopy.SelectObject( myBitmap );

    // Raster copy Bitmap from object pChartDC is pointing to, which is MSChart


    // CAUTION: this process copies _exactly_ what is shown on screen. If MSChart is

    // off-screen (ie. if the page is scrollable and it is currently hidden from

    // view) OR a dialog or other window is blocking its view, then you will not

    // get the correct result, as it will either turn out all black or have

    // artifacts from other windows on it. It is for this reason I have chosen to

    // copy the MSChart _before_ showing the 'Save File Dialog'

    myCopy.BitBlt( 0, 0, mschartWidth, mschartHeight, pChartDC, 0, 0, SRCCOPY );

    // Retrieve information about the CBitmap

    BITMAP bits;
    myBitmap.GetBitmap( &bits );

    // Open clipboard and empty its contents

    OpenClipboard();
    EmptyClipboard();

    // Copy our new MSChart bitmap to clipboard and close it

    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();

        // Paste from the EditCopy method from the parent dlg

        m_mschart1.EditPaste();

        // Refresh the MSChart to reflect changes

        m_mschart1.Refresh();

        // Initialize a Timer event.

        //

        // Timer will trigger an event in X milliseconds,

        // the event being the MemDC copy

        // and BitBlt of the resulting MSChart.

        // Then it automatically closes the dialog.

        // All these events should make it seem seamless

        // to the user, which no input is required of.

        //    Refer to function: --> void CMSChartSaveDlg::OnTimer(UINT nIDEvent)

        SetTimer(1, 1000, 0);

        return TRUE;  // return TRUE unless you set the focus to a control

                      // EXCEPTION: OCX Property Pages should return FALSE

    }
    void CPrintMyMschartLegendsDlgExportForm::OnTimer(UINT nIDEvent)
    {
        // TODO: Add your message handler code here and/or call default

        CClientDC dc(this);

        CDC* pChartDC;
        // Get device context from MSChart

        pChartDC = m_mschart1.GetDC();
        // Get dimensions of MSChart

        RECT mschartRect;
        m_mschart1.GetClientRect( &mschartRect );
        int mschartWidth = mschartRect.right - mschartRect.left;
        int mschartHeight = mschartRect.bottom - mschartRect.top;
        // Create CBitmap

        CBitmap myBitmap;
        // Create Compatible Bitmap for MSChart

        myBitmap.CreateCompatibleBitmap( pChartDC, 
                               mschartWidth, mschartHeight);
        // Define device-context object

        CDC myCopy;
        myCopy.CreateCompatibleDC( pChartDC );
        // Get pointer to object being replaced

        myCopy.SelectObject( myBitmap );
        // Raster copy Bitmap from object pChartDC

        // is pointing to, which is MSChart

        //

        // CAUTION: this process copies

        // _exactly_ what is shown on screen.

        myCopy.BitBlt( 0, 0, mschartWidth, mschartHeight, 
                               pChartDC, 0, 0, SRCCOPY );
        // Retrieve information about the CBitmap

        BITMAP bits;
        myBitmap.GetBitmap( &bits );
        // Open clipboard and empty its contents

        OpenClipboard();
        EmptyClipboard();
        // Copy our new MSChart bitmap to clipboard and close it

        SetClipboardData( CF_BITMAP, myBitmap.GetSafeHandle() );
        CloseClipboard();

        CDialog::OnTimer(nIDEvent);
        // Kill the timer, so that it only occurs once

        KillTimer(nIDEvent);

        // This will close the dialog

        // and return a IDOK message back to parent

        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].

Sample Image

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.

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