Excel VB newbie. I know I must be missing something very simple. How do I get before_save event to work with more than one worksheet? Only one needs code. I have it in ThisWorkbook. It works if I only have one sheet in my workbook.
After seeing the comment that it doesn't matter if there's more than one worksheet I looked again at my code. I fixed the code and now the BeforeSave event will trigger and not save until all conditions are met like it's supposed to.
The BeforeSave event triggers if I put it in ThisWorkbook. But if I put it in Sheet1 and call the sub in ThisWorkbook, it still runs the sub like it's supposed to but doesn't prevent it from saving. Hoping this makes sense. I know the code is messy so please bear with me.
Sheet1:
Sub checkSheet1()
Dim cellCount As Variant, findEmpty As String, Counter%
allYellowCellsArray = Array(Range("C6"), Range("C7"), Range("C8"), Range("C9"), Range("C18"), Range("C19"), Range("C20"), Range("C21"), Range("C22"), Range("C29"), Range("C30"), Range("C31"), Range("C32"), Range("C33"), Range("C42"), Range("C62"))
noDateYellowCellsArray = Array(Range("C6"), Range("C7"), Range("C8"), Range("C9"), Range("C18"), Range("C19"), Range("C20"), Range("C21"), Range("C22"), Range("C29"), Range("C30"), Range("C31"), Range("C32"), Range("C33"), Range("C42"))
emptyCell = ""
Counter = 0
Debug.Print vbNewLine & "List the values of each cell in the array:"
'count number of yellow/empty cells
For Each cellCount In allYellowCellsArray
Debug.Print cellCount.Address() & " value is " & cellCount & " and color is " & cellCount.DisplayFormat.Interior.Color
If cellCount = emptyCell Then
Counter = Counter + 1
End If
Next
'If-Then statements to alert how many yellow cells are still empty.
If Counter >= 1 Then
MsgBox "(" & Counter & ") Mandatory Cells Have Not Been Completed", vbExclamation, "Missing Information"
'cellCount = "Enter Missing Information"
End If
For Each cellCount In noDateYellowCellsArray
If cellCount.Value = "" Then
cellCount.Value = "Enter Missing Information"
End If
Next
'Evaluate all yellow cells to prevent empty cells and make sure the set values have been changed ----
Dim cellValue As Variant
Dim fieldsAreYellow As Boolean
fieldsAreYellow = True
Dim redCellColor As Boolean
redCellColor = True
Dim cellCellColor As Variant
Debug.Print vbNewLine & "List cells that are red:"
For Each cellCellColor In allYellowCellsArray 'check for red cells
If cellCellColor.DisplayFormat.Interior.Color = 255 Then 'if cell background color is red
redCellColor = True
Debug.Print cellCellColor.Address() & " is " & cellCellColor.DisplayFormat.Interior.Color
Cancel = True
End If
If redCellColor = False Then
MsgBox "There are no more red cells."
Cancel = True
End If
Next cellCellColor
Dim cellCountRedCells As Variant, redCellCounter%
redCellCounter = 0
For Each cellCountRedCells In allYellowCellsArray
If cellCountRedCells.DisplayFormat.Interior.Color = 255 Then 'red
redCellCounter = redCellCounter + 1
Debug.Print "redCellCounter is " & redCellCounter
'MsgBox "redCellCounter is " & redCellCounter
End If
Next
Debug.Print "redCellCounter is " & redCellCounter
'Check to see if cells in array have been changed
Debug.Print vbNewLine & "List the current background color of the first non-numeric cell that stopped the loop:"
For Each cellValue In allYellowCellsArray
If cellValue = "Enter Missing Information" Then
Debug.Print vbNewLine & cellValue
fieldsAreYellow = False
Debug.Print cellValue.Address() & " color is " & cellValue.DisplayFormat.Interior.Color
MsgBox "Check all of your cells for correct information." & vbNewLine & "There are still (" & redCellCounter & ") red cells.", vbCritical, "SAVE CANCELLED"
Cancel = True ' ** prevent the file from being saved **
Exit For
End If
Next cellValue
'Final check
If (fieldsAreYellow = True) And (redCellCounter = 0) Then
MsgBox "The document will be saved." & vbNewLine & "Remember the naming convention." & vbNewLine & "Customer_PIP Seal Calculator_Part Number rev#_Part Name_DDMMYY", vbInformation, "Good to Go!"
Cancel = False 'allow save
Else:
MsgBox "This file will not save until all of the cells have correct information.", vbCritical, "SAVE CANCELLED"
Cancel = True 'cancel save
End If
End Sub
ThisWorkbook:
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Call Sheet1.checkSheet1
End Sub
I created a new excel file and tested this event. It works perfectly on both sheets.
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
MsgBox "OK"
End Sub
I suggest to try this on a new file and then copy your code to the new file.
In order to make the event was as you need, the called Sub must be transformed in a Function returning Boolean.
The event code should look like this:
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Cancel = Sheet1.checkSheet1
End Sub
And the called function, like this:
Public Function checkSheet1() As Boolean
If 1 = 1 Then
MsgBox "The saving cannot take place..."
checkSheet1 = True 'instead of Cancel = True in the Sub
Else
checkSheet1 = False
End If
End Function
You must adapt your code to finally return something like checkSheet1 = Cancel. But take care to properly declare Dim Cancel as Boolean...
If something unclear, please, do not hesitate to ask for clarifications. If you need me to transform your existing Sub, I can do it, but I think it is better for you do do that, in order to understand the meaning and learn...
Loop Through Worksheets In BeforeSave
Option Explicit
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
' Create a list of worksheet names.
Const wsList As String = "Sheet1,Sheet2,Sheet3"
Dim nms() As String ' Declare an array of type 'String'.
nms = Split(wsList, ",") ' Write the list to the array.
Dim ws As Worksheet ' Declare a worksheet variable.
Dim n As Long ' Declare a 'counter' variable of type 'Long'.
' Loop through the elements (names) in the array.
For n = 0 To UBound(nms)
' Define current worksheet.
Set ws = ThisWorkbook.Worksheets(nms(n))
' Do something, e.g. write some text to cell 'A1' and autofit column 'A'.
ws.Range("A1").Value = "Testing worksheet '" & ws.Name & "'."
ws.Columns("A").AutoFit
Next n
End Sub
Related
I'm trying to use this answer, but set it up where the Function is in another xlam workbook.
Example:
This works from remote workbook:
Sub Test()
FuncName = "#MyFunctionkClick()"
MyVal = "TestVal"
Range("A1").Value = MyVal
Range("A1").Formula = "=HYPERLINK(""" & FuncName & """, """ & Range("A1").Value & """)"
End Sub
Sub TestTwo()
Application.Run ("'remotewb.xlam'!MyFunctionkClick")
End Sub
Function MyFunctionkClick()
Set MyFunctionkClick = Selection 'This is required for the link to work properly
MsgBox "The clicked cell addres is " & Selection.Row
End Function
But I tried this without luck:
Sub Test()
'Application.Run ("'Master Calc with Macro.xlsm'!SummarizeMaster")
'Application.Run ("'remotewb.xlam'!testremote")
'Application.Run ("'remotewb.xlam'!#MyFunctionkClick()")
'Application.Run ("'remotewb.xlam'!MyFunctionkClick") ' When calling from Remote WB it errored if I used ()
'Range("A1:A5").Formula = "=HYPERLINK(""#MyFunctionkClick()"", ""Run a function..."")"
' Range("A1:A5").Formula = "=HYPERLINK(""#MyFunctionkClick()"", ""Run a function..."")"
Range("A1:A5").Formula = "=HYPERLINK(""[remotewb.xlam]!MyFunctionkClick"", ""Run a function..."")"
'Range("A1").Formula = "=HYPERLINK(""Application.Run (" 'remotewb.xlam'!MyFunctionkClick")"", ""Run a function..."")"
End Sub
Please, try the next scenario:
Create a function in that the other workbook. For testing reasons, it should be good to place it in "Personal.xlsb", as I am trying it:
Function GiveMeFive(x As Long, y As Long) As Long
Debug.Print "In Personal.xlsb code: " & x + y 'not important, ONLY TO SEE IT WORKING with parameters in Immediate Window
GiveMeFive = 5 'it can be calculated, but look to the function name :)
End Function
Create the (necessary) hyperlink in the active sheet (it can be created in any sheet):
Sub TestCalFunctionHyp()
Dim FuncName As String, myVal As String
FuncName = "#MyFunctionHyp()"
myVal = "Call external Function (parameters):4|3" 'just to see how to call it with parameters
Range("A1").Value = myVal
Range("A1").Formula = "=HYPERLINK(""" & FuncName & """, """ & Range("A1").Value & """)"
End Sub
How the (directly) called (by hyperlink) function should look:
Function MyFunctionHyp()
Dim arr
Set MyFunctionHyp = Selection
arr = Split(Split(Selection.Value, ":")(1), "|")
TestTwo CLng(arr(0)), CLng(arr(1)) 'calling the sub calling the one in the other wb
End Function
The sub calling the function in the other workbook should look like:
Sub TestTwo(arg1 As Long, arg2 As Long)
Dim x As Long
x = Run("'C:\Users\YourUser\AppData\Roaming\Microsoft\Excel\XLSTART\PERSONAL.XLSB'!GiveMeFive", arg1, arg2)
Debug.Print "Received from called function: " & x
End Sub
The function calls the function using its full path, only due to the fact that, in case the workbook keeping the function is not open, it will open it...
Please, take care to adapt the path in order to use your real YourUser...
I would like to receive some feedback after testing it. If something not clear enough, do not hesitate to ask for clarifications.
I have an excel sheet which contains two columns ( the column A contains Names and the column B contains Number Phones ).
I have created a comboBox with the list of names and I want to select a name from this comboBox and then update its corresponding phone number. I tried this code but it does not work.
Sub UpdateNumber()
Dim Ans As String, Index As Integer
Ans = InputBox("What is " & NameForm.ComboBox1.Value & " 's new phone number?")
If Ans <> "" Then
Index = NameForm.ComboBox1.ListIndex
Sheets("Names").Range("A" & Index).Offset(0, 1).Value = Ans
End If
End Sub
Could someone help me, please ?
try below code
Sub UpdateNumber(Optional boxShow As Boolean = True)
Dim Ans As String, Index As Integer
If boxShow = false Then exit sub
Ans = InputBox("What is " & NameForm.ComboBox1.Value & " 's new phone number?")
If Ans <> "" Then
Index = NameForm.ComboBox1.ListIndex
Sheets("Names").Range("A" & Index).Offset(0, 1).Value = Ans
End If
End Sub
sub btn_onclick()
boxShow True 'no msgbox
boxShow False 'with msgbox
boxShow 'no msgbx
end sub
found there
Code to add a sheet and rename that sheet from the user.
Sub tenloops1()
Worksheets.Add
Sheets(ActiveSheet.Name).Select = InputBox("Enter Sheet Name")
End Sub
I would do this slightly different to minimize the error that can happen when you are adding and naming a sheet.
Logic
You have to take care of the following
The sheet name is valid. i.e It is not an empty string or it is not more than 31 characters. Neither it should contain the characters /,\,[,],*,?,:
There should not be a sheet already with that name.
Error Handling On Error GoTo... to catch any other errors that may rise.
Code
Option Explicit
Sub Sample()
Dim Ret As Variant
On Error GoTo Whoa
'~~> Get user input
Ret = InputBox("Enter a valid sheet name")
If Ret = "" Then Exit Sub
'~~> Check if the sheet name is valid
If IsValidSheetName(Ret) = False Then
MsgBox "The sheet name cannot have length more than 31 " & _
"characters. Neither it can contain the characters /,\,[,],*,?,:"
Exit Sub
End If
'~~> Check if there is no other sheet with that name
If DoesSheetExist(Ret) Then
MsgBox "There is already a sheet with that name. Enter a new name"
Exit Sub
End If
'~~> Add the sheet and name it in one go
ThisWorkbook.Sheets.Add.Name = Ret
Exit Sub
Whoa:
MsgBox Err.Description
End Sub
Private Function IsValidSheetName(userinput As Variant) As Boolean
Dim IllegalChars As Variant
Dim i As Long
IllegalChars = Array("/", "\", "[", "]", "*", "?", ":")
If Len(userinput) > 31 Then Exit Function
For i = LBound(IllegalChars) To UBound(IllegalChars)
If InStr(userinput, (IllegalChars(i))) > 0 Then Exit Function
Next i
IsValidSheetName = True
End Function
Private Function DoesSheetExist(userinput As Variant) As Boolean
Dim wsh As Worksheet
On Error Resume Next
Set wsh = ThisWorkbook.Sheets(userinput)
On Error GoTo 0
If Not wsh Is Nothing Then DoesSheetExist = True
End Function
ActiveSheet.Name = InputBox("Enter Sheet Name")
and make sure the user does not enter any invalid characters.
You can also do it this way:
Sub tenloops1()
Dim ws As Worksheet
Dim sName as String
sName = InputBox("Enter Sheet Name")
' also may want to check for sName being a valid sheet name here
If Len(sName) > 0 Then
Set ws = Worksheets.Add()
ws.Name = sName
Else
' user clicked cancel
End If
End Sub
Structured example call
[1] Get user input
[2] Repeat in a loop if a sheet name is invalid or exists already
[3] Add sheet and name it
This approach doesn't pretend to be the best one, but you can profit from studying it, as it demonstrates an alternative approach in help functions b) and c)
Sub ExampleCall()
'~~~~~~~~~~~~~~~~~~~~~~~~~
'[1] Get user input
'~~~~~~~~~~~~~~~~~~~~~~~~~
Dim Sheetname As String
Sheetname = InputBox("Enter a valid sheet name or leave blank to exit.")
If Sheetname = vbNullString Then Exit Sub
'~~~~~~~~~~~~~~~~~~~~~~~~~
'[2] Check sheet validity (via help function SheetError()
'~~~~~~~~~~~~~~~~~~~~~~~~~
Do While SheetError(Sheetname, ThisWorkbook)
If Sheetname = vbNullString Then Exit Sub
Sheetname = InputBox("Enter a valid sheet name")
If StrPtr(Sheetname) = 0 Then MsgBox "Cancelled by user.": Exit Sub
Loop
'~~~~~~~~~~~~~~~~~~~~~~~~~
'[3] Add sheet and name it
'~~~~~~~~~~~~~~~~~~~~~~~~~
ThisWorkbook.Sheets.Add.Name = Sheetname
End Sub
Function SheetError()
Controls user inputs of sheet names in ExampleCall and uses two help functions b) and c)
Private Function SheetError(Sheetname As String, wb As Workbook, Optional ShowMsg As Boolean = True) As Boolean
'Purpose: check for possible sheet errors; return True if so
Dim msg As String
'a) Check sheet length
If Not Len(Sheetname) Or Len(Sheetname) > 31 Then
If ShowMsg Then msg = "The sheet name cannot be empty or have more than 31 characters." & vbNewLine
SheetError = True
End If
'b) Check if sheet already exists
If SheetExists(Sheetname, wb) Then
If ShowMsg Then msg = msg & "There is already a sheet with that name. Enter a new name!" & vbNewLine
SheetError = True
End If
'c) Check if the sheet name is valid
If IsValidSheetName(Sheetname) = False Then
If ShowMsg Then msg = msg & "The sheet name must not contain /,\,[,],*,?,: characters."
SheetError = True
End If
If SheetError And ShowMsg Then MsgBox msg, vbExclamation, "Sheet Error"
End Function
Help function b) SheetExists()
Allows a one line check:
Private Function SheetExists(Sheetname As String, wb As Workbook) As Boolean
'Purp.: check if sheet exists
'Date: 2021-03-08
'Auth.: https://stackoverflow.com/users/6460297/t-m
SheetExists = Not IsError(Application.Evaluate("'" & wb.Path & "\[" & wb.Name & "]" & Sheetname & "'!A1"))
End Function
Help function c) IsValidSheetName()
Compares a byte array (by) derived from sheetname characters with an array of illegal characters (illegalAsc) via Application.Match().
Note that
Match() isn't restricted to only 1 array argument!
(shows the 1-based positions of illegal occurrencies, non-findings error)
Count() ignores error elements, so it suffices to detect at least one occurrence of an element
The illegal characters not allowed in sheet names are /\[]*?:
Private Function IsValidSheetName(Sheetname As String) As Boolean
'Auth.: https://stackoverflow.com/users/6460297/t-m
'Purp.: check for valid sheet name
'Date: 2021-03-08
'a) length cannot exceed 31 characters
If Len(Sheetname) > 31 Then Exit Function
'b) define illegal character codes
Dim IllegalAsc As Variant
IllegalAsc = Array(47, 92, 91, 93, 42, 63, 58) ' i.e. /\[]*?:
'c) convert name to byte array
Dim by() As Byte: by = Sheetname
'd) return true if no counted occurrencies of illegal matches
With Application
IsValidSheetName = .Count(.Match(IllegalAsc, by, 0)) = 0 ' edited due to comment
End With
End Function
I am new to VBA and hope the code does what you want.
Sub tenloops1()
Worksheets.Add
ActiveSheet.Name = InputBox("Enter Sheet Name")
End Sub
I'm trying to avoid Event loops by disabling Events at crucial points. However, it doesn't always work. For instance, this code for a Combo box:
Private Sub TempComboS_Change()
Dim e
e = Application.EnableEvents
Application.EnableEvents = False
'
Application.EnableEvents = e
End Sub
The blank line is where the useful code goes; as it stands it obviously doesn't do anything. However, when I run it this way (with the blank line), it reaches "End Sub", then it goes back to the beginning and runs again. (This would make the useful code run twice).
Why is this happening?
EDIT: To clarify for the folks who've been helping me.
I have a macro that opens the dropdown list of the Combo box, activates it, then ends. It works properly. When I select an item from the open list, the Change event runs. This is the current version of the change event:
Private Sub TempComboS_Change()
End Sub
I put a breakpoint on the Private Sub line. It shows that this Change event runs, then runs again. I suspect that it has been doing this all along, and I noticed it now because I need to add code here.
I have no class modules or userforms. The controls are on a worksheet.
I'm going to try the "Run Once" suggestion, and I'll let you know if it works.
I tried the "Run Once" code you suggested. It sort of works, but I seem to have a bigger issue. When I select a drop-down list from a data-validated cell, the TempComboS_Change event triggers -- but not only didn't I touch this combo box, the cell isn't the LinkedCell for the combo box. In other words, it seems to be triggering by actions unconnected to the combo box!
Got to find out about that Call Stack thing...
Here is a bit of code to help investigate "sequence of events" issues
In a Standard Module
Public Enum eNewLine
No
Before
After
Both
End Enum
Public Function timeStamp(Optional d As Double = 0, Optional newLine As eNewLine = No, Optional Indent As Long = 0, _
Optional Caller As String, Optional Context As String, Optional message As String) As String
Dim errorMessage As String
If Err.number <> 0 Then
errorMessage = "ERROR: " & Err.number & ": " & Err.Description
Err.Clear
End If
If d = 0 Then d = Time
With Application.WorksheetFunction
timeStamp = .Text(Hour(d), "00") & ":" & .Text(Minute(d), "00") & ":" & .Text(Second(d), "00") & ":" & .rept(Chr(9), Indent)
End With
If Len(Caller) <> 0 Then timeStamp = timeStamp & Chr(9) & Caller
If Len(Context) <> 0 Then timeStamp = timeStamp & ": " & Chr(9) & Context
If Len(message) <> 0 Then timeStamp = timeStamp & ": " & Chr(9) & message
Select Case newLine
Case Before
timeStamp = Chr(10) & timeStamp
Case After
timeStamp = timeStamp & Chr(10)
Case Both
timeStamp = Chr(10) & timeStamp & Chr(10)
Case Else
End Select
If Len(errorMessage) <> 0 Then
timeStamp = timeStamp & Chr(9) & errorMessage
End If
End Function
At the top of each Module
'Module level Trace Hearder
Const debugEvents as Boolean = True
Const cModuleName As String = "myModuleName"
Const cModuleIndent As Long = 1
You can assign a module level indent for each module to organise the hierarchy an make it easy to understand.
In each Sub or Function (or property if you need)...
sub mySubName()
Const cMyName As String = "mySubName"
If debugEvents Then Debug.Print timeStamp(NewLine:=Before,Indent:=cModuleIndent, Caller:=cModuleName, Context:=cMyName, Message:="Start")
'Do stuff
If debugEvents Then Debug.Print timeStamp(NewLine:=After,Indent:=cModuleIndent, Caller:=cModuleName, Context:=cMyName, Message:="End")
End Sub
...Or you can use Me.Name for the Context if its a form or a sheet etc. and you can put whatever message or variable values you like in the Message.
You can also use a Timer (eg MicroTimer) and put the result in the Message section.
Here is an example output:
15:54:07: Roll-Up Select: Worksheet_Activate: Start: 3.24591834214516E-03
15:54:07: cDataViewSheet: Class_Initialize: Start
15:54:07: cRevealTarget: Class_Initialize: START
15:54:07: cRevealTarget: Class_Initialize: END
15:54:09: cDataViewSheet: startTimer: : START
15:54:09: cDataViewSheet: startTimer: init Timer
15:54:09: cOnTime: Class_Initialize
15:54:09: cOnTime: Let PulseTime: Inheret PulseTime from host sheet
15:54:09: cDataViewSheet: startTimer: : END
15:54:09: Roll-Up Select: Worksheet_Activate: END: 1.38736216780671
Private Sub cmbOrder_Change()
If cmbOrder = "" Then Exit Sub
Dim arr As Variant, maxorder As Integer
arr = Range("rngOrder")
maxorder = WorksheetFunction.Max(arr)
Dim errmsg As String, err As Boolean
err = False
errmsg = "This value must be a whole number between 1 and " & maxorder + 1
Dim v As Variant
v = cmbOrder.Value
If IsNumeric(v) = False Or (IsNumeric(v) = True And (v > maxorder + 1) Or v < 1)
Then
MsgBox errmsg
cmbOrder = ""
err = False
Else
txtOrder.Value = cmbOrder.Value
End If
End Sub
A bit late to the party but the problem of code repetition can be shown here in similar circumstances. Remove the first line of code and any error messages are dished out twice. This is because of the line that clears the ComboBox that is regarded as a change and picks up another error as null input is an error! May help someone with similar issue.
The Combobox_Change() will fire whenever there is a change in the combobox. For example
Option Explicit
Private Sub UserForm_Initialize()
ComboBox1.AddItem "Bah Blah"
End Sub
Private Sub CommandButton1_Click()
'~~> If something is selected in the combo then
'~~> this line will cause ComboBox1_Change to fire
ComboBox1.Clear
End Sub
Private Sub ComboBox1_Change()
MsgBox "A"
End Sub
So if you load the userform and select an item ComboBox1_Change will fire. You then use the commanbutton to clear the combo the ComboBox1_Change will again fire.
There is one more scenario when the change will again fire. When you change the combobox from the ComboBox1_Change event itself. Here is an example. And I believe this is what is happening in your case.
Scenario 1
Private Sub UserForm_Initialize()
ComboBox1.AddItem "Bah Blah"
End Sub
Private Sub ComboBox1_Change()
MsgBox "A"
ComboBox1.Clear
End Sub
Scenario 2
Private Sub UserForm_Initialize()
ComboBox1.AddItem "Bah Blah"
ComboBox1.AddItem "Bah Blah Blah"
End Sub
Private Sub ComboBox1_Change()
MsgBox "A"
ComboBox1.ListIndex = 1
End Sub
In the first scenario you can getaway with
Private Sub UserForm_Initialize()
ComboBox1.AddItem "Bah Blah"
End Sub
Private Sub ComboBox1_Change()
If ComboBox1 <> "" Then
MsgBox "A"
End If
End Sub
In the 2nd Scenario, you can use something like this
Dim boolRunOnce As Boolean
Private Sub UserForm_Initialize()
ComboBox1.AddItem "Bah Blah"
ComboBox1.AddItem "Bah Blah Blah"
End Sub
Private Sub ComboBox1_Change()
If boolRunOnce = False Then
MsgBox "A"
boolRunOnce = True
ComboBox1.ListIndex = 1
Else
boolRunOnce = False
End If
End Sub
I have a group box with option buttons in it and I need to find out which one of them is selected in VBA. I have been browsing MSDN for hours now and I can't find a solution.
There has to be a way to find the selected option button. Possibly find the group by name and for-each through each option button?
Here's what seems to be a working solution.
(Nod to KazJaw for Dim ... As OptionButton. this seems to be the key to get .GroupBox to work)
Function WhichOption(shpGroupBox As Shape) As OptionButton
Dim shp As OptionButton
Dim shpOptionGB As GroupBox
Dim gb As GroupBox
If shpGroupBox.FormControlType <> xlGroupBox Then Exit Function
Set gb = shpGroupBox.DrawingObject
For Each shp In shpGroupBox.Parent.OptionButtons
Set shpOptionGB = shp.GroupBox
If Not shpOptionGB Is Nothing Then
If shpOptionGB.Name = gb.Name Then
If shp.Value = 1 Then
Set WhichOption = shp
Exit Function
End If
End If
End If
Next
End Function
Use it like this
Sub test()
Dim shpOpt As OptionButton
Set shpOpt = WhichOption(Worksheets("Sheet1").Shapes("Group Box 1"))
Debug.Print shpOpt.Name
End Sub
If you really need to check OptionButton which are grouped (Grouped in the way we group any type of shape) you could go with this code:
Sub Grouped_into_UnitType()
Dim i!
'grouped into 'UnitType' Shape
For i = 1 To ActiveSheet.Shapes("UnitType").GroupItems.Count
With ActiveSheet.Shapes("UnitType").GroupItems(i).ControlFormat
If .Value = 1 Then
MsgBox "Chosen item: " & i
End If
End With
Next i
End Sub
Edit having in mind the following picture the code above will solve the problem if we have Option Buttons which are group in the way we group any Shapes placed in the sheet.
The code under the picture will find which option button is selected if they are located within GroupBox. Code check the name of the group in which OptionButton is located.
Important Note! the code below didn't work until I switched Excel off and run it again.
Sub Grouped_into_GroupBox_UnitType()
Dim OB As OptionButton
For Each OB In ActiveSheet.OptionButtons
'check if grouped into 'UnitType' Shape
If OB.GroupBox.Name = "UnitType" Then
If OB.Value = 1 Then
MsgBox "Chosen item: " & OB.Name & _
vbNewLine & _
"Alt text: " & OB.ShapeRange.AlternativeText
End If
End If
Next
End Sub
Lets say you have two standard option buttons:
To check if its "on" use:
Dim opt As Shape
Set opt = Worksheets("Sheet1").Shapes("Option Button 1")
If opt.ControlFormat.Value = xlOn Then
Debug.Print "option is ""on"" value of 1"
Else
Debug.Print "option is ""off"" value of -4146"
End If
To get its alternat text use:
Debug.Print "Alternate Text is: " & opt.AlternativeText
For a large amount of options the "FormControlType" property can be used:
Dim s as Shape
For Each s In Worksheets("Sheet1").Shapes
If s.FormControlType = xlOptionButton Then
If s.ControlFormat.Value = xlOn Then
Debug.Print "option is ""on"" value of 1"
Else
Debug.Print "option is ""off"" value of -4146"
End If
Debug.Print "Alternate Text is: " & s.AlternativeText
End If
Next
If you wanted a particular group:
Dim s As Shape, o
For Each s In Worksheets("Sheet1").Shapes
If s.FormControlType = xlOptionButton Then
Set o = s.OLEFormat.Object
If o.GroupBox.Name = "Group Box 3" Then
If s.ControlFormat.Value = xlOn Then
Debug.Print "Option is ""on"" value of 1"
Else
Debug.Print "Option is ""off"" value of -4146"
End If
Debug.Print "Alternate Text is: " & s.AlternativeText
Debug.Print "Group: " & o.GroupBox.Name
End If
Set o = Nothing
End If
Next