OpenXML SDK注入VBA到Excel工作簿

9
我可以成功地将一段VBA代码注入到生成的Excel工作簿中,但我想做的是使用Workbook_Open()事件,使得VBA代码在文件打开时执行。我将子程序添加到我的xlsm模板文件的"ThisWorkbook"对象中。然后,我使用OpenXml生产力工具来反映代码并获取编码的VBA数据。
当文件生成并查看VBA时,我看到"ThisWorkbook"和"ThisWorkbook1"对象。我的VBA位于"ThisWorkbook"对象中,但代码从未在打开时执行。如果我将我的VBA代码移动到"ThisWorkbook1"中并重新打开文件,则可以正常工作。为什么会多出一个"ThisWorkbook"?不可能向Excel电子表格中注入Workbook_Open()子程序吗?这是我使用的C#代码片段:
private string partData = "...";  //base 64 encoded data from reflection code
//open workbook, myWorkbook
VbaProjectPart newPart = myWorkbook.WorkbookPart.AddNewPart<VbaProjectPart>("rId1");
System.IO.Stream data = GetBinaryDataStream(partData);
newPart.FeedData(data);
data.Close();
//save and close workbook

有人有想法吗?


在VBA编辑器中,当您右键单击ThisWorkbook和ThisWorkbook1时,是否有“删除ThisWorkbook...”选项被禁用? - Derek B. Bell
3个回答

6
根据我的研究,目前没有办法以可以在C#中操作的格式插入项目部分数据。在OpenXML格式中,VBA项目仍然以二进制格式存储。但是,将一个Excel文档中的 VbaProjectPart 复制到另一个Excel文档中应该可以工作。因此,您需要事先确定项目部分要说的内容。
如果您可以接受这种方法,那么您可以将以下代码添加到模板Excel文件的 'ThisWorkbook' Microsoft Excel对象中,并加上适当的宏代码:
Private Sub Workbook_Open()
    Run "Module1.SomeMacroName()"
End Sub

要将一个文件中的 VbaProjectPart 对象复制到另一个文件中,您需要使用以下代码:
public static void InsertVbaPart()
{
    using(SpreadsheetDocument ssDoc = SpreadsheetDocument.Open("file1.xlsm", false))
    {
        WorkbookPart wbPart = ssDoc.WorkbookPart;
        MemoryStream ms;
        CopyStream(ssDoc.WorkbookPart.VbaProjectPart.GetStream(), ms);

        using(SpreadsheetDocument ssDoc2 = SpreadsheetDocument.Open("file2.xlsm", true))
        {
            Stream stream = ssDoc2.WorkbookPart.VbaProjectPart.GetStream();
            ms.WriteTo(stream);
        }
    }
}

public static void CopyStream(Stream input, Stream output)
{
    byte[] buffer = new byte[short.MaxValue + 1];
    while (true)
    {
        int read = input.Read(buffer, 0, buffer.Length);
        if (read <= 0)
            return;
        output.Write(buffer, 0, read);
    }
}

希望这有所帮助。

我还应该补充一点,在写完之后,我发现了一个名为ClosedXML的平台。它专门设计用于与OpenXML Excel文件交互,并且非常直观,这也可能会有所帮助。 - Lentonator

1

我发现其他答案仍会导致重复的“Worksheet”对象。我使用了与@ZlotaMoneta类似的解决方案,但使用了此处找到的不同语法

List<VbaProjectPart> newParts = new List<VbaProjectPart>();
using (var originalDocument = SpreadsheetDocument.Open("file1.xlsm"), false))
{
    newParts = originalDocument.WorkbookPart.GetPartsOfType<VbaProjectPart>().ToList();

    using (var document = SpreadsheetDocument.Open("file2.xlsm", true))
    {
        document.WorkbookPart.DeleteParts(document.WorkbookPart.GetPartsOfType<VbaProjectPart>());

        foreach (var part in newParts)
        {
            VbaProjectPart vbaProjectPart = document.WorkbookPart.AddNewPart<VbaProjectPart>();
            using (Stream data = part.GetStream())
            {
                vbaProjectPart.FeedData(data);
            }                    
        }

        //Note this prevents the duplicate worksheet issue
        spreadsheetDocument.WorkbookPart.Workbook.WorkbookProperties.CodeName = "ThisWorkbook";
    }
}

0
你需要在“xl/workbook.xml”对象中指定“codeName”属性,在将VbaProjectPart与宏一起添加后,请添加此代码:
var workbookPr = spreadsheetDocument.WorkbookPart.Workbook.Descendants<WorkbookProperties>().FirstOrDefault();
workbookPr.CodeName = "ThisWorkBook";

打开文件后,现在应该一切正常。

所以,要添加宏,你需要:

  1. 将文档类型更改为启用宏

  2. 添加 VbaProjectPart 并将其与之前创建的宏关联起来

  3. 在 xl/workbook..xml 中添加 workbookPr codeName 属性,值为"ThisWorkBook"

  4. 另存为 .xlsm 格式


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接