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.CommandButton
but 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 VBComponet
s 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.
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.
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");
}
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
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
User contributions licensed under CC BY-SA 3.0