How to add error message inside multiples if conditions - vba

It's probably an easy one but I'm kinda new to programming. I probably did some hard-coding beceause I don't know any better.
I would like to add an error message if the first If statement isn't met. I will give you a simplified example of my code. If you want the actual code, let me know and I will edit my question (but careful, it's long!)
If CheckBox1.Value = True and CheckBox2.Value = False then
(CODE number 1)
Else
MsgBox ("error")
End If
If CheckBox1.Value = False and CheckBox2.Value = True then
(CODE numer 2)
Else
MsgBox("Error")
End if
The problem here is that the If statements keeps coming and the code will run line by line. I am aware of that. But I just don't know any better. So I can't place any msgbox error because the code will keep running and execute the rest.
How Do I fragment theses lines so I can place any messagebox I want for each blocks of if ?
Sorry for my english.

Condense that into an If...ElseIf...End If statement.
If CheckBox1.Value = True and CheckBox2.Value = False then
(CODE number 1)
ElseIf CheckBox1.Value = False and CheckBox2.Value = True then
(CODE numer 2)
Else
MsgBox("Error")
End if
This will check the first statement, and if it evaluates as true, it'll do (CODE number 1). If it's not true, it'll check to see if the second statement is true. If the second statement is true, it'll do (CODE number 2). If the second statement isn't true, it'll do MsgBox("Error")
If you have a BUNCH of these, you can do a Select Case True workaround, but it's generally not advised.

Related

My VBA for loop in MS word 2016 is not working

I having trouble with the following code in MS word VBA
For i = 6 To ActiveDocument.Tables(2).Rows.Count
z = Len(ActiveDocument.Tables(2).Cell(i, 2).Range.Text) - 2
x = Len(ActiveDocument.Tables(2).Cell(i, 3).Range.Text) - 2
b = ActiveDocument.Tables(2).Cell(i, 5).Range.ContentControls.Item(1).ShowingPlaceholderText
If (z = 0 And x = 0) Then
If b = True Then
MsgBox "Please do error 1!"
If vbOK Then
Exit Sub
End If
Else
MsgBox "Please do error 2!"
If vbOK Then
Exit Sub
End If
End If
Else
If b = True Then
MsgBox "Please do error 3!"
If vbOK Then
Exit Sub
End If
Else
Confirm = MsgBox("Are you sure to submit?", vbYesNo, "Confirmation")
If Confirm = vbNo Then
Exit Sub
End If
End If
End If
Next i
The for loop won't go into the second line to check if z or x is having value or not
I doubt moving Next i would have solved anything. This code is riddled with badness.
My impression is that your code is intended to check three columns in a table (from rows 6 downwards) - this appears to be a consistency check.
Naming. z, x and b are not very descriptive. Using names like lengthCol2, lengthCol3 and hasPlaceHolderText will help you follow your logic more closely.
Use Option Explicit. Always.
You use a standard MsgBox call, which by default only has a single button ("OK"). The MsgBox is a blocking code element, so the macro will not progress until the user has clicked "OK".
vbOK is an enumerated value (value = 1). So If vbOK then always comes out true. Always. You appear to be seeking some sort of user input, but you are not clear on what that input is.
Address these simple steps gives us:
For i = 6 To ActiveDocument.Tables(2).Rows.Count
lengthCol2 = Len(ActiveDocument.Tables(2).Cell(i, 2).Range.Text) - 2
lengthCol3 = Len(ActiveDocument.Tables(2).Cell(i, 3).Range.Text) - 2
hasPlaceHolderText = ActiveDocument.Tables(2).Cell(i, 5).Range.ContentControls.Item(1).ShowingPlaceholderText
If (lengthCol2 = 0 And lengthCol3 = 0) Then
If hasPlaceHolderText = True Then
MsgBox "Please do error 1!"
Exit Sub
Else
MsgBox "Please do error 2!"
Exit Sub
End If
Else
If hasPlaceHolderText = True Then
MsgBox "Please do error 3!"
Exit Sub
Else
Confirm = MsgBox("Are you sure to submit?", vbYesNo, "Confirmation")
If Confirm = vbNo Then
Exit Sub
End If
End If
End If
Next i
Your logic is negative-biased - that is, intending to find reasons not to do something than to do something. Positive-biased logic is usually easier to understand and maintain - the coder's intent is clearer.
Rewording the logic gives us:
For i = 6 To ActiveDocument.Tables(2).Rows.Count
lengthCol2 = Len(ActiveDocument.Tables(2).Cell(i, 2).Range.Text) - 2
lengthCol3 = Len(ActiveDocument.Tables(2).Cell(i, 3).Range.Text) - 2
hasPlaceHolderText = ActiveDocument.Tables(2).Cell(i, 5).Range.ContentControls.Item(1).ShowingPlaceholderText
If (lengthCol2 > 0 OR lengthCol3 > 0) AND hasPlaceHolderText Then
Confirm = MsgBox("Are you sure to submit?", vbYesNo, "Confirmation")
If Confirm = vbYes Then
'Do submission code here - or call the submission procedure
End If ' Just do nothing if they say "No" - this is what your current code does.
Else
' The next line could be used instead of the nested IF-the-else statements following.
'MsgBox " Table contents are not valid, please ensure columns 2,3 and 5 are completed"
If hasPlaceHolderText then
If (lengthCol2 = 0 And lengthCol3 = 0) Then
MsgBox "Please do error 1!"
Else
MsgBox "Please do error 2!"
EndIF
Else
MsgBox "Please do error 3!"
End If
End If
Next i
Note that in your logic, either Column 2 or Column 3 can be empty and (as long as placeholder text not being shown) you document is ready for submission. Perhaps you meant AND instead of OR (i.e. all columns should be filled).
There is still one problem. Your loop. As currently written, you loop over the logic, thus you ask the user to either check errors or submit the document x number of times based on error checking in each row. But, just moving the Next i does not solve the problem because the only results that are retained are those in the last row. In other words, all the previous rows could be bad/invalid, but you would still be able to submit.
We can fix this last bit by creating cumulative logic. In other words, we track the errors in a short loop, then go into the main logic. This seems to be a little more complex but it really is relative straight forwards. But, we do need more Booleans to make it work.
Dim rowsOK as Boolean
'explicit initialisation - I am working on a positive bias here.
rowsOK = True
For i = 6 To ActiveDocument.Tables(2).Rows.Count
Dim lengthCol2OK as Boolean ' Use these just to make the logic clearer and the code cleaner
Dim lengthCol3OK as Boolean
Dim hasPlaceHolderTextOK as Boolean
lengthCol2OK = Len(ActiveDocument.Tables(2).Cell(i, 2).Range.Text) > 2
lengthCol3OK = Len(ActiveDocument.Tables(2).Cell(i, 3).Range.Text) > 2
hasPlaceHolderTextOK = ActiveDocument.Tables(2).Cell(i, 5).Range.ContentControls.Item(1).ShowingPlaceholderText
rowsOK = rowsOK And ((lengthCol2OK Or lengthCol3OK) And hasPlaceHolderTextOK) ' Note: Using "Or" here as per original code logic
' Extra logic could go here to message the user if any of the above are false.
Next i
If rowsOK Then
Confirm = MsgBox("Are you sure to submit?", vbYesNo, "Confirmation")
If Confirm = vbYes Then
'Do submission code here - or call the submission procedure
End If ' Just do nothing if they say "No" - this is what your current code does.
Else
MsgBox " Table contents are not valid, please ensure columns 2,3 and 5 are completed"
End If
However, this logic works on all the rows, so identifying individual row errors in not possible in the main loop. You could work extra logic in the For-Next loop to message the user for errors.
Now the code is maintainable, and more likely does what you want.
Key points:
Use Option Explicit. This prevents typos and ensures that you are using variables in they way you intend.
Use meaningful variable names. Makes it easier to follow what you want to do.
Don't confuse enumerated values with returns from functions. Don't confuse constants with variables.
Take some time to review your logic chains to ensure they do what you want to do, rather than not do what you don't want to do. The latter has greater chance of missing a non-valid path.

Assign a different macro per click of VBA Button

I have an excel button that hides columns "S-U" when I click it. I want to click the button a 2nd time and it hides columns "P-R" and etc. Can you manipulate an excel button per click?
If there are just a few things you want it to do (it sounds like it), I would have my macro evaluate the current state and act accordingly.
In your example, you indicate that you first want it to hide S-U, then P-R on a second click. This will do that:
Sub HideColumns()
If Sheets("Sheet1").Range("S:S").EntireColumn.Hidden = False Then
Sheets("Sheet1").Range("S:U").EntireColumn.Hidden = True
Else
Sheets("Sheet1").Range("P:R").EntireColumn.Hidden = True
End If
End Sub
Of course, you can extend this with additional conditions and actions.. like changing the text of the button to represent what it will do next:
Assume you have it labeled "Hide Rows S:U" to start, you can change it inside VBA to indicate what it would do on the next click:
Sub HideColumnsUpdateText()
If Sheets("Sheet1").Range("S:S").EntireColumn.Hidden = False Then
Sheets("Sheet1").Range("S:U").EntireColumn.Hidden = True
Sheets("Sheet1").Buttons("Button 1").Text = "Hide Rows P:R"
Else
Sheets("Sheet1").Range("P:R").EntireColumn.Hidden = True
End If
End Sub
There's really no end to what you can do once you start evaluating the current state (extend using ElseIf, or even use Case). You just have to keep the logic straight.
Edit to further expand:
If your situation is linear - that is, if want to hide more and more columns in the same order, you just need to do else ifs to evaluate the situation, and step through the order that you want to hide them.
Personal note: I find that "IF whatever =true" statements are a little easier to follow than "if whatever = false" (and technically, you don't even have to have type the "=True"). But it means you need to start at the last possibility, and work backwards. Otherwise you need to evaluate by "= False" (like I first demonstrated), but I find it a little harder to follow. Your results may vary.
You indicated that you want these hidden in order: "S:U","P:R","M:O","J:L","G:I" . Here is a script that, once all of those rows are hidden, the button would then show them all. So I start by evaluating if the last possibility is true -that is- are Rows G:I hidden already? If so, then show them all. I've also included updating the button text, but that's optional.
Sub hideSetsOfColumnsProgressively()
' progressive order of button function: "S:U","P:R","M:O","J:L","G:I","Unhide Rows"
If Sheets("Sheet1").Range("G:I").EntireColumn.Hidden = True Then
Sheets("Sheet1").Range("G:U").EntireColumn.Hidden = False 'shows all rows
'optionally change the text of the button to indicate the next function:
Sheets("Sheet1").Buttons("Button 1").Text = "Hide Rows S:U"
ElseIf Sheets("Sheet1").Range("J:L").EntireColumn.Hidden = True Then
'then we've already hidden all of the other columns, so do the last set
Sheets("Sheet1").Range("G:I").EntireColumn.Hidden = True
Sheets("Sheet1").Buttons("Button 1").Text = "Unhide Rows"
ElseIf Sheets("Sheet1").Range("M:O").EntireColumn.Hidden = True Then
Sheets("Sheet1").Range("J:L").EntireColumn.Hidden = True
Sheets("Sheet1").Buttons("Button 1").Text = "Hide Rows G:I"
ElseIf Sheets("Sheet1").Range("P:R").EntireColumn.Hidden = True Then
Sheets("Sheet1").Range("M:O").EntireColumn.Hidden = True
Sheets("Sheet1").Buttons("Button 1").Text = "Hide Rows J:L"
ElseIf Sheets("Sheet1").Range("S:U").EntireColumn.Hidden = True Then
Sheets("Sheet1").Range("P:R").EntireColumn.Hidden = True
Sheets("Sheet1").Buttons("Button 1").Text = "Hide Rows M:O"
Else
Sheets("Sheet1").Range("S:U").EntireColumn.Hidden = True
Sheets("Sheet1").Buttons("Button 1").Text = "Hide Rows P:R"
End If
End Sub
The nice part about this script is that it loops. You can just keep clicking it and it will hide progressively more and more rows, then show them all.
I hope this answers your question. Keep in mind that the logic has to be solid, or you'll get unexpected results.
One solution to this problem is be declaring a global variable, and assigning a counter to it:
Private i As Integer
Sub Button1_Click()
If i = 0 Then
Do Stuff 'This is the first click
i = i + 1
Else
Do Stuff the Second Time 'This is the second time and beyond.
i = i + 1
End If
End Sub
i will automatically be assigned a value of 0. The first time it checks i = 0 it will operate however you want the first time. Once it sets i = i + 1 then it will do whatever else you want it to do for the second time. If you want to make it a third time, you can always do Else If i = 1 Then, etc.
Under the Visual Basic Editor, create a new module if you don't already have one and place this in there. Create a button and assign the Button1_Click Macro to the Button.
If you only need to alternate between two options, e.g. like a toggle on off, then use a boolean:
Public x As Boolean
Private Sub CommandButton1_Click()
If x Then MsgBox ("ON") Else MsgBox ("OFF")
x = Not x
End Sub

Resume next index in a for If a condition is true VBA

I want to resume the next index in a for if a condition is granted
The code i'm trying to use looks like this:
For i = 0 to 10
If condition is true then
Next i
else
'code
end if
Next i
Any help is welcome
Just:
For i = 0 to 10
If condition is False then
'code
end if
Next i
If the condition is True then you will just skip to the next i automatically
If the situation is more complicated, because of VBA's lack of a Continue construct, you would need to use a GoTo:
For i = 0 to 10
'code
If condition is true then
'more code
GoTo LoopBottom
else
'still more code
end if
'even more code
LoopBottom:
Next i
Why not use Not in that if statement?
For i = 0 to 10
If Not condition is true Then
'code
End If
Next i
Though John Coleman's answer is better if you wish to have a more complicated statement. His answer would also be more readable.

Application.Interactive = True fails

In the worksheet SelectionChange event I set :
Application.Interactive = False
.... do work
Application.Interactive = True
For some reason Interactive is not getting set back to true, even though I know the code is being executed.
Excel 2013
Any ideas?
Sub Main()
Debug.Print Application.Interactive
Application.Interactive = False
Debug.Print Application.Interactive
Application.Interactive = True
Debug.Print Application.Interactive
End Sub
Doesn't fail for me... Try that and see more here
Since you've discovered the reason for failing the conclusion could be that the Application.Volatile needs to be set to false on a function that uses it because the Interactive mode blocks the user interface and the Volatile calculates things based on user input. While the user input is blocked you can't expect a function that evaluates the user input to work. One excludes the other - hope that makes sense.
Additionally, check your on error resume next or on error goto <label> statements as the would cause skipping some of the code therefore the Application.Interactive = True would have never get executed.

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.