首页 > 解决方案 > 如何(或可能)从通过 VSTO 添加到 Excel 的功能区调用 VBA(子或函数)

问题描述

我正在开发一个项目,其中有一个 Visual Studio C# Excel 插件项目 (VSTO)。从 VSTO 代码中,我将带有按钮的功能区添加到 Excel (Office.IRibbonExtensibility)。通过 Excel 中添加的按钮,我可以对 C# 代码(在 VSTO 插件中)中的方法进行回调调用。我正在寻找(而不是寻找)从该按钮调用 VBA(函数的子函数)到 Excel 文件中的 VBA 代码的方法。换句话说,通过该按钮,我知道如何调用 C# 中的代码,但不知道如何调用 Excel 文件本身中的 VBA 代码。我花了相当长的时间搜索信息并测试一些盲目的想法,但没有成功找到任何东西或从我的测试中获得一些好的结果。我将不胜感激朝着正确的方向迈进。

不确定这是否是稍后添加此内容的正确位置和方式(2/10/20 @17:08 GMT -7)注释(在获得以下答案后)。我创建了一个小型演示项目并将其上传到 github。项目中也有一个视频(mp4)文件来展示它是如何工作的。 https://github.com/MNemteanu/ExcelVSTOAddInDemo

标签: c#excelvbavstoadd-in

解决方案


这个有可能。
您必须注意,VBA 代码存储在特定的工作簿中,而 VSTO 加载项在应用程序中加载,尽管有活动的工作簿。除非开发人员知道,否则两者都不知道对方。
为了实现这样的交互,您必须了解以下内容:
1. 宏保存工作簿的名称;
2. 宏名。

知道了这一点,您将能够应用在 3d 评论中发布的解决方案。
下面是一个例子。
前提条件:
1. 我已经准备好启用宏的工作簿“VBA.xlsm”;
2. 本工作簿在常规模块中有一个名为“Foo”的宏。

实现:
1、新建VSTO插件;
2. 添加名为“Ribbon1”的功能区(可视化设计器)并将其设置为具有“自定义”ControlIdType(作为名为“测试”的单独选项卡);
3. 在该功能区中添加一个名为“callVBA”的按钮,该按钮将检查书名并尝试运行工作簿的宏。

我没有向 ThisAddIn.cs 类添加任何代码。我使用的唯一代码 - 在 Ribbon 类中的按钮单击事件处理程序中:

public partial class Ribbon1
    {
        private void Ribbon1_Load(object sender, RibbonUIEventArgs e)
        {

        }

        private void callVBA_Click(object sender, RibbonControlEventArgs e)
        {
            if (Globals.ThisAddIn.Application.ActiveWorkbook.Name == "VBA.xlsm")
            {
                Globals.ThisAddIn.Application.Workbooks["VBA.xlsm"].Application.Run("Foo");
            }
        }
    }

这很容易,但需要您准备好先决条件。
这个怎么运作:
在此处输入图像描述

更新 1

这是更复杂的方法,它检查打开的工作簿并根据是否存在具有所需宏的工作簿来启用/禁用特定按钮。它还检查新打开的并处理仅包含一个Workbook_Activate事件的最近关闭的工作簿。
如果你不做任何检查 - 你可能会得到System.Runtime.InteropServices.COMException一个

Message=无法将焦点移至控件,因为它不可见、未启用或属于不接受焦点的类型。

 public partial class Ribbon1
    {
        private bool vbaMacroFound;

        private void Ribbon1_Load(object sender, RibbonUIEventArgs e)
        {
            CheckButtons();
            Globals.ThisAddIn.Application.WorkbookActivate += new Excel.AppEvents_WorkbookActivateEventHandler(Workbook_Activate);
        }

        private void callVBA_Click(object sender, RibbonControlEventArgs e)
        {
            if (vbaMacroFound)
            {
                Globals.ThisAddIn.Application.Workbooks["VBA.xlsm"].Application.Run("Foo");
            }
        }

        private void Workbook_Activate(Excel.Workbook Wb)
        {
            CheckButtons();
        }

        private void CheckButtons()
        {
            vbaMacroFound = false;
            this.callVBA.Enabled = false;
            this.callVBA.ScreenTip = "There is no specified macro in none of active workbooks";

            foreach (Excel.Workbook book in Globals.ThisAddIn.Application.Workbooks)
            {

                if (book.Name.Equals("VBA.xlsm"))
                {
                    this.callVBA.Enabled = true;
                    this.callVBA.ScreenTip = "Call the sub from VBA";
                    vbaMacroFound = true;
                }

            }
        }
    }

推荐阅读