简体   繁体   English

OpenXML SDK将VBA注入excel工作簿

[英]OpenXML SDK Inject VBA into excel workbook

I can successfully inject a piece of VBA code into a generated excel workbook, but what I am trying to do is use the Workbook_Open() event so the VBA code executes when the file opens. 我可以成功地将一段VBA代码注入到生成的excel工作簿中,但我想要做的是使用Workbook_Open()事件,以便在文件打开时执行VBA代码。 I am adding the sub to the "ThisWorkbook" object in my xlsm template file. 我将sub添加到我的xlsm模板文件中的“ThisWorkbook”对象。 I then use the openxml productivity tool to reflect the code and get the encoded VBA data. 然后我使用openxml生产力工具来反映代码并获取编码的VBA数据。

When the file is generated and I view the VBA, I see "ThisWorkbook" and "ThisWorkbook1" objects. 当生成文件并查看VBA时,我看到“ThisWorkbook”和“ThisWorkbook1”对象。 My VBA is in "ThisWorkbook" object but the code never executes on open. 我的VBA在“ThisWorkbook”对象中,但代码永远不会在打开时执行。 If I move my VBA code to "ThisWorkbook1" and re-open the file, it works fine. 如果我将我的VBA代码移动到“ThisWorkbook1”并重新打开该文件,它可以正常工作。 Why is an extra "ThisWorkbook" created? 为什么要创建一个额外的“ThisWorkbook”? Is it not possible to inject an excel spreadsheet with a Workbook_Open() sub? 是否无法使用Workbook_Open()子系统注入excel电子表格? Here is a snippet of the C# code I am using: 这是我正在使用的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

Anyone have ideas? 有人有想法吗?

Based on my research there isn't a way to insert the project part data in a format that you can manipulate in C#. 基于我的研究,没有办法以您可以在C#中操作的格式插入项目部件数据。 In the OpenXML format, the VBA project is still stored in a binary format. 在OpenXML格式中,VBA项目仍以二进制格式存储。 However, copying the VbaProjectPart from one Excel document into another should work. 但是,将VbaProjectPart从一个Excel文档复制到另一个Excel文档应该可行。 As a result, you'd have to determine what you wanted the project part to say in advance. 因此,您必须事先确定项目部分要求您说出的内容。

If you are OK with this, then you can add the following code to a template Excel file in the 'ThisWorkbook' Microsoft Excel Object, along with the appropriate Macro code: 如果您对此没有问题,则可以将以下代码添加到“ThisWorkbook”Microsoft Excel对象中的模板Excel文件中,以及相应的宏代码:

Private Sub Workbook_Open()
    Run "Module1.SomeMacroName()"
End Sub

To copy the VbaProjectPart object from one file to the other, you would use code like this: 要将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);
    }
}

Hope that helps. 希望有所帮助。

You need to specify "codeName" attribute in the "xl/workbook..xml" object After feeding the VbaProjectPart with macro. 在使用宏提供VbaProjectPart之后,需要在“xl / workbook..xml”对象中指定“codeName”属性。 Add this code: 添加此代码:

var workbookPr = spreadsheetDocument.WorkbookPart.Workbook.Descendants<WorkbookProperties>().FirstOrDefault();
workbookPr.CodeName = "ThisWorkBook";

After opening the file everything should work now. 打开文件后,一切都应该正常工作。

So, to add macro you need to: 因此,要添加宏,您需要:

  1. Change document type to macro enabled 将文档类型更改为启用宏

  2. Add VbaProjectPart and feed it with earlier created macro 添加VbaProjectPart并使用之前创建的宏提供它

  3. Add workbookPr codeName attr in xl/workbook..xml with value "ThisWorkBook" 在xl / workbook..xml中添加workbookPr codeName attr,其值为“ThisWorkBook”

  4. Save as with .xlsm ext. 另存为.xlsm ext。

I found that the other answers still resulted in the duplicate "Worksheet" object. 我发现其他答案仍然导致重复的“工作表”对象。 I used a similar solution to what @ZlotaMoneta said, but with a different syntax found here : 我使用了与@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";
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM