Excel VBA - Passing argument to a function - vba

I attempted to create an Excel function that will bold whatever range I tell it to in whatever form I request. Unfortunately, I've only had partial success in correctly passing the variable and obtaining this outcome. Of course, nobody likes a partial so can someone please let me know what I'm missing.
Sub Macro1()
On Error Resume Next
'Create & reset testing area.
Range("A1:C6").value = "A"
Range("A1:C6").Font.Bold = False
[b2].Select
'The two lines below call the function perfectly and the cells are bolded without issue
Text_bold ([a1])
Text_bold (Cells(2, 1))
'However, the party stops there as the following code errors out.
Text_bold ([b1].Address)
Text_bold (Selection)
Text_bold (Range("B3"))
'Similarly, the below fails as well...
Text_bold (Range("B4:C4"))
'And even less surprising, the following also refuses to assist in the endeavor...
Text_bold (Application.Union(Range("B5:C5"), Range("B6:C6")))
End Sub
Function Text_bold(x As Range)
'MsgBox VarType(x)
x.Font.Bold = True
End Function
Please help.

The parentheses around your function parameters are causing the problem. They are forcing the enclosed value to be evaluated before being passed as the function parameter, passing a Range.Value instead of Range object.
Sub Macro1()
On Error Resume Next
'Create & reset testing area.
Range("A1:C6").Value = "A"
Range("A1:C6").Font.Bold = False
[b2].Select
'The two lines below call the function perfectly and the cells are bolded without issue
Text_bold [a1]
Text_bold Cells(2, 1)
'However, the party stops there as the following code errors out.
Text_bold Range([C1].Address)
Text_bold Selection.Range
Text_bold Range("B3")
'Similarly, the below fails as well...
Text_bold Range("B4:C4")
'And even less surprising, the following also refuses to assist in the endeavor...
Text_bold Application.Union(Range("B5:C5"), Range("B6:C6"))
MsgBox "OK"
End Sub
If you really want to use parentheses, prefix your function with Call statement.
Call Text_bold(Application.Union(Range("B5:C5"), Range("B6:C6")))

In order to get more details about the issue you need to remove the statement
On Error Resume Next (aka On Error Hide All Bugs)
After I removed it I was able to determine the problems
The function (which should be a Sub because it doesn't return a value) is expecting a Range object: Text_bold(x As Range)
the line Text_bold ([b1].Address) is calling it incorrectly with parenthesis, and it is attempting to send as argument a string, not a range
all your calls to the function should be without brackets
Try this:
Sub Macro1()
'Create & reset testing area.
Range("A1:C6").Value = "A"
Range("A1:C6").Font.Bold = False
[b2].Select
Text_bold [a1]
Text_bold Cells(2, 1)
Text_bold [b1]
Text_bold Selection
Text_bold Range("B3")
Text_bold Range("B4:C4")
Text_bold Application.Union(Range("B5:C5"), Range("B6:C6"))
'A sub cannot return a value, a function can but it doesn't have to
'To return a value from the Text_bold function
Dim functionResponse As Boolean
functionResponse = Text_bold([B3]) '<- THIS is where you need to use brackets
MsgBox "Text in Cell [B3] is bold: " & functionResponse
End Sub
Function Text_bold(x As Range) As Boolean
x.Font.Bold = True
Text_bold = (x.Font.Bold = True) 'assign the return value to the function name
End Function

Related

How do I find out why I get an error when writing to an Excel cell with VBA?

I'm still fairly new to VBA and struggling with its limitations (and mine!). Here's my code:
Sub updateCache(CacheKey As String, CacheValue As Variant)
Dim DataCacheWorksheet As Worksheet, CacheRange As Range, Found As Variant, RowNum As Integer
Set DataCacheWorksheet = ThisWorkbook.Worksheets("DataCache")
Set CacheRange = DataCacheWorksheet.Range("A1:B999")
Set Found = CacheRange.Find(What:=CacheKey)
If Found Is Nothing Then
RowNum = CacheRange.Cells(Rows.Count, 2).End(xlUp).Row
DataCache.Add CacheKey, CacheValue
On Error Resume Next
DataCacheWorksheet.Cells(1, 1).Value = CacheKey
DataCacheWorksheet.Cells(1, 2).Value = CacheValue
Else
'Do other things
End If
End Sub
When I step through the code, Excel simply exits the sub at the line DataCacheWorksheet.Cells(1, 1).Value = CacheKey, with no error. So, two questions:
What's the bug that's preventing the value from being updated?
Why does Excel ignore my On Error command?
Edit: If I run the line in the IDE's "Immediate" box, I get the error "Run-time error '1004' Application-defined or object-defined error. I get the same error regardless of the value of CacheKey (I tried Empty, 1234 and "Hello").
Edit 2: If I modify the sub so that CacheKey and CacheValue are hardcoded and the reference to DataCache is removed, and then I run the sub standalone it works. So why doesn't it work when called from another function? Is it possible that Excel is locking cells while doing calculations?
Not sure if this applies, but you mentioned you were calling this macro from another function. If you are calling it from a function, depending on how you are calling it, that would explain your problem. For example, a worksheet function entered into a cell cannot modify another cell on the worksheet. And the attempt to do so will result in the macro merely exiting at that point, without throwing a VBA error.
How to work around this depends on specifics you have yet to share. Sometimes, worksheet event code can be useful.
Ok, wasn't about to write an answer, but there are 3 things you should modify in your code:
Found As Range and not As Variant
RowNum As Long in case it's a row after ~32K
To trap errors usually On Error Resume Next won't help you, it will just jump one line of code. You need to handle the error situation.
Modified Code
Sub updateCache(CacheKey As String, CacheValue As Variant)
Dim DataCacheWorksheet As Worksheet, CacheRange As Range, Found As Range, RowNum As Long ' < use Long instead of Integer
Set DataCacheWorksheet = ThisWorkbook.Worksheets("DataCache")
Set CacheRange = DataCacheWorksheet.Range("A1:B999")
Set Found = CacheRange.Find(What:=CacheKey)
If Found Is Nothing Then ' check if not found in cache (*Edit 1)
RowNum = CacheRange.Cells(Rows.Count, 2).End(xlUp).Row
DataCache.Add CacheKey, CacheValue ' I assume you have a `Dictionary somewhere
' On Error Resume Next <-- Remove this, not recommended to use
DataCacheWorksheet.Cells(1, 1).Value = CacheKey
DataCacheWorksheet.Cells(1, 2).Value = CacheValue
Else
'Do other things
End If
End Sub

Simple Error Handling for GoTo or Other

I'm attempting to create a macro that will allow a User to click a button and the excel sheet will either 1: Move to the next/previous sheet or 2: Copy existing sheet and then move to the copied sheet.
Currently I'm stuck dealing w/ a error handling situation and am not sure how to get around it. I'm not sure if I'm using the On Error correctly. Essentially I need it to go to my next sub if a page doesn't exist. If the page does exist, to simply ActiveSheet.Index + 1 then select.
Sub Function1()
On Error GoTo fixer
Sheets(ActiveSheet.Index + 1).Select
fixer:
Call Copier2
End Sub
Sub Copier2()
ActiveWorkbook.ActiveSheet.Copy _
After:=ActiveWorkbook.ActiveSheet
End Sub
Any help is greatly appreciated, I'm quite a novice at this stuff so don't be afraid to dumb it down for me.
Let's have some fun with this, to illustrate the mechanics.
First let's extract a method whose job is to activate a given worksheet. That method will return a Boolean value that indicates whether it succeeded or not. Because we want to return a value, this will be a Function procedure:
Private Function ActivateSheet(ByVal index As Long) As Boolean
Dim result As Boolean
On Error GoTo CleanFail
ActiveWorkbook.Sheets(index).Select
result = True
CleanExit:
ActivateSheet = result
Exit Function
CleanFail:
Err.Clear
result = False
Resume CleanExit
End Function
The "happy path" assigns result to True and then assigns the function's return value to result and then returns.
The "error path" jumps to CleanFail, which clears the error (likely some index out of bounds error), assigns result to False and then Resume CleanExit clears the error-handling state and resumes to CleanExit, which assigns the function's return value to result and then returns.
The macro can do this now:
Public Sub NavigateRight()
If Not ActivateSheet(ActiveSheet.Index + 1) Then
'copy the current sheet if there's no next sheet:
ActiveSheet.Copy After:=ActiveSheet
End If
End Sub
And we can also have this one:
Public Sub NavigateLeft()
If Not ActivateSheet(ActiveSheet.Index - 1) Then
'copy the current sheet if there's no previous sheet:
ActiveSheet.Copy Before:=ActiveSheet
End If
End Sub
Don't make procedures just for the sake of making procedures: use them to abstract concepts: a procedure like Copier2 doesn't really need to exist, it's just wrapping a single call against the Excel object model - better to inline it IMO.

Edge cases in IsNumeric- is this overthinking it

I have code which looks like this:
Select Case IsNumeric(somevariable)
Case True
Resume Next
Case False
Call notnum
Else
Call MyErrorHandler
End Select
Is this overthinking it? Is there a chance IsNumeric will return something other than True or False here or is this bad programming practice?
Don't need the else as it will be true or false however, just a note the Else should be Case Else (moot point though as you are about to delete it)
Based on this though I wouldn't use a case for only 2 options:
If IsNumeric(somevariable) then
Resume Next
Else
Call MyErrorHandler
End if
Edit: Here is how error checking works:
Sub SheetError()
Dim MySheet As String
On Error GoTo ErrorCheck
MySheet = ActiveSheet.name
Sheets.Add
ActiveSheet.name = MySheet
MsgBox "I continued the code"
ActiveSheet.name = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
MsgBox "I will never get to here in the code"
End
ErrorCheck:
If Err.Description = "Cannot rename a sheet to the same name as another sheet, a referenced object library or a workbook referenced by Visual Basic." Then
Resume Next
Else
MsgBox "Error I am not designed to deal with"
End If
End Sub
Copy and paste this module to your personal workbook or to a new workbook and run it, step through line by line using F8 to see how it is actually dealing with the error.
From OP's comment I'm not using my error handler. I want to do stuff with the hopefully numeric output
Sub demo()
Dim inputs As Variant
inputs = InputBox("Prompt", "Title", "Default")
If Not IsNumeric(inputs) Then
notnum
Else
' Do what you want with numeric input inside the Else
End If
' Maybe do more stuff irrespective of input
End Sub
Sub notnum()
' do not numeric stuff here
End Sub
Or if you want to keep prompting for numeric input until the users gets it right or cancels
Sub demo2()
Dim inputs As Variant
Do
inputs = InputBox("Enter something Numeric", "Title", "Default")
Loop Until IsNumeric(inputs) Or inputs = vbNullString
If Not inputs = vbNullString Then
' Do wht you want with numeric input inside the Else
End If
' Maybe do more stuff irrespective of input
End Sub
Input box can have different type of input validation. Try this
something = Application.InputBox("Pls Insert the Number", Type:=1)
If something = False Then Exit Sub
'Type:=0 A formula
'Type:=1 A number
'Type:=2 Text (a string)
'Type:=4 A logical value (True or False)
'Type:=8 A cell reference, as a Range object
'Type:=16 An error value, such as #N/A
'Type:=64 An array of values

Simple Excel VB Cell value change - Error #1004: Application-defind or object-defined error

i've been trying to fix this error for hours now and i can't seem to see what it is that i am doing wrong here.
My macro is as it follows:
Function testValueChange(cell As Range)
On Error GoTo ErrorHandler
Dim testValue As String
testValue = Range(cell.Address)
MsgBox testValue
Range("Q2") = "new value"
Exit Function
ErrorHandler:
MsgBox "Error #" & Err & " : " & Error(Err)
End Function
I am able to retrieve the "testValue" correctly and it is displayed on the MsgBox. However, when i try to set a new value to the cell "Q2", i get the error "Error #1004: Application-defined or object-defined error."
There are several entries for that error over the web but none of them in this situation when i'm trying to simply set a String value to an empty cell!
The cell "Q2" is empty, i've chosen that cell specifically because it is not merged, nor referenced on another function or anything.
Thanks in advance for all the help!
I'm on Linux OS right now so I can't test this in Excel, but try setting a range object first then assigning the value with .value property. i.e:
Dim RNG as Range
Set RNG = Range("Q2")
RNG.value = "new value"
If you not interested in a return then shahkalpesh has the answer, but if your interested in monitoring the macro silently when run from somewhere else then return boolean like so.
Function testValueChange(cell As Range) as boolean
TestValueChange = TRUE
On Error GoTo ErrorHandler
Dim testValue As String
testValue = Range(cell.Address)
MsgBox testValue
Range("Q2") = "new value"
Exit Function
ErrorHandler:
testValueChange = FALSE
End Function
Usage in a vba sub.
If testValueChange then
'process worked
Else
'process didn't work
End if
Unless testvalue was ment to be testValueChange and returned as a string?
You cannot assign values to cells, or change them in any manner, when calling from a Function (or from a call chain that has started by a Function). Only a Sub will work. The only exception is that function value can be assigned to the cell containing the function reference

Store #VALUE! #NUM! #REF! in variable

So a simple version of what I'm trying to do.
Say I know there is an error in cell(1,1), furthermore I know it is either #num!, #ref! or #value!, I want to be able to store the respective error message in a variable, so I can print it to a different sheet.
This is what I tried and it clearly failed.
Sub FindAndPrintErrors
dim Store as string
If IsError(Range("A1"))) = True Then
Store = Range("A1").value 'it breaks here'
end if
range("B1") = Store
end sub
I know I can do this but I wonder if there is a better way.
Sub FindAndPrintErrors2
dim Store
If IsError(Range("A1"))) = True Then
temp = Range("A1").value 'it breaks here'
if temp = "error 2029" then
store = "#num!"
' and so on'
end if
range("B1") = Store
end sub
Instead of .value try .Text. This can be stored in your variable.
?cells(1,2).text
#N/A
?cells(1,2).value
Error 2042
?cells(2,2).text
#REF!
?cells(2,2).value
Error 2023
Just make your variable of type Variant. Then you can put in anything that goes in a cell, including error values.
To elaborate a little, your code could look like this:
Public Sub copyFromCellIfError()
Dim v
v = [q42]
If IsError(v) Then
[z99] = v
End If
End Sub
That's assuming you want the actual error value copied, and not a string representation of it.