Troubleshoot "Unable to get the Buttons property of the Worksheet class"? - excel

I created a 7-sheet Excel spreadsheet as a test-help companion to a popular bridge book by Kantar called "Modern Bridge Defense". The spreadsheet has a fairly large number of form buttons that allow a user to show or hide test answers from each of the seven chapters in the book
I have vba code that shows or hides the answer text, depending on the caption ('show' or 'hide') of the associated button, and a 'show/hide all' button that will show or hide all answers associated with a particular section of that chapter's test.
I also have vba code that initializes all the buttons on all 7 sheets. When the user first opens the spreadsheet, he/she is asked if they are OK with hiding all the answers. If they agree, the initialization routine loops through all 7 sheets, and the code for each sheet loops through all the buttons on that sheet, hiding each answer that isn't already hidden.
All this works fine if I step through the buttons in debug mode, but fails with "Unable to get the Buttons property of the Worksheet class" at some point (not always the same point) when I try to run it full speed.
This behavior seems like it might be some sort of timing/race issue, but I'm having trouble imagining how that could be, as I don't think I'm really tasking my laptop (XPS15 7590 with 32GB Ram, 1TB SSD).
Here is my initialization routine and the function it calls to iterate through the sheet buttons:
Option Explicit
Private Sub Workbook_Open()
Debug.Print ("In Workbook_Open()")
Dim res As VbMsgBoxResult
res = MsgBox("Hide all answers?", vbYesNoCancel, "Kantar Test Initialization")
If res = vbYes Then
res = MsgBox("Caution! This action will hide all answers - Are you SURE you want to do this?", vbYesNoCancel, "Are you SURE?")
If res = vbYes Then
'OK, user is sure about doing this!
Dim sheet As Worksheet
For Each sheet In Application.Sheets
Debug.Print "Initializing worksheet " & sheet.Name
On Error Resume Next
InitializeAllButtons sheet
If Err <> 0 Then
Debug.Print "call to InitializeAllButtons)" & sheet.Name & " failed with " & Err.Description
End If
Next sheet
End If
End If
End Sub
and here's the function that actually 'clicks' the buttons
Sub InitializeAllButtons(sheet As Worksheet)
Dim btn As Excel.Button, addrstr, startcellstr, startrowstr, endrowstr, colstr As String
Dim pos As Integer
Dim startrow As Integer
Dim endrow As Integer
Dim col As Integer
Dim rownum As Integer
With sheet
For Each btn In .Buttons
Debug.Print (vbTab & btn.Name & ", " & btn.Caption)
'11/27/22 rev to set 'all' button captions to 'Hide All', click on all 'Hide' row buttons
If InStr(btn.Caption, "All") > 0 Then
btn.Caption = "Show All"
Debug.Print "btn " & btn.Name & " caption changed to Show All"
Else 'is normal row show/hide button
If btn.Caption = "Hide" Then
RowButtonClick (btn.Name)
End If
End If
Next
End With
End Sub
In response to a question, here is the 'RowButtonClick()' function
Function RowButtonClick(btn_name As String) As Integer
Dim btn As Excel.Button
Dim btn_text As String
Dim cellstr As String
Dim row, col As Integer
Dim textrange As Range
'for multiple row show/hide ops
Dim cellstrlen As Integer
Dim startrowstr, endrowstr, startcolstr As String
Dim startrow As Integer
Dim endrow As Integer
Dim charidx As Integer
'11/24/22 multiple row button names include '_'
charidx = InStr(btn_name, "_")
If charidx > 0 Then
'11/24/22 row addresses may be 1, 2, or 3 digits
GetStartEndRowCol btn_name, startrow, endrow, col
Debug.Print ("RBC just after GSER: start row = " & startrow & ", end row = " & endrow)
Else 'single row: endrow = startrow
cellstr = Mid(btn_name, 9)
startrow = Range(cellstr).row
endrow = startrow
col = Range(cellstr).Column
Debug.Print "RBC: Single row show/hide action"
End If
Set btn = Application.ActiveSheet.Buttons(btn_name)
btn_text = btn.Caption
With Application.ActiveSheet
If btn_text = "Show" Then
Application.ActiveSheet.Range(.Cells(startrow, col + 1), .Cells(endrow, col + 2)).NumberFormat = ""
btn.Caption = "Hide"
Else
Application.ActiveSheet.Range(.Cells(startrow, col + 1), .Cells(endrow, col + 2)).NumberFormat = "; ; ;"
btn.Caption = "Show"
End If
End With
RowButtonClick = endrow 'so calling fcn knows the next row to try
End Function

Related

Skip rows with empty cells

I'm working on automating orders over WhatsApp with an Excel Sheet.
My programming experience is bare bones, so I used tutorials and other stack overflow threads to get to solutions. I have something that works, but these scripts send the full lists even if the item quantity cell is empty, which doesn't work for me.
From my understanding I need a If Else statement to do this, but I dont know where to place it.
The goal is to if a cell in the column is empty that row is skipped. How can I do that?
The below is the script that opens the browser and sends the messages.
Sub WebWhatsApp()
Dim pop As Range
Dim BOT As New WebDriver
Dim KS As New Keys
Dim count_row As Integer
count_row = ActiveSheet.Cells(Rows.Count, "A").End(xlUp).Row
Dim rng As Range
Set rng = Sheets("Interface").Range(Cells(12, 1), Cells(count_row, 5))
Dim myString As String
myString = Rang2String(rng)
BOT.Start "chrome", "https://web.whatsapp.com/"
BOT.Get "/"
MsgBox _
"Please scan the QR code." & _
"After you are logged in, please confirm this message box by clicking 'ok'", vbOKOnly, "WhatsApp Bot"
searchtext = Sheets("Interface").Range(Cells(5, 8), Cells(5, 8))
textmessage = myString
BOT.FindElementByXPath("//*[#id='side']/div[1]/div/label/div/div[2]").Click
BOT.Wait (500)
BOT.SendKeys (searchtext)
BOT.Wait (500)
BOT.SendKeys (KS.Enter)
BOT.Wait (500)
BOT.SendKeys (textmessage)
BOT.Wait (1000)
BOT.SendKeys (KS.Enter)
MsgBox "Done."
End Sub
And this is the script that turns a range of the Excel sheet into a string that the main script sends out as text messages.
Function Rang2String(rng As Range) As String
Dim strng As String
Dim myRow As Range
Dim KS As New Keys
With rng
For Each myRow In .Rows
strng = strng & Join(Application.Transpose(Application.Transpose(myRow.Value)), " | ") & vbNewLine
Next
End With
Rang2String = Left(strng, Len(strng) - 1)
End Function
I realize that the answer could be very obvious but I cant seem to see a solution.
Thanks in advance.
Option Explicit
Function Rang2String(rng As Range) As String
Const COL_QU = "D" ' quantity column
Dim e As String, myrow As Range
With Application
For Each myrow In rng.Rows
If Len(myrow.Cells(1, COL_QU)) > 0 Then
Rang2String = Rang2String & e & Join(.Transpose(.Transpose(myrow)), " | ")
e = vbNewLine
End If
Next
End With
End Function

Subscript out of range error in Excel VBA when I add a new worksheet at the end of the list of worksheets only when the VB window is closed

I have built an Excel Workbook that is intended for evaluation of an organization as a whole and then evaluation of each of several sites for that organization. There is an initial assessment and then an on-site assessment for the organization, and for each facility. Depending on the organization, the number of facilities will vary. There is a first "Configuration" tab where the user of the workbook enters (or copies and pastes) the list of facilities and determines which facilities are to be included in the evaluation.
The second and third worksheets are the assessment for the organization as a whole, and the fourth and fifth worksheets are template assessment forms for the facilities.
Once the list of facilities is entered, the user clicks on a button labeled "Create Facility Tabs" that steps through the facility list and creates the needed worksheets for each facility.
If the list is fresh (starting from its initial form), then the template worksheets are renamed for the first facility and new worksheets are created for the remainder.
If there are already worksheets identified, the software checks each facility to see if its page already exists, and only creates additional worksheets for newly added facilities.
When additional worksheets are needed, the code first counts the number of additional worksheets that are needed (two for each facility), creates those worksheets, and then steps through them copying the template contents onto the forms and the change code for the worksheets into the worksheet's module.
The software works perfectly over and over again when I have the VBA window open. It does everything it is supposed to do. However, when I close the VBA window, the code creates all the worksheets, copies everything into the first worksheet, and then raises a Subscript Out of Range error. Any ideas what I am doing wrong?
Here is the code:
Public Sub CreateFacilities()
Dim row As Long
Dim facility_name As String
Dim facility_list As String
Dim facilities As Variant
Dim include As Boolean
Dim ws_init As Worksheet
Dim ws_fac As Worksheet
Dim ws_new_init As Worksheet
Dim ws_new_fac As Worksheet
Dim ws_config As Worksheet
Dim facility_count As Long
Dim tabs_to_create As Long
Dim fac_initial_range As Range
Dim fac_initial_address As String
Dim fac_onsite_range As Range
Dim fac_onsite_address As String
Dim message As String
Dim title As String
Dim answer As Variant
Dim code_line As String
Dim b_width As Long
Dim c_width As Long
Dim counter As Long
Dim init_sheet_number As Long
Dim fac_sheet_number As Long
Dim tab_count As Long
title = "Creating Facility Tabs"
message = "Before you execute this function you should" & vbCrLf & "add any study-specific questions to the" & vbCrLf
message = message & "Initial Assessment - Facility1 and" & vbCrLf & "On-Site Assessment - Facility1 tabs so" & vbCrLf
message = message & "they will be included on the created facility tabs" & vbCrLf & vbCrLf
message = message & "Do you wish to continue?"
answer = MsgBox(message, vbYesNo + vbQuestion, title)
If answer = vbNo Then
Exit Sub
End If
Set ws_config = ThisWorkbook.Sheets("Configuration")
Set ws_init = ThisWorkbook.Sheets(4)
Set ws_fac = ThisWorkbook.Sheets(5)
b_width = ws_init.Columns("B").ColumnWidth
c_width = ws_init.Columns("C").ColumnWidth
Set fac_initial_range = ws_init.Range("A1:C" & Trim(Str(FindLastRow(ws_init))))
Set fac_onsite_range = ws_fac.Range("A1:C" & Trim(Str(FindLastRow(ws_fac))))
fac_initial_address = fac_initial_range.address
fac_onsite_address = fac_onsite_range.address
code_line = ThisWorkbook.VBProject.VBComponents("Module1").CodeModule.Lines(1, 50) 'get code for each new worksheet
facility_list = "" 'get list of facilities
facility_count = 0
For row = 4 To 54
facility_name = ThisWorkbook.Sheets("Configuration").cells(row, 2).value
include = ThisWorkbook.Sheets("Configuration").cells(row, 4).value
If facility_name = "" Then 'reached the end of the list
Exit For
Else:
If include Then 'the Do Assessment column is marked TRUE
If Not WorksheetExists(facility_name) Then 'the tabs for this facility do not already exist
facility_list = facility_list & facility_name & ","
End If
End If
End If
Next row
facility_list = Left(facility_list, Len(facility_list) - 1) 'remove trailing comma
If facility_list = "" Then 'no new facilities were added to the list
MsgBox "There were no facilties specified for inclusion"
Exit Sub
End If
facilities = Split(facility_list, ",") 'there is a list of facilities to add
facility_count = UBound(facilities) + 1
If ActiveWorkbook.Sheets.Count = 5 Then 'no facility tabs have been added
If facility_count = 1 Then 'there is only one facility - no tabs need to be added
tabs_to_create = 0
facility_name = facilities(0)
ws_init.Name = CreateInitialTabName(facility_name)
ws_fac.Name = CreateOnSiteTabName(facility_name)
Else:
tabs_to_create = (facility_count - 1) * 2
ActiveWorkbook.Sheets.Add After:=ActiveWorkbook.Sheets(ActiveWorkbook.Sheets.Count), Count:=tabs_to_create
For counter = LBound(facilities) To UBound(facilities)
facility_name = facilities(counter)
If counter = 0 Then 'rename the first two facility worksheets that already exist
ws_init.Name = CreateInitialTabName(facility_name)
ws_fac.Name = CreateOnSiteTabName(facility_name)
Else: 'for the rest, add worksheets and copy template content and code
init_sheet_number = ((counter - 1) * 2) + 6
fac_sheet_number = init_sheet_number + 1
Set ws_new_init = ActiveWorkbook.Sheets(init_sheet_number) 'create initial assessment sheet for facility
ws_new_init.Columns("B").ColumnWidth = b_width
ws_new_init.Columns("C").ColumnWidth = c_width
ws_new_init.Name = CreateInitialTabName(facility_name)
fac_initial_range.Copy Destination:=ws_new_init.Range("A1")
ThisWorkbook.VBProject.VBComponents(ws_new_init.CodeName).CodeModule.AddFromString code_line
Set ws_new_fac = ActiveWorkbook.Sheets(fac_sheet_number) 'create on-site assessment sheet for facility
ws_new_fac.Columns("B").ColumnWidth = b_width
ws_new_fac.Columns("C").ColumnWidth = c_width
ws_new_fac.Name = CreateOnSiteTabName(facility_name)
fac_onsite_range.Copy Destination:=ws_new_fac.Range("A1")
ThisWorkbook.VBProject.VBComponents(ws_new_fac.CodeName).CodeModule.AddFromString code_line
End If
Next counter
End If
Else: 'there are more than 5 tabs in the workbook - some were already added
tab_count = ActiveWorkbook.Sheets.Count
tabs_to_create = facility_count * 2
ActiveWorkbook.Sheets.Add After:=ActiveWorkbook.Sheets(tab_count), Count:=tabs_to_create
For counter = LBound(facilities) To UBound(facilities)
facility_name = facilities(counter)
init_sheet_number = (counter * 2) + (tab_count + 1)
fac_sheet_number = init_sheet_number + 1
Set ws_new_init = ActiveWorkbook.Sheets(init_sheet_number)
Set ws_new_fac = ActiveWorkbook.Sheets(fac_sheet_number)
ws_new_init.Name = CreateInitialTabName(facility_name)
ws_new_fac.Name = CreateOnSiteTabName(facility_name)
ws_new_init.Columns("B").ColumnWidth = b_width
ws_new_fac.Columns("B").ColumnWidth = b_width
ws_new_init.Columns("C").ColumnWidth = c_width
ws_new_fac.Columns("C").ColumnWidth = c_width
fac_initial_range.Copy Destination:=ws_new_init.Range("A1")
fac_onsite_range.Copy Destination:=ws_new_fac.Range("A1")
ThisWorkbook.VBProject.VBComponents(ws_new_init.CodeName).CodeModule.AddFromString code_line
ThisWorkbook.VBProject.VBComponents(ws_new_fac.CodeName).CodeModule.AddFromString code_line
Next counter
End If
ws_config.Activate
MsgBox Str(facility_count) & " facilities added"
End Sub

Table refresh vba excel Call procedure from another procedure Error Code 1004

I have a call procedure to clear contents of tables across multiple worksheets.
This procedure is invoked only from the 2nd sheet of the workbook. When I invoke this, I am getting Error 1004 "Application-defined or Object-defined error".
Below is the parent code base invoking the sub procedure:
Sub ValidateData_BDV1()
On Error Resume Next
Err.Clear
'''''Define Variables'''''''''
Dim mySheet As Worksheet
Dim mySheetName As String
Dim bdvName As Variant
Dim sqlQuery As String
Dim connectStr As String
Dim wsMatch As Worksheet
Dim myWorkbook As Workbook: Set myWorkbook = ThisWorkbook
'''''''''Set Variables''''''''
cancelEvent = False
Set mySheet = ActiveSheet 'Sets mySheet variable as current active sheet
mySheetName = mySheet.Name
driverName = mySheet.Range("B1").Value2 'Get the value of the TDV driver
' MsgBox driver
dataSourceName = mySheet.Range("B3").Value2 'Get the data source name for the published TDV database
' MsgBox dataSourceName
schemaName = mySheet.Range("B5").Value2 'Get the schema name of the published tdv view
bdvName = mySheet.Range("B6").Value2 'Get the name of the published BDV
''''''''''Refresh data across sheets'''''''''''''
Application.ScreenUpdating = False 'Prevent screen flickering while doing the refresh
'''''''''''''''''''''''''''''''''''''''
''''''''''''Call sub procedure'''''''''
Call ClearTableContents
''''''''''''''''''''''''''''''''''''
mySheet.Activate
Application.ScreenUpdating = True 'Prevent screen flickering while doing the refresh
''''''''Show User id and Password box'''''''''
If Len(Uid) < 1 Or Len(Password) < 1 Then
UserForm1.Show
End If
If (cancelEvent = True) Then
Exit Sub
End If
............
............perform some task with error handling
Below is the code base of the called Sub
Sub ClearTableContents()
Dim wrksht As Worksheet
Dim objListObj As ListObjects
Dim tableName As String
Dim ActiveTable As ListObject
Dim rowCount As Integer
Dim colCount As Integer
Dim i As Integer
Dim j As Integer
'''''Iterate through the Bdv1, bdv2 and Match sheets. Set default table sizes for each
sheet'''''''''
For j = 2 To 4
If (j = 2) Or (j = 3) Then
rowCount = 5
colCount = 6
ElseIf (j = 4) Then
rowCount = 5
colCount = 9
End If
Application.ScreenUpdating = False 'Prevent screen flickering while doing the refresh
Set wrksht = ActiveWorkbook.Worksheets(j)
Set objListObj = wrksht.ListObjects 'Get list of tables objects from the current sheet
'''''''Iterate through the tables in the active worksheet''''''''''''''
For i = 1 To objListObj.Count
tableName = objListObj(i).Name
Set ActiveTable = wrksht.ListObjects(tableName)
On Error Resume Next
''''''For each table clear the contents and resize the table to default settings''''''''''''
With wrksht.ListObjects(i)
.DataBodyRange.Rows.Clear
.Range.Rows(rowCount & ":" & .Range.Rows.Count).Delete
.HeaderRowRange.Rows.ClearContents
.HeaderRowRange.Rows.Clear
.Range.Columns(colCount & ":" & .Range.Columns.Count).Delete
.Resize .Range.Resize(rowCount, colCount)
End With
wrksht.Columns("A:Z").AutoFit
Next i
Next j
ThisWorkbook.Worksheets(2).Activate '''set the active sheet to the sheet number 2
Application.ScreenUpdating = True 'Prevent screen flickering while doing the refresh
Exit Sub
'Error Handling
NoTableSelected:
MsgBox "There is no Table currently selected!", vbCritical
End Sub
Please help in resolving the issue.
If I execute as independent macro on click of the button, it works perfectly well.
I am going to post this as an "answer", since I think it may at least help, if not solve, your issue.
Clearing tables (list objects) via VBA code can be a little tricky, and I learned this hard way. I developed and have been using the below function for quite some time and it works like a charm. There are comments to explain the code in the function.
Sub clearTable(whichTable As ListObject)
With whichTable.DataBodyRange
'to trap for the bug where using 'xlCellTypeConstants' against a table with only 1 row and column will select all constants on the worksheet - can't explain more than that its a bug i noticed and so did others online
If .rows.count = 1 And .columns.count = 1 Then
If Not .Cells(1, 1).HasFormula Then .Cells(1, 1).ClearContents
Else
'my tables often have formulas that i don't want erased, but you can remove if needed
On Error Resume Next
.SpecialCells(xlCellTypeConstants).ClearContents
On Error GoTo 0
End If
'remove extra rows so table starts clean
Dim rowCount As Long
rowCount = .rows.count
If rowCount > 1 Then .rows("2:" & rowCount).Delete 'because you can't delete the first row of the table. it will always have 1 row
End With
End Sub
Call the procedure like this:
Dim lo as ListObject
For each lo in Worksheets(1).ListObjects
clearTable lo
next
Commented line to make my code work
.Range.Columns(colCount & ":" &
.Range.Columns.Count).Delete

Reset count of buttons so it starts from Button 1 again after all Buttons are deleted

I have inserted the following three buttons into my Excel spreadsheet:
I am using a German Excel version: Schaltfläche = Button
As you can see the buttons are counted by 1,2,3 and each additional button will get the next higher count number.
Now, I run the following VBA to delete all buttons:
Sub Delete_Buttons()
Sheet1.Select
Sheet1.Buttons.Delete
End Sub
After I run this VBA I insert the buttons again:
As you can see the buttons are counted starting from 3.
How can I reset the count of the buttons and make the count starting from 1 again?
or you could use a sub that "orders" the buttons text by their Left property
this example uses a SortedList object
Sub Rename_Buttons()
With CreateObject("System.Collections.SortedList")
Dim btn As Button
For Each btn In Sheet1.Buttons
.Add btn.Left, btn
Next
Dim j As Long
For j = 0 To .Count - 1
.GetValueList()(j).Text = "Button" & j + 1
Next
End With
End Sub
You could also use:
Sub create_new()
Dim counter As Long, MaxValue As Long, i As Long
Dim btn As Button, btnNew As Button
Dim FullBtnName As String
counter = 3
MaxValue = 0
'You could change sheet name
With ThisWorkbook.Worksheets("Sheet1")
'Loop all butons in the sheet
For Each btn In .Buttons
'Split the name to get its number
FullBtnName = btn.Name
If CInt(Split(FullBtnName, " ")(1)) > MaxValue Then
'Get the max number
MaxValue = CInt(Split(FullBtnName, " ")(1))
End If
Next btn
'Loop 3 times to create 3 shhets
For i = 1 To 3
Set btnNew = .Buttons.Add(.Range("A" & i + 5).Left, .Range("A" & i + 5).Top, .Range("A" & i + 5).Width, .Range("A" & i + 5).Height)
'Change button name & caption
With btnNew
.Caption = "Button " & MaxValue + i
.Name = "Button" & MaxValue + i
End With
Next i
End With
End Sub
The best way to solve this issue is:
Run the VBA to delete all buttons:
Sub Delete_Buttons()
Sheet1.Select
Sheet1.Buttons.Delete
End Sub
Save the file and close it.
Re-open the file and then insert the buttons again.
(Now it starts counting from Button 1 again)

Custom right-click menu - OnAction works straight away rather than on button-press

I'm creating a custom menu in Excel which consists of various sub-menus. It's for picking various machinery items and there's about 250 possible outcomes.
In any case, I've got the menu built and want it so that the .Caption is entered into the cell when the menu is used. I've put the .OnAction into the relevant buttons but, unfortunately, the .OnAction activates when the file is opened, not when the button is clicked. As such, all 250-odd .Captions are quickly entered into the same cell in quick succession.
Quick edit - the important bit is towards the bottom of the BuildMenus, where the .OnAction calls the function AddStuff. I know this is running on the Workbook_Activate which is why it runs straight away but everywhere else I've looked online does it the same way.
Private Sub Workbook_Activate()
BuildMenus
End Sub
Private Sub BuildMenus()
'Workbook_SheetBeforeRightClick(ByVal Sh As Object, ByVal Target As Range, Cancel As Boolean)
Dim AmountOfCats As Integer
Dim ThisIsMyCell As String
ThisIsMyCell = ActiveCell.Address
'this is where we would set the amount of categories. At the moment we'll have it as 15
AmountOfCats = 15
Dim cBut As CommandBarControl
Dim Cats As CommandBarControl
Dim SubCats As CommandBarControl
Dim MenuDesc As CommandBarButton
On Error Resume Next
With Application
.CommandBars("Cell").Controls("Pick Machinery/Plant...").Delete
End With
Set cBut = Application.CommandBars("Cell").Controls.Add(Type:=msoControlPopup, Temporary:=True)
cBut.Caption = "Pick Machinery/Plant.."
With cBut
.Caption = "Pick Machinery/Plant..."
.Style = msoButtonCaption
End With
Dim i As Integer
Dim j As Integer
Dim k As Integer
Dim SC As Integer
Dim AmountOfMenus As Integer
SC = 1
Dim MD As Integer
MD = 1
Dim MyCaption As String
For i = 0 To AmountOfCats - 1
Set Cats = cBut.Controls.Add(Type:=msoControlPopup, Temporary:=True)
Cats.Caption = Categories(i + 1)
Cats.Tag = i + 1
For j = 0 To (SubCatAmounts(i + 1) - 1)
Set SubCats = Cats.Controls.Add(Type:=msoControlPopup, Temporary:=True)
SubCats.Caption = SubCatArray(SC)
SubCats.Tag = j + 1
AmountOfMenus = MenuAmounts(SC)
For k = 0 To AmountOfMenus - 1
Set MenuDesc = SubCats.Controls.Add(Type:=msoControlButton)
With MenuDesc
.Caption = MenuArray(MD)
.Tag = MD
MyCaption = .Caption
.OnAction = AddStuff(MyCaption)
End With
MD = MD + 1
Next
SC = SC + 1
Next
Next
On Error GoTo 0
End Sub
Function AddStuff(Stuff As String)
Dim MyCell As String
MyCell = ActiveCell.Address
ActiveCell.Value = Stuff
End Function
OnAction expects a string value: instead you are calling your AddStuff sub while creating your menu...
.OnAction = "AddStuff """ & MyCaption & """"
is what you want (assuming I got my quotes right)
I was making a mistake with my AddStuff - I was calling it as a function when instead it should have been a macro (or a regular sub). A slight modification to Tim Williams' .OnAction code
MyButton.OnAction = "AddStuff(""" & MyButton.Caption & """)"
did the trick.

Resources