65.9K
CodeProject 正在变化。 阅读更多。
Home

避免 MFC 中 MSChart EditCopy / Legend 错误

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.67/5 (2投票s)

2005年2月2日

3分钟阅读

viewsIcon

66728

downloadIcon

2456

一种在 MFC 中解决 ActiveX MSChart 控件 EditCopy / Legend 错误的方法。

Sample Image

引言

本文提供的代码提出了一种解决方法,用于解决使用 ActiveX 控件(MSChart)的 EditCopy 方法导出图表时遇到的问题。问题在于,当使用 EditCopy 方法将图表复制到内存时,此方法不会导出图例中的文本。 相反,它会被替换为“C1”、“C2”等……微软承认了这个问题,并提供了一个描述该问题的页面和一个解决方案,但他们只为 Visual Basic 5/6 提供了解决方法。 对于在 MFC 项目中使用 Visual C++ 6 的人,没有提供解决方案。

由于我正在从事的项目经常使用 MSChart,并且 MSChart 实际上非常有用,因此无法将其全部替换为另一种类型的图表。 因此,我必须解决这个问题并开发一种避免 EditCopy 错误的方法。

背景

从根本上讲,Windows 中的一切,如果你可以想象,都是一种画布。 因此,屏幕上的任何对象都可以被引用,然后作为位图提取。 您必须确定对象的坐标,并使用 Windows API 的 BitBlt 函数直接将该块复制到内存中。 这有点像截屏,只不过您只拍摄特定区域的屏幕。

使用代码

如前所述,通常会使用 EditCopy 方法(如下面的代码所示)将图表复制到剪贴板。 这是一种简单的一行方法,适用于任何类型的图表,但有一个例外。 如果您使用的是包含图例的图表,则该错误会阻止图例被正确输出。 相反,你会得到“C1”、“C2”等……[参考图 2]。

m_mschart1.EditCopy();

Sample Image

为了避免这个特殊的问题,必须应用另一种方法来检索_带有_图例的图表。 如下面的代码所示,您需要获取 MSChart 的设备上下文并执行图表的 BitBlt 操作,然后将其作为位图复制到剪贴板。

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

关注点

使用 BitBlt 方法当然很有趣,但并非没有缺点。 因为 BitBlt 就像拍摄屏幕截图一样,并且您为其提供了需要从屏幕复制的区域的坐标,如果图表恰好在屏幕外(即,如果您的图表位于可以滚动的文档上并且当前从视图中隐藏)或者它被另一个窗口遮挡,您会在剪贴板中获得一个图像,该图像要么是黑色的,要么充满了伪影。 我针对此方法的解决方法是使用 EditCopy 方法复制 MSChart 的数据,并创建一个新的弹出对话框,其中包含另一个 MSChart 并使用 PasteCopy 方法(这有效,因为 EditCopy 也复制数据,而不仅仅是将图像复制到剪贴板)。 确保对话框的尺寸正确,并使用 OnTimer() 事件以及 OnInitDialog() 中的 SetTimer()(请参见下面的代码)。

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

这样做会将图表粘贴到另一个 MSChart 中,该 MSChart 在您的对话框中设置。 然后,由此创建一个计时器,该计时器等待 x 毫秒,然后将 MSChart 捕获到剪贴板,然后终止时间,同时自动关闭对话框。

这样,您得到的是一个在屏幕上闪烁的弹出窗口,该窗口将图表复制到剪贴板并自行关闭[参考图 3]。

Sample Image

这是我解决这个问题的一种方法。 这都在本文提供的源代码中。

历史

当前版本:版本 1.1。

  • 版本 1.1 - 添加了弹出对话框方法。
  • 版本 1.0 - 针对 EditCopy 错误的 BitBlt 解决方法。
© . All rights reserved.