【问题标题】:Duplicate an Excel chart and move it to another sheet复制 Excel 图表并将其移动到另一个工作表
【发布时间】:2015-11-18 14:27:53
【问题描述】:

我正在使用 C# Excel 互操作,我想从一个工作表创建一个图表的副本,但我希望这个副本在另一张工作表上。我尝试了以下方法:

Excel.ChartObject chartTemplate = (Excel.ChartObject)sheetSource.ChartObjects("chart 1");
object o = chartTemplate.Duplicate();
Excel.ChartObject chart = (Excel.ChartObject)sheetSource.ChartObjects("chart 2");
chart.Name = "Skew" + expiry.ToString("MMMyy");
range = sheetDestination.Range["T" + chartRowCoutner.ToString()];

chart.Chart.Location(Excel.XlChartLocation.xlLocationAsObject, range);

但是当我尝试这个时,最后一行会抛出一个错误:

在 projectname.exe 中发生了“System.Exception”类型的未处理异常

附加信息:读取 Excel 文件 C:\ 时出错...文件路径...\template.xlsx:值不在 预期范围。

我也尝试过传入工作表而不是范围:

chart.Chart.Location(Excel.XlChartLocation.xlLocationAsObject, sheetDestination);

但这给出了同样的错误。我无法理解错误的原因或如何修复/绕过它。

我试图避免将剪贴板带入其中,但即使我尝试复制和粘贴,我仍然只能将其粘贴为图像,这确实不理想:

Excel.ChartArea chartArea = chart.ChartArea;
chartArea.Copy();
range = sheetDestination.Range["T" + chartRowCoutner.ToString()]; // Note that chart is not on the sheet sheetDestination
range.PasteSpecial(Excel.XlPasteType.xlPasteAll);

我现在能想到的唯一其他解决方案是在 VBA 中执行此操作,然后通过互操作执行宏。但可以肯定的是,只需使用不带剪贴板的互操作,它就可以以一种干净的方式完成。

【问题讨论】:

  • 你能给那个范围对象写一些随机值吗?
  • @MatthewD 是的。我刚刚测试了用range.Value = "TESTING"; 替换错误行,它工作正常。
  • 嘿,好像有一个 chart.copy 方法。 msdn.microsoft.com/en-us/library/…
  • 这是图表区域的另一个选项。 stackoverflow.com/questions/25120862/…
  • @MatthewD 作为最后的手段,我会尝试这样做,但如果我可以将剪贴板排除在外,我更愿意这样做。在我看来,.Location() method 是为跨工作表移动图表而设计的,但我找不到任何示例。

标签: c# excel charts excel-interop


【解决方案1】:

您已经找到了解决方案,但我不会给您一天的鱼,而是给您一个正确的答案,这将帮助您完成任何 C# Excel 编码任务。

Excel 的 C# 互操作模型与 VBA Excel 模型几乎相同。

这意味着将 VBA 录制的宏转换为 C# 很简单。让我们通过一个练习来试试这个,比如将图表移动到不同的工作表。


在 Excel 的开发人员选项卡中单击录制宏>右键单击图表>选择移动图表>选择对象:Sheet2>单击确定>单击停止宏录制。

要查看录制的宏,请按 Alt + F11 调出 VB 编辑器:

在上面的屏幕截图中,VBA 显示 Location() 的第二个参数是 Name,它实际上是一个 字符串参数...

让我们将此 VBA 宏转换为 C#:

@Ama 编辑

以下建议已过时,实际上无需担心释放 COM 对象,这是在 RELEASE 模式下自动完成的 (DEBUG mode does not)。见Hans Passant's answer to "Clean up Excel Interop Objects with IDisposable"


这里的诀窍是:永远不要对 com 对象使用 2 个点。

注意我是怎么写的:

var sheetSource = workbookWrapper.ComObject.Sheets["Sheet1"];

但它有两个点,所以我写这个:

var workbookComObject = workbookWrapper.ComObject;
var sheetSource = workbookComObject.Sheets["Sheet1"];

参考:How do I properly clean up Excel interop objects?

您将在上述 QA 中看到 VSTOContrib 之类的项目使用的 AutoReleaseComObject 代码。

完整代码如下:

using Microsoft.Office.Interop.Excel;
...
var missing = Type.Missing;
using (AutoReleaseComObject<Microsoft.Office.Interop.Excel.Application> excelApplicationWrapper = new AutoReleaseComObject<Microsoft.Office.Interop.Excel.Application>(new Microsoft.Office.Interop.Excel.Application()))
{
    var excelApplicationWrapperComObject = excelApplicationWrapper.ComObject;
    excelApplicationWrapperComObject.Visible = true;

    var excelApplicationWrapperComObjectWkBooks = excelApplicationWrapperComObject.Workbooks;
    try
    {
        using (AutoReleaseComObject<Workbook> workbookWrapper = new AutoReleaseComObject<Workbook>(excelApplicationWrapperComObjectWkBooks.Open(@"C:\Temp\ExcelMoveChart.xlsx", false, false, missing, missing, missing, true, missing, missing, true, missing, missing, missing, missing, missing)))
        {
            var workbookComObject = workbookWrapper.ComObject;
            Worksheet sheetSource = workbookComObject.Sheets["Sheet1"];
            ChartObject chartObj = (ChartObject)sheetSource.ChartObjects("Chart 3");
            Chart chart = chartObj.Chart;
            chart.Location(XlChartLocation.xlLocationAsObject, "Sheet2");

            ReleaseObject(chart);
            ReleaseObject(chartObj);
            ReleaseObject(sheetSource);

            workbookComObject.Close(false);
        }
    }
    finally
    {
        excelApplicationWrapperComObjectWkBooks.Close();
        ReleaseObject(excelApplicationWrapperComObjectWkBooks);

        excelApplicationWrapper.ComObject.Application.Quit();
        excelApplicationWrapper.ComObject.Quit();
        ReleaseObject(excelApplicationWrapper.ComObject.Application);
        ReleaseObject(excelApplicationWrapper.ComObject);

        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();    
    }
}

private static void ReleaseObject(object obj)
{
    try
    {
        while (System.Runtime.InteropServices.Marshal.ReleaseComObject(obj) > 0);
        obj = null;
    }
    catch (Exception ex)
    {
        obj = null;
        Console.WriteLine("Unable to release the Object " + ex.ToString());
    }
}

我知道释放所有对象,使用 GC.Collect 并且在分配时不使用两个点似乎在顶部,但至少当我退出 Excel 实例时,进程被释放,我不必以编程方式杀死 Excel处理!

参考:Microsoft KB: Office application does not quit after automation from .NET client

【讨论】:

  • @Dan 如果您在服务器上执行此操作,我建议您改用ClosedXML
  • 愚蠢的问题,但是什么是服务器?
  • 您的开发盒是否运行服务器操作系统?或存储在服务器上的 Excel 文件? 0x80010105 RPC_E_SERVERFAULT 表示服务器抛出异常
  • 等一下……你的开发 PC 上安装了 Excel 吗?
  • 为混淆道歉,目前的代码确实有效。要获取错误,您需要将行 Excel.Shape shape = shapes.Item("Chart Copy"); 更改为 Excel.Shape shape = shapes.Item(chartCopy.Name);
【解决方案2】:

来自此处的 MSDN 文档:

https://msdn.microsoft.com/en-us/library/microsoft.office.tools.excel.chart.location.aspx

它声明对于对象类型的 Name 参数:

名字 类型:System.Object 如果 Where 是 xlLocationAsObject,则为嵌入图表的工作表的名称;如果是 xlLocationAsNewSheet,则为新工作表的名称。

这与同一链接页面底部的示例有些误导。从给出的示例中可以看出,您实际上应该传递工作表名称的字符串。示例中的相关行复制如下(示例用于复制到 new 工作表):

chart1.Location(Excel.XlChartLocation.xlLocationAsNewSheet, 
    "Sales");

所以,为了移动到现有工作表,我会这样做:

chart1.Location(Excel.XlChartLocation.xlLocationAsObject, 
        "ExistingSheetName");

请勿传递范围、工作簿或工作表对象。尝试工作表名称的字符串

现在,从上面链接的同一个 MSDN 文档页面中,如果您想在将图表移动到另一个工作表后在页面内重新定位图表,还有其他说明,为方便起见,在此重复:

如果要将图表移动到工作表上的另一个位置,请使用 P:Microsoft.Office.Interop.Excel.ChartArea.Top 属性和 P:Microsoft.Office.Interop.Excel.ChartArea.Left ChartArea 的属性。您可以使用 ChartArea 属性获取 Chart 的 ChartArea 对象。

如果您要将图表移至现有工作表,请注意不要将图表与现有数据重叠。如果是这样,您将不得不单独编写代码。

【讨论】:

  • 谢谢,我明天试试。虽然必须将工作表的名称作为字符串传递似乎很疯狂,但是发生了奇怪的事情。
  • @Dan - 我同意。特别是因为参数是 System.Object 类型的。我不清楚为什么该方法不简单地采用字符串参数。当然,由于 string 继承自 object(显然),因此 string 作为参数是有效的。几乎感觉它是方法签名设计框架中的一个错误……很少见,但并非不可能。
  • 所以图表现在确实复制到了目标工作表。但是尝试使用 chartArea.Top = range.Top; 重新定位它会引发错误:HRESULT: 0x80010105 (RPC_E_SERVERFAULT)
  • 您是否查看了我的答案并在 VBA 中录制了一个执行相同操作的宏?为什么是服务器故障?您不是在服务器上运行自动化代码吗?请参阅此知识库:support.microsoft.com/en-us/kb/257757
  • @JeremyThompson 是的,它使用形状。它抛出了相同的错误:Excel.Shapes shapes = sheetReport.Shapes; Excel.Shape shape = shapes.Item(chartCopy.Name); shape.Top = range.Top; 虽然当你使用宏记录器移动东西时,它使用一个名为IncrementTop 的函数或其他东西,而不是直接设置Top 属性,但我首先在 VBA 中尝试了我的 C# 代码,它可以工作在那里。
【解决方案3】:

这不是您所提问题的答案,但可能会有成果

如果您要制作副本并针对不同的变体进行编辑,这不是解决方案

如果您真的只是在复制图表,那么我建议您改用 Excel 的“相机”功能。它基本上会在另一个工作表中创建一个窗口 - 您可以通过编程方式执行此操作,并且它有很好的文档记录,但是我认为如果我没有指出的话,我认为我会疏忽大意。

-E

如果您要进行编辑并且问题仍然存在,请在评论中告诉我 - 我已经完成了这项工作,然后我只需要回顾我的工作簿并确切了解我是如何做到的。

'相机选项很好,因为它不会'重新计算'数据 - 所以我想它运行得更快;大型工作簿中的一个问题。

【讨论】:

  • 所以它基本上是一个链接图像?不,这不是我害怕之后的样子。我希望将实际图表作为图表对象。还是谢谢。
  • 是的 - 一个链接的图像。
猜你喜欢
  • 1970-01-01
  • 2018-07-22
  • 2016-09-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-16
  • 1970-01-01
相关资源
最近更新 更多