Do nothing in vba - vba

Is there an equivalent to python "pass" in VBA to simply do nothing in the code?
for example:
For Each ws In ThisWorkbook.Sheets
If ws.Name = "Navy Reqs" Then
ws.Select
nReqs = get_num_rows
Cells(1, 1).Select
If ActiveSheet.AutoFilterMode Then Cells.AutoFilter
Selection.AutoFilter
ElseIf ws.Name = "temp" Then
pass
Else
ws.Select
nShips = get_num_rows
End If
Next
I get an error here that pass is not defined. Thanks.

just remove pass and re run the code. VBA will be happy to accept that I believe

Don't include any statements:
Sub qwerty()
If 1 = 3 Then
Else
MsgBox "1 does not equal 3"
End If
End Sub

Just leave it blank. You can also use a Select statement, it's easier to read.
For Each ws In ThisWorkbook.Sheets
Select Case ws.Name
Case "Navy Reqs":
'...
Case "temp":
'do nothing
Case Else:
'...
End Select
Next

Write code that does what it says, and says what it does.
For Each ws In ThisWorkbook.Sheets
If ws.Name = "Navy Reqs" Then
ws.Select
nReqs = get_num_rows
Cells(1, 1).Select
If ActiveSheet.AutoFilterMode Then Cells.AutoFilter
Selection.AutoFilter
Else If ws.Name <> "temp" Then
ws.Select
nShips = get_num_rows
End If
Next
That's all you need. An instruction that means "here's some useless code" does not exist in VBA.
You want comments that say why, not what - a comment that says 'do nothing is the exact opposite of that. Don't write no-op code, it's pure noise.
Assuming Python's pass works like C#'s continue statement and skips to the next iteration, then the VBA equivalent is the one and only legitimate use of a GoTo jump:
For ...
If ... Then GoTo Skip
...
Skip:
Next

This code shows an IF test that keeps searching unless it gets a match.
Function EXCAT(Desc)
Dim txt() As String
' Split the string at the space characters.
txt() = Split(Desc)
For i = 0 To UBound(txt)
EXCAT = Application.VLookup(txt(i), Worksheets("Sheet1").Range("Dept"), 2, False)
If IsError(EXCAT) Then Else Exit Function
Next
' watch this space for composite word seach
EXCAT = "- - tba - -"
End Function

I coded in COBOL for many years and the equivalent 'do nothing' statement is NEXT SENTENCE.
In VBA, I find myself creating a dummy variable (sometimes a global) dim dummy as integer and then when I need that 'do nothing' action in an If..Then..Else I put in a line of code: dummy = 0.

This is actually a legit question. I want to run a debug routine that stops when a certain critical value is, say, 8, i.e., set break point at x = 8 and then step through line by line. So the following construct is useful:
Select Case x
Case 21
'do nothing
Case 8
'do nothing
Case 14
'do nothing
Case 9
'do nothing
End Select
Since you can't put a break point on a comment, an actual statement is needed.
You also can't put a break point on the Case statements because that gets executed every time.
Obviously anything here is OK e.g., x=x, but it would be nice to have something formal like pass.
But I've been using x=x.

Most languages have a "null" or "empty" statement like Python's pass. These statements have evolved because they are practically useful, and in some grammars, necessary. For example, as others have suggested, null statements can serve as side-effect free anchors for a debugger.
I use:
Debug.Assert True
Note that if your use case is to set a conditional breakpoint, you may find Stop more useful, as in:
If targetShape.Type = msoAutoShape Then
Stop
End

Related

VBA How to add cell values without looping

I have read about Application.WorksheetFunction.Sum but I was wondering if there is a specific method or property of the Range-object that does the job. I have looked into Range's members but I didn't find anything.
Is there a way to add without the use of a worksheet function? And without having to loop through each cell. Something like:
Sub test()
Range("A1") = 1
Range("A2") = 0
Range("A3") = 2
Range("A4") = Range("A1:A3").Add
MsgBox Range("A4")
End Sub
With output 3.
go on then, a 4th way :o)
evaluate(join(application.transpose(range("a1:a5").Value),"+"))
In general, there is a third way, which you did not mention:
Application.Sum
It is a bit different than WorksheetFunction.Sum, in the fact that it is a bit more discrete - it does not throw errors with a MsgBox, even when it should.
Something like this will have only 2 errors thrown with MsgBox and you will get 2 errors in the immediate window:
Option Explicit
Public Sub WaysOfSumming()
'This is Div/0 Error:
Range("A1").Formula = "=5/0"
Debug.Print Excel.WorksheetFunction.Sum(Range("A1"), 3, 5)
Debug.Print Application.Sum(Range("A1"), 3, 5)
Debug.Print Excel.WorksheetFunction.Sum(Range("A1"), 3, 5)
Debug.Print Application.Sum(Range("A1"), 3, 5)
End Sub

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

On Error works in first and doesn't work in second instance. Bug?

I have a very strange problem here. Here's the code:
reqLang = "ENG"
Select Case reqLang
Case "CRO", "ENG"
'first loop -------------------------------------
On Error GoTo reqLangVisible
i = 1
'Loop until ccCROENG's are all hidden and then go to reqLangVisible.
Do
ActiveDocument.SelectContentControlsByTag("ccCROENG")(i) _
.Range.Font.Hidden = True 'hides all CCs
i = i + 1
Loop
reqLangVisible:
'second loop -------------------------------------
On Error GoTo langOut
i = 1
'Loop until ccreqLang's are all visible and then go to langOut.
Do
ActiveDocument.SelectContentControlsByTitle("cc" & reqLang)(i) _
.Range.Font.Hidden = False 'activates reqLang CCs
i = i + 1
Loop ' CAN'T GET OUT -----------------------------------
Case "CROENG"
i = 1
'Loop until ccCROENG's are all visible and then go to langOut.
Do
On Error GoTo langOut
ActiveDocument.SelectContentControlsByTag("ccCROENG")(i) _
.Range.Font.Hidden = False 'Shows all CCs
i = i + 1
Loop
End Select
langOut:
MsgBox "Success!" '------- yeah, not really.
Stop
I hope it's clear enough what it's trying to do (at least programming-wise). I have multiple ContentControls(CCs) with same titles and tags. The problem I end up with is marked with CAN'T GET OUT, because, you guessed it - I can't get of this second loop! I end up with the Out of range error because it runs out of CCs.
What's even weirder is that it actually did get out of the first loop which has the exact same On Error statement, thought pointing to a different section.
Is it me, or did I just - however unlikely - run onto a bug in VBA?
In any case, is there a solution or at least a workaround?
Typically you only use error handling for dealing with unexpected or unpredictable situations, such as not being able to access a drive, or finding you have no network access.
Error handling is not intended as a substitute for reasonable checks which could otherwise be done. i.e. collections have a Count property which you can use when looping over their items, so avoiding any error caused by trying to access Item(n+1) when there are only n items (and here you know n from Count). Alternatively, use a For Each loop.
Here's some sample code demonstrating use of two methods for looping over your controls:
Sub Tester()
Dim cc1 As ContentControls, cc2 As ContentControls
Dim c, i As Long
With ActiveDocument
Set cc1 = .SelectContentControlsByTag("tbTag")
Set cc2 = .SelectContentControlsByTitle("tbTitle")
End With
Debug.Print "cc1 has " & cc1.Count
Debug.Print "cc2 has " & cc2.Count
'use the Count property
For i = 1 To cc1.Count
Set c = cc1(i)
c.Range.Font.Hidden = True
Next i
'use a For Each loop
For Each c In cc2
c.Range.Font.Hidden = False
Next c
End Sub
This is the type of scenario for which this type of flow control is designed.
Applied to your original code:
Sub Tester2()
Dim reqLang, cc As ContentControls, c
reqLang = "ENG"
Select Case reqLang
Case "CRO", "ENG"
Set cc = ActiveDocument.SelectContentControlsByTag("ccCROENG")
SetTextHidden cc, True
Set cc = ActiveDocument.SelectContentControlsByTitle("cc" & reqLang)
SetTextHidden cc, False
Case "CROENG"
Set cc = ActiveDocument.SelectContentControlsByTag("ccCROENG")
SetTextHidden cc, False
End Select
MsgBox "Success!" '-- yeah really
End Sub
Sub SetTextHidden(cc As ContentControls, MakeHidden As Boolean)
Dim c
For Each c In cc
c.Range.Font.Hidden = MakeHidden
Next c
End Sub
So if you've read my comment, and to formally answer your question, it is not a bug.
You just need to use Error Handling Routines correctly.
What you're trying to do is somewhat like below. HTH.
Select Case reqlang
Case "CRO", "ENG"
On Error Resume Next '~~> ignores the error when encountered
'~~> Your loop which possibly creates the error goes here
On Error Goto 0 '~~> resets the actively triggered error handling
Case "CROENG"
On Error Resume Next '~~> ignores the error when encountered
'~~> Your loop which possibly creates the error goes here
On Error Goto 0 '~~> resets the actively triggered error handling
End Select
MsgBox "Success"
But as the link suggest, you need to handle errors and not simply disregard them. Try cheking on the actual error and find a way to correct it or avoid it.
You'll be surprise that you won't even be needing the Error Handling Routine.

How do I specify a range in the do until loop in VBA?

I'm writing a code, so that
Do Until Range("A1:A10")=5
""
Loop
I want a certain range to all have the same numbers, but VBA keeps on telling me there's a type mismatch. It seems that you can only work with one cell at a time or you would have to use the "And" function? (Do Until Range("A1")=5 And Range("A2")=5 , etc.) But is there a way to have the loop run until a certain range of cells satisfies the condition?
I think you're looking for something like this:
Dim rngUpdate As Range
Set rngUpdate = Range("A1:A5")
Do Until WorksheetFunction.CountIf(rngUpdate, 5) = rngUpdate.Cells.Count
'Your code goes here
Loop
You can also use "for next", in your case:
For i = 1 To 10
If Cells(i, 1) = "5" Then
Exit For ' when cell value is 5, it exits loop
Else
*do other code*
End if
Next i

AndAlso/OrElse in VBA

I'm trying to get a lazy evaluation with 'And' in my Excel macro by doing the following:
If Not myObject Is Nothing *And* myObject.test() Then
'do something'
Else
'do something else'
End If
I know lazy evaluation exists in VB.NET as AndAlso and OrElse but cannot find anything similar in VBA. If lazy evaluation does not exist in VBA, what's the best way to structure the code so that it will evaluate the way I expect?
The only short circuiting (of a sort) is within Case expression evaluation, so the following ungainly statement does what I think you're asking;
Select Case True
Case (myObject Is Nothing), Not myObject.test()
MsgBox "no instance or test == false"
Case Else
MsgBox "got instance & test == true"
End Select
End Sub
This is an old question, but this issue is still alive and well. One workaround I've used:
Dim success As Boolean ' False by default.
If myObj Is Nothing Then ' Object is nothing, success = False already, do nothing.
ElseIf Not myObj.test() Then ' Test failed, success = False already, do nothing.
Else: success = True ' Object is not nothing and test passed.
End If
If success Then
' Do stuff...
Else
' Do other stuff...
End If
This basically inverts the logic in the original question, but you get the same result. I think it's a cleaner solution than the others here that only use If statements. The solution using a Select statement is clever, but if you want an alternative using only If statements, I think this is the one to use.
Or you could create a function that takes your object as a parameter and returns boolean for either case. That's what I usually to.
i.e.
if Proceed(objMyAwesomeObject) then
'do some really neat stuff here
else
'do something else, eh
end if
...
end sub
private function Proceed(objMyAwesomeObject as Object)
if not objMyAweseomeObject is nothing then
Proceed = true
elseif objMyAwesomeObject.SomeProperty = SomeValue then
Proceed = true
else
Proceed = false
endif
end function
Improving on this answer to a different question about the same basic problem, here is what I chose to do:
dim conditionsValid as boolean
conditionsValid = myObject Is Nothing
if conditionsValid then conditionsValid = myObject.test()
if conditionsValid then conditionsValid = myObject.anotherTest()
if conditionsValid then
'do something'
else
'do something else'
end if
I think this code is clearer than the other answers that have been suggested, and you (usually) don't need a different variable for each validation, which is the improvement over the original answer to the other question. By the way, each new condition you need adds just one more line of code.
If Not myObject Is Nothing Then
If myObject.test() Then
'do something'
End If
Else
'do something else'
End If
I think that's the way you have to do it.
Edit
Maybe like this
Dim bTestsFailed as Boolean
bTestsFailed = False
If Not myObject Is Nothing Then
If myObject.test() Then
'do something'
Else
bTestsFailed = True
End If
Else
bTestsFailed = True
End If
If bTestsFailed Then
'do something else
End If
Isn't VBA great?
A trick around missing values may help:
Dim passed, wrongMaxPercent, wrongPercent, rightMinPercent, rightPercent
wrongPercent = 33
rightPercent = 55
'rightMinPercent = 56
wrongMaxPercent = 40
passed = (Len(wrongMaxPercent) = 0 Or wrongPercent < wrongMaxPercent) And _
(Len(rightMinPercent) = 0 Or rightPercent >= rightMinPercent)
Since the following syntax works
If myObject.test() Then do something
Then the one liner syntax could be used to short circuit the evaluation. Below, the first If statement ensures that myObject is something. Otherwise, it won't even try to evaluate the second If.
If Not myObject Is Nothing Then If myObject.test() Then
'do something'
Else
'do something else'
End If
Of course, if you want 'do something else' if myObject Is Nothing, then this may not work.
Update 2020/06/30
After a comment pointed out that this answer didn't work, I have verified that the syntax does not appear to work in modern VBA. Leaving original answer for legacy purposes.
One can switch the logical condition, working with the Or operator and switch off error messages like this:
Err.Clear
On Error Resume Next
If myObject Is Nothing Or Not myObject.test() Then
Err.Clear
'do something else'
Else
'do something'
End If
On Error Goto 0 ' or On Error Goto ErrorHandler (depending on previous setting in the code)
The test for Nothing is not necessary - it only serves to clarify what is meant.
I don't know any equivalent for OrElse, but there is a limited but useful solution for AndAlso. The following two are equivalent:
vb.net: If Condition1 AndAlso Condition2 Then DoSomething
vba: If Condition1 Then If Condition2 Then DoSomething
The are two limitations:
It only works as a one liner, cannot be used to start an if-block
The Else block is executed if the first condition is true and the second is false, not when the first condition is false
Even considering these two fairly crippling limitations, I often use this little trick when I don't have an Else block.
Here is an example:
Sub Test()
Dim C As Collection
' This is what I often use
If Not C Is Nothing Then If C.Count Then DoSomethingWith C
' Here are other usages I stay away from, because of bad readability
If Not C Is Nothing Then If C.Count Then Debug.Print "not empty" Else Debug.Print "empty or nothing"
Set C = New Collection
If Not C Is Nothing Then If C.Count Then Debug.Print "not empty" Else Debug.Print "empty or nothing"
C.Add 1
If Not C Is Nothing Then If C.Count Then Debug.Print "not empty" Else Debug.Print "empty or nothing"
End Sub```
We might want to say:
If XX < 7 Then
XX = 19
but XX might be Null, so we have to test.
We can do this:
If Switch ( IsNull( XX ) , True, True, XX < 7 ) Then
XX = 19
So, if XX is Null, we drop through to do the assignment, otherwise only do it if the test, XX < 7, is True.