【问题标题】:How to handle events from embedded Excel.OleObjects or Excel.Shapes如何处理来自嵌入式 Excel.OleObjects 或 Excel.Shapes 的事件
【发布时间】:2014-04-17 14:24:36
【问题描述】:

我正在开发一个旧的VBA 程序的C#VB.NET 端口。它有很多 MSForms/OleObjects 嵌入其中,例如 CommandButton 甚至图像。

我的第一个想法是将所有按钮声明为Microsoft.Vbe.Interop.Forms.CommandButton,但这会导致COM 异常,即System._COM type 无法转换为...Forms.CommandButton。如果我尝试更通用的this solution 版本,我找不到任何项目,如果我尝试遍历所有VBComponets,我注意到它们是workbook 中的所有工作表,但没有一个控制:

foreach (VBComponent xxx in Globals.ThisWorkbook.VBProject.VBComponents) {
    Interaction.MsgBox(xxx.Name);
    Interaction.MsgBox(xxx.ToString);
}

因此所有这些控件都不在.VBComponets 中,但我可以在thisworkbook.worksheets(n).OLEobjects 中找到它们作为OLEobjects(这对我来说是违反直觉的,但我可能不理解系统开始)。

如何处理来自此类对象的 Click 操作?

我假设我需要使用Excel.OLEObjectEvents_Event 接口,但我似乎无法弄清楚如何使用。如果我尝试使用delegates 制作自定义事件,我似乎无法将它们分配给OleObjects。如果我使用ActionClickEventHandler.CreateDelegate,我会遇到各种各样的错误,这让我觉得这是一条死胡同。

MS 的official documentation 似乎没有多大帮助,尽管它确实向我介绍了Verbidea,我正在研究它。到目前为止,这只产生了类似于“应用程序无法启动”的 COM 错误。

即使只是尝试使用两个标准事件之一,.GotFocus,我总是会拉出 0x80040200 错误。 示例:

Excel.OLEObject ButtonCatcher = Globals.ThisWorkbook.Worksheets(1).OLEObjects("CommandButton1");
ButtonCatcher.GotFocus += CommandButton1_Click;

在第二行抛出 COMException Exception from HRESULT: 0x80040200。该按钮已启用,这是我在查找code number from the office dev site.后检查的

在包含控件的工作表的代码中尝试更多 generic approach

object CommandButtonStart = this.GetType().InvokeMember("CommandButton1", System.Reflection.BindingFlags.GetProperty, null, this, null);

引发缺少方法错误。

非常感谢任何帮助,这似乎应该是显而易见的,我很想念它。


**编辑:我还发现我可以将这些控件转换为Excel.Shape,但这实际上并没有让我更接近从 VSTO 运行函数或子程序。我在玩Excel.Shape.OnAction,但这需要调用 VBA 子程序。据推测,只要 VSTO 是 COM 可见的,我就可以调用从 VSTO 调用子的 VBA 子。这似乎真的很迂回,我只想作为最后的手段。

【问题讨论】:

  • 您确定它们是否是工作表上的 ActiveX 控件吗?常规表单控件(非 activeX)不显示事件,只能通过 onAction 方法调用 subs。您可能会遇到哪些问题?
  • 帮助我的无知。我发现 VBA 嵌入在 Office 软件程序(或采用它的其他程序之一)中。 VBA 不是独立的。那么an old VBA program是什么?
  • 顺便说一句,但根据我的旅行社的说法,所有好的“最后的手段”都是“真正的迂回”。这几乎是一个规则。
  • @Kyle 它们是 ActiveX 控件,例如,可以通过 TructCenter 选项禁用 ActiveX 使其消失。
  • @Smandoli 当然,它不是一个完整的程序,但编程分类法不是我的强项(在 Bio.SE 上向我寻求实际分类法的帮助)。我们应该从 VSTO 中借用并将项目称为插件吗?我不介意任何人编辑该短语以使其更正确。我没有编写原始代码,也从未发现过 VBA。 VBA 解决方案?有组织的宏集合?我认为重点是,不是吗?

标签: c# vb.net excel vba vsto


【解决方案1】:

解决方案类型: VSTO Document-Level

场景:
1.) Excel.Worksheet 在运行时创建。 (不是Worksheet Host Item
2.) 在运行时在工作表上添加一个按钮,单击时触发 C# 代码。

程序集参考:
Microsoft.Vbe.Interop (Microsoft.Vbe.Interop.dll)
Microsoft.Vbe.Interop.Forms (Microsoft.Vbe.Interop.Forms.dll)
Microsoft.VisualBasic (Microsoft.VisualBasic.dll)

测试/工作代码:

using MSForms = Microsoft.Vbe.Interop.Forms;
using System.Windows.Forms;

...

Microsoft.Vbe.Interop.Forms.CommandButton CmdBtn;

private void CreateOLEButton()
{
   Excel.Worksheet ws = Globals.ThisWorkbook.Application.Sheets["MyWorksheet"];

   // insert button shape
   Excel.Shape cmdButton = ws.Shapes.AddOLEObject("Forms.CommandButton.1", Type.Missing, false, false, Type.Missing, Type.Missing, Type.Missing, 500, 5, 100, 60);
   cmdButton.Name = "btnButton";

   // bind it and wire it up
   CmdBtn = (Microsoft.Vbe.Interop.Forms.CommandButton)Microsoft.VisualBasic.CompilerServices.NewLateBinding.LateGet(ws, null, "btnButton", new object[0], null, null, null);
   CmdBtn.Caption = "Click me!";
   CmdBtn.Click += new MSForms.CommandButtonEvents_ClickEventHandler(ExecuteCmd_Click);
}

private void ExecuteCmd_Click()
{
   MessageBox.Show("Click");
}

【讨论】:

    【解决方案2】:

    您是否尝试过使用 NewLateBinding.LateGet?

    using MSForms = Microsoft.Vbe.Interop.Forms;
    using Microsoft.VisualBasic.CompilerServices;
    
    ...
    
    MSForms.CommandButton CommandButton1 = (MSForms.CommandButton)NewLateBinding.LateGet(Globals.ThisWorkbook.Worksheets(1), null, "CommandButton1", new object[0], null, null, null);                
    CommandButton1.Click += new Microsoft.Vbe.Interop.Forms.CommandButtonEvents_ClickEventHandler(CommandButton1_Click);
    

    它在 MSDN 上的 VSTO forumsold blog post 中被引用。

    【讨论】:

    • 不,我会试一试。
    • 仍然得到 0x80040200,结合 Ben 的评论,我们也许可以让它工作。
    • 即使使用提升的注册库,仍然得到0x80040200 错误。不过似乎是个好主意。发现这并不难测试,制作一个 ActiveX 按钮并尝试 NewLateBinding.LateGet 在该行抛出错误。
    【解决方案3】:

    您能否以编程方式将代码添加到工作簿中的 CodeModule,like this

    Private Sub CommonButton_Click(ByVal buttonName As String)
      MsgBox "You clicked button [" & buttonName & "]"
    End Sub
    
    Private Sub CreateEventHandler(ByVal buttonName As String)
        Dim VBComp As VBIDE.VBComponent
        Dim CodeMod As VBIDE.CodeModule
        Dim codeText As String
        Dim LineNum As Long
    
        Set VBComp = ThisWorkbook.VBProject.VBComponents(Me.CodeName)
        Set CodeMod = VBComp.CodeModule
        LineNum = CodeMod.CountOfLines + 1
    
        codeText = codeText & "Private Sub " & buttonName & "_Click()" & vbCrLf
        codeText = codeText & "  Dim buttonName As String" & vbCrLf
        codeText = codeText & "  buttonName = """ & buttonName & "" & vbCrLf
        codeText = codeText & "  CommonButton_Click buttonName" & vbCrLf
        codeText = codeText & "End Sub"
        CodeMod.InsertLines LineNum, codeText
    End Sub
    

    【讨论】:

    • 刚试了一下,从 HRESULT 抛出异常:0x80040200,让我玩一会儿
    • 尝试稍微清理一下,也有一些无效的演员表。即 Me.Codename 是 Sheet1 但需要是 int;所以更改为this.index等。可以编译,但它没有向按钮添加任何功能。
    • 如果它应该将 VBA 宏放在模块或工作表代码之一的末尾,它似乎没有把它们放在那里(我检查了所有这些)。 Button 什么都不做,我尝试使用 VBComp 的索引,但仍然没有。
    • 啊,我刚刚意识到你的意思是这是一个 VBA 解决方案。是的,我可以称它为由 VBA 处理的操作,但这与我的目标完全无关。我试图让它们由 VSTO 处理,而不是在 VBA 中,如果我们可以避免的话。
    【解决方案4】:

    使用互操作表单工具包。它是微软免费提供的。它提供了 com 包装器和事件信使类,用于将事件数据与 .NET 和 VBA 进行通信。我用它来处理 .NET 中来自 VBA 的控制事件,以及从 .NET 到 VBA 的事件。您将使用互操作

    http://www.codeproject.com/Articles/15690/VB-C-Interop-Form-Toolkit

    来自工具包文档:

    Interop UserControls 在 Visual Basic 6.0 中提供了一组基本的内在事件(Click、GotFocus、Validate 等)。您可以在 Visual Studio .NET 中定义自己的自定义事件并使用 RaiseEvent 引发它们。为了在 Visual Basic 6.0 中处理事件,您需要在代码中添加项目引用并添加 WithEvents 变量声明,然后使用 WithEvents 变量而不是控件本身来处理事件。

    如何处理互操作用户控件自定义事件

    1。 在 Visual Basic 6.0 的项目菜单上,单击引用。请注意,已经有对 ControlNameCtl 的引用,其中 ControlName 是您的 Interop UserControl 的名称。

    2。 在“可用引用”列表中,找到 ControlName 的引用并选中它,然后单击“确定”。

    3。 在代码编辑器中,为 WithEvents 变量添加声明:

    Dim WithEvents ControlNameEvents As ControlLibrary.ControlName
    

    在 Form_Load 事件处理程序中,添加以下代码来初始化 WithEvents 变量:

    Private Sub Form_Load()
        Set ControlNameEvents = Me.ControlNameOnVB6Form
    End Sub
    

    在 WithEvents 变量的事件处理程序中添加代码以处理您的自定义事件:

    Private Sub ControlNameEvents_MyCustomEvent()
        MsgBox("My custom event fired")
    End Sub
    

    【讨论】:

      【解决方案5】:

      感谢 Leo Gurdian
      在 VB.Net 中看起来像这样:

      程序集参考:
      Microsoft.Vbe.Interop (Microsoft.Vbe.Interop.dll)
      Microsoft.Vbe.Interop.Forms (Microsoft.Vbe.Interop.Forms.dll)
      Microsoft.VisualBasic (Microsoft.VisualBasic.dll)

      Imports Microsoft.Office.Interop.Excel
      Imports Microsoft.VisualBasic
      Imports MSForms = Microsoft.Vbe.Interop.Forms
      
      Private Sub CreateOLEButton()
          Dim ws As Excel.Worksheet = (CType(Application.ActiveSheet, Excel.Worksheet))
          Dim cmdButton As Excel.Shape = ws.Shapes.AddOLEObject("Forms.CommandButton.1", Type.Missing, False, False, Type.Missing, Type.Missing, Type.Missing, 500, 5, 100, 60)
          cmdButton.Name = "btnButton"
          Dim CmdBtn As Microsoft.Vbe.Interop.Forms.CommandButton = CType(Microsoft.VisualBasic.CompilerServices.NewLateBinding.LateGet(ws, Nothing, "btnButton", New Object(-1) {}, Nothing, Nothing, Nothing), Microsoft.Vbe.Interop.Forms.CommandButton)
          CmdBtn.Caption = "Click me!"
          AddHandler CmdBtn.Click, AddressOf ExecuteCmd_Click
      End Sub
      Private Sub ExecuteCmd_Click()
          'do something 
      End Sub
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-06-22
        • 2018-10-15
        • 1970-01-01
        • 1970-01-01
        • 2021-11-07
        • 1970-01-01
        • 1970-01-01
        • 2021-05-10
        相关资源
        最近更新 更多