How to handle events from embedded Excel.OleObjects or Excel.Shapes

18

I'm working on C# and now VB.NET ports of an old VBA program. It has lots of MSForms/OleObjects embedded in it like CommandButton or even images.

My first thought was to declare all the buttons as Microsoft.Vbe.Interop.Forms.CommandButtonbut that leads to a COM exception that the System._COM type can't be cast to ...Forms.CommandButton. If I try a more generic version of this solution, I don't find any items, and if I try to go through all VBComponets I note that they are all the sheets in the workbook, but none of the controls:

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

Thus all of these controls are not in .VBComponets, but I can find them as OLEobjects in thisworkbook.worksheets(n).OLEobjects (this is counterintutive to me, but I probably don't understand the system to begin with).

How do I handle the Click action from such an object?

I'm assuming that I need to be using the Excel.OLEObjectEvents_Event interface, but I can't seem to figure out how. If I try to make custom events with delegates, I don't seem to be able to assign them to OleObjects. If I use ActionClickEventHandler.CreateDelegate I can get a huge variety of errors that makes me think that's a dead end.

The official documentation from MS doesn't seem that helpful, though it did introduce me to the idea of Verb, which I'm looking into. So far that has only produced COM errors along the lines of "Application Failed to start."

Even just trying to use one of the two standard events, .GotFocus, I always pull a 0x80040200 error. Example:

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

Throws a COMException Exception from HRESULT: 0x80040200 at the second line. The button is enabled, which is I checked after looking up the code number from the office dev site.

Trying a more generic approach within the code for a sheet containing controls:

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

Throws a Missing Method error.

Any help is greatly appreciated, this seems like this should be obvious and I'm missing it.


**Edit: I have also found that I can cast these controls into Excel.Shape but that doesn't actually get me any closer to running a function or sub from the VSTO. I'm playing with Excel.Shape.OnAction but this requires a VBA sub to be called. Presumably, I could call a VBA sub which calls a sub from the VSTO as long as the VSTO was COM visible. This seems really round-about and I'd only like to do it as a last resort.

c#
vb.net
excel
vba
vsto
asked on Stack Overflow Mar 13, 2014 by Atl LED • edited Oct 18, 2019 by Jaymin

4 Answers

1

Have you tried using 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);

It's referenced on MSDN in the VSTO forums and in an old blog post.

answered on Stack Overflow Mar 24, 2014 by Peter G
1

Solution Type: VSTO Document-Level

Scenario:
1.) Excel.Worksheet created at run-time. (not a Worksheet Host Item)
2.) Add a button on the Worksheet at run-time that triggers C# code when clicked.

Assembly References:
Microsoft.Vbe.Interop (Microsoft.Vbe.Interop.dll)
Microsoft.Vbe.Interop.Forms (Microsoft.Vbe.Interop.Forms.dll)
Microsoft.VisualBasic (Microsoft.VisualBasic.dll)

Tested / Working Code:

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");
}
answered on Stack Overflow Oct 13, 2017 by Leo Gurdian • edited Jan 19, 2018 by Leo Gurdian
0

Can you programmatically add code to a CodeModule in the Workbook, 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
answered on Stack Overflow Mar 24, 2014 by Peter G • edited May 23, 2017 by Community
0

Use the interop forms toolkit. It is free from Microsoft. It provides com wrappers and event messenger class that communicates the event data to and from .NET to VBA. I have used it to handle control events from VBA in .NET, and events from .NET to VBA. You would use the interop

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

From the toolkit ducoumentation:

Interop UserControls provide a basic set of intrinsic events (Click, GotFocus, Validate, etc.) in Visual Basic 6.0. You can define your own custom events in Visual Studio .NET and raise them using RaiseEvent. In order to handle the events in Visual Basic 6.0, you need to add a project reference and add a WithEvents variable declaration in your code, and then handle the events using the WithEvents variable rather than the control itself.

How To Handle Interop UserControl Custom Events

1. In Visual Basic 6.0, on the Project menu, click References. Note that there is already a reference to ControlNameCtl, where ControlName is the name of your Interop UserControl.

2. In the Available References list, locate a reference for ControlName and check it, and then click OK.

3. In the Code Editor, add a declaration for a WithEvents variable:

Dim WithEvents ControlNameEvents As ControlLibrary.ControlName

In the Form_Load event handler, add the following code to initialize the WithEvents variable:

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

Add code to handle your custom event in the WithEvents variable's event handler:

Private Sub ControlNameEvents_MyCustomEvent()
    MsgBox("My custom event fired")
End Sub
answered on Stack Overflow Aug 20, 2014 by John Pearson • edited Aug 20, 2014 by John Pearson

User contributions licensed under CC BY-SA 3.0