VBA Excel multiple elseif statement - excel

I would like to make a shorter code for multiple elseif statements
My code looks like this:
Sub geography()
Worksheets("Social").Rows("3:165").Hidden = True
Dim cell As Range
For Each cell In Range("F3:F165")
If cell.Value = "GIS" Then
Rows(cell.Row).EntireRow.Hidden = False
ElseIf cell.Value = "CLIMATE" Then
Rows(cell.Row).EntireRow.Hidden = False
ElseIf cell.Value = "TRAVEL" Then
Rows(cell.Row).EntireRow.Hidden = False
ElseIf cell.Value = "TOURISM" Then
Rows(cell.Row).EntireRow.Hidden = False
ElseIf cell.Value = "WILDLIFE" Then
Rows(cell.Row).EntireRow.Hidden = False
End If
Next
End Sub
I found some similar thread here:
Eliminating multiple Elseif statements
but it applies to the range instead of the boolean, like in my case.
Regardless I built the code, based on my situation:
Sub geography2()
Dim arr, res
Dim cell As Range
Dim Variable As Boolean
arr = Array(Array("GIS", False), _
Array("CLIMATE", False), _
Array("TRAVEL", False), _
Array("TOURISM", False), _
Array("WILDLIFE", False))
res = Rows(cell.Row).EntireRow.Hidden
If Not IsError(res) Then
Variable = res
End If
End Sub
but it doesn't work, as the debugger points the line:
res = Rows(cell.Row).EntireRow.Hidden
and says:
Object variable or with block variable not set
How can I cut down the bulk elseif statement then?

Hide Rows (Match / Select Case)
The Select Case version is case-sensitive while the Application.Match version is not.
The Code
Option Explicit
Sub geographyMatch()
Const RowNumbers As String = "3:165"
Dim Criteria As Variant
Criteria = Array("GIS", "CLIMATE", "TRAVEL", "TOURISM", "WILDLIFE")
Worksheets("Social").Rows(RowNumbers).EntireRow.Hidden = True
Dim rng As Range
Dim cel As Range
For Each cel In Worksheets("Social").Columns("F").Rows(RowNumbers)
If Not IsError(Application.Match(cel.Value, Criteria, 0)) Then
If Not rng Is Nothing Then
Set rng = Union(rng, cel)
Else
Set rng = cel
End If
End If
Next cel
If Not rng Is Nothing Then
rng.EntireRow.Hidden = False
End If
End Sub
Sub geographySelectCase()
Const RowNumbers As String = "3:165"
Worksheets("Social").Rows(RowNumbers).EntireRow.Hidden = True
Dim rng As Range
Dim cel As Range
For Each cel In Worksheets("Social").Columns("F").Rows(RowNumbers)
Select Case cel.Value
Case "GIS", "CLIMATE", "TRAVEL", "TOURISM", "WILDLIFE"
If Not rng Is Nothing Then
Set rng = Union(rng, cel)
Else
Set rng = cel
End If
End Select
Next cel
If Not rng Is Nothing Then
rng.EntireRow.Hidden = False
End If
End Sub

To eliminate multiple elseifs, or arrays, try combining if statements with regular expression
Make sure you enable regular expression on: Tools > References > checkbox: "Microsoft VBScript Regular Expressions 5.5"
The function will look for the strings you mentioned ("GIS|CLIMATE|TRAVEL|TOURISM|WILDLIFE") and return True if it passes the regex test, it unhides the cell
Please let me know if it works, if not lets try solving it!
Thanks,
Option Explicit
Dim wb As Workbook
Dim cel As Range
Dim sRng As Range
Dim regex As New RegExp
Sub foo()
Set wb = ThisWorkbook
Set sRng = wb.Sheets("Social").Range("F3:F165")
wb.Sheets("Social").Rows("3:165").Hidden = True
For Each cel In sRng
If chkexist(cel.Value, "GIS|CLIMATE|TRAVEL|TOURISM|WILDLIFE") = True Then
cel.EntireRow.Hidden = False
Else
End If
Next cel
End Sub
Private Function chkexist(ByRef chkstr As String, ByVal patstr As String) As Boolean
'function that tests str if contains regex pattern
'returns boolean
With regex
.Global = True
.Pattern = patstr
End With
chkexist = regex.Test(chkstr)
End Function

Related

vba - add multiple criteria: if entering word #1, #2 and so on in a cell, then messagebox

I'd like to add multiple criteria to this code:
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Const srcCol As String = "A"
Const Criteria As String = "*high*"
Dim rng As Range: Set rng = Intersect(Columns(srcCol), Target)
If rng Is Nothing Then
Exit Sub
End If
Application.EnableEvents = False
Dim aRng As Range
Dim cel As Range
Dim foundCriteria As Boolean
For Each aRng In rng.Areas
For Each cel In aRng.Cells
If LCase(cel.Value) Like LCase(Criteria) Then
MsgBox ("Please check 2020 assessment")
foundCriteria = True
Exit For
End If
Next cel
If foundCriteria Then
Exit For
End If
Next aRng
Application.EnableEvents = True
End Sub
At the current state, this works in this way: if a cell of column "A" contains word "high", alert pop up.
I would like to add more criteria: if cell in column "A" contains "high" but ALSO if a cell in column "A" contains "critic", show me the same alert box.
I started from row "Const Criteria As String = "high", and tried adding "And", "Or", "If", "& _", but nothing seems working to add the second criteria.
Any hint?
A Worksheet Change: Target Contains One of Multiple Strings
If you plan on using exclusively contains for the various criteria, you can do the following changes:
Const CriteriaList As String = "high,critic" ' add more
If LCase(cel.Value) Like "*" & LCase(Criteria(n)) & "*" Then
The Code
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Const srcCol As String = "A"
Const Delimiter As String = "," ' change if you need "," in the criterias
Const CriteriaList As String = "*high*,*critic*" ' add more
Dim rng As Range: Set rng = Intersect(Columns(srcCol), Target)
If rng Is Nothing Then
Exit Sub
End If
Dim Criteria() As String: Criteria = Split(CriteriaList, Delimiter)
Application.EnableEvents = False
Dim aRng As Range
Dim cel As Range
Dim n As Long
Dim foundCriteria As Boolean
For Each aRng In rng.Areas
For Each cel In aRng.Cells
For n = 0 To UBound(Criteria)
If LCase(cel.Value) Like LCase(Criteria(n)) Then
MsgBox ("Please check 2020 assessment")
foundCriteria = True
Exit For
End If
Next n
Next cel
If foundCriteria Then
Exit For
End If
Next aRng
Application.EnableEvents = True
End Sub

Is there any way to shorten down this IsEmpty() and Range statement in VBA? [duplicate]

This question already has answers here:
Detect if range is empty
(8 answers)
Closed 2 years ago.
Is there any way to shorten down this line with a better range statement? When one cell out of the range ("C3:I3") is empty I need it to produce a MsgBox, else run the rest of the code.
If IsEmpty(Range("C3")) = True Or IsEmpty(Range("D3")) = True Or IsEmpty(Range("E3")) = True Or IsEmpty(Range("F3")) = True Or IsEmpty(Range("G3")) = True Or IsEmpty(Range("H3")) = True Or IsEmpty(Range("I3")) = True Then
When I use If IsEmpty(Range("C3:I3")) = True Then the code behaves differently and does not work when only one cell is empty.
Try this:
Function IsRangeEmpty(ByRef theRange As Range) As Boolean
Dim c As Range
For Each c In theRange
If Not IsEmpty(c) Then
IsRangeEmpty = False
Exit Function
End If
Next c
IsRangeEmpty = True
End Function
You could any of the below:
Loop range:
Sub test()
Dim rng As Range, cell As Range
With ThisWorkbook.Worksheets("Sheet1")
Set rng = .Range("C3:C13")
For Each cell In rng
If cell = "" Then
MsgBox "Empty cell!"
End If
Next
End With
End Sub
Pass the range to an array and loop array - Faster:
Sub test_1()
Dim arr As Variant
Dim i As Long
With ThisWorkbook.Worksheets("Sheet1")
arr = .Range("C3:C13")
For i = LBound(arr) To UBound(arr)
If arr(i, 1) = "" Then
MsgBox "Empty cell!"
End If
Next
End With
End Sub

Unable to search and replace the values using column headers

I'm trying to create a vba script that will search for the _ in all the cells fallen under Crude Items column. However, when it finds one, it will split the values from _ and place the rest in corresponding cells fallen under Refined Ones column.
I've tried with the following which is doing the job flawlessly but I wish to search and replace the values using column headers:
Sub CopyAndReplace()
Dim cel As Range
For Each cel In Range("A2:A" & Cells(Rows.Count, 1).End(xlUp).row)
If cel.value <> "" Then
Sheets("Sheet1").Range(cel(1, 3).Address) = Split(cel, "_")(1)
End If
Next cel
End Sub
To let you visualize how the sheet might look like:
How can I search and replace the values using column headers?
I am not sure this is what you are after, but a few important mentions...
Try to always use at least a worksheet qualifier when writing your code. How else is your program going to know explicitly where you would like it to operate?
I have changed your process slightly, but again, not sure if this is exactly what you are after. See below code.
Sub SplitByHeader()
Dim i As Long
Dim crudeHeader As Range, refinedHeader As Range
Dim ws As Worksheet
'set ws
Set ws = ThisWorkbook.Sheets("Sheet1")
'set header ranges
Set crudeHeader = ws.Rows(1).Find(What:="Crude Items", LookAt:=xlWhole)
Set refinedHeader = ws.Rows(1).Find(What:="Refined Ones", LookAt:=xlWhole)
'simple error handler
If crudeHeader Is Nothing Or refinedHeader Is Nothing Then Exit Sub
For i = 2 To ws.Cells(ws.Rows.Count, crudeHeader.Column).End(xlUp).Row
If ws.Cells(i, crudeHeader.Column).Value <> "" Then
ws.Cells(i, refinedHeader.Column).Value = Split(ws.Cells(i, crudeHeader.Column).Value, "_")(1)
End If
Next i
End Sub
I have just tried this one with the code below:
It is a good idea to add additional check to the condition, like this - If myCell.Value <> "" And InStr(1, myCell, "_") Then to avoid starting from A2.
The idea is that the LocateValueCol locates the column of the first row, which has the string, passed to it. Knowing this, it works ok.
Option Explicit
Sub CopyAndReplace()
Dim searchColumn As Long
searchColumn = LocateValueCol("SearchCol", Worksheets(1))
Dim replaceColumn As Long
replaceColumn = LocateValueCol("ReplaceCol", Worksheets(1))
Dim myCell As Range
Dim lastCell As Long
With Worksheets(1)
lastCell = .Cells(.Rows.Count, searchColumn).End(xlUp).Row
For Each myCell In .Range(.Cells(1, searchColumn), .Cells(lastCell, searchColumn))
If myCell.Value <> "" And InStr(1, myCell, "_") Then
.Cells(myCell.Row, replaceColumn) = Split(myCell, "_")(1)
End If
Next
End With
End Sub
This is the function, locating the columns. (If you have ideas for improvement, feel free to make a PR here):
Public Function LocateValueCol(ByVal textTarget As String, _
ByRef wksTarget As Worksheet, _
Optional rowNeeded As Long = 1, _
Optional moreValuesFound As Long = 1, _
Optional lookForPart = False, _
Optional lookUpToBottom = True) As Long
Dim valuesFound As Long
Dim localRange As Range
Dim myCell As Range
LocateValueCol = -999
valuesFound = moreValuesFound
Set localRange = wksTarget.Range(wksTarget.Cells(rowNeeded, 1), wksTarget.Cells(rowNeeded, Columns.Count))
For Each myCell In localRange
If lookForPart Then
If textTarget = Left(myCell, Len(textTarget)) Then
If valuesFound = 1 Then
LocateValueCol = myCell.Column
If lookUpToBottom Then Exit Function
Else
Decrement valuesFound
End If
End If
Else
If textTarget = Trim(myCell) Then
If valuesFound = 1 Then
LocateValueCol = myCell.Column
If lookUpToBottom Then Exit Function
Else
Decrement valuesFound
End If
End If
End If
Next myCell
End Function
Private Sub Increment(ByRef valueToIncrement As Variant, Optional incrementWith As Double = 1)
valueToIncrement = valueToIncrement + incrementWith
End Sub
Private Sub Decrement(ByRef valueToDecrement As Variant, Optional decrementWith As Double = 1)
valueToDecrement = valueToDecrement - decrementWith
End Sub
For fun using regex and dynamically finding header columns. You can swop out the regex based function for your own and still have the dynamic column finding.
Option Explicit
Public Sub test()
Dim i As Long, inputs(), re As Object, ws As Worksheet
Dim inputColumn As Range, outputColumn As Range, inputColumnNumber As Long, outputColumnNumber As Long
Const SEARCH_ROW As Long = 1
Const INPUT_HEADER As String = "Crude items"
Const OUTPUT_HEADER As String = "Refined Ones"
Const START_ROW = 2
Set re = CreateObject("VBScript.RegExp")
Set ws = ThisWorkbook.Worksheets("Sheet1")
Set inputColumn = GetColumnByHeader(ws, SEARCH_ROW, INPUT_HEADER)
Set outputColumn = GetColumnByHeader(ws, SEARCH_ROW, OUTPUT_HEADER)
If inputColumn Is Nothing Or outputColumn Is Nothing Then Exit Sub
inputColumnNumber = inputColumn.Column
outputColumnNumber = outputColumn.Column
With ws
inputs = Application.Transpose(.Range(.Cells(START_ROW, inputColumnNumber), .Cells(.Cells(.Rows.Count, inputColumnNumber).End(xlUp).Row, inputColumnNumber)).Value)
For i = LBound(inputs) To UBound(inputs)
inputs(i) = GetMatch(re, inputs(i))
Next
.Cells(START_ROW, outputColumnNumber).Resize(UBound(inputs), 1) = Application.Transpose(inputs)
End With
End Sub
Public Function GetColumnByHeader(ByVal ws As Worksheet, ByVal SEARCH_ROW As Long, ByVal columnName As String) As Range
Set GetColumnByHeader = ws.Rows(SEARCH_ROW).Find(columnName)
End Function
Public Function GetMatch(ByVal re As Object, ByVal inputString As String) As String
With re
.Global = True
.MultiLine = True
.Pattern = "_(.*)"
If .test(inputString) Then
GetMatch = .Execute(inputString)(0).SubMatches(0)
Else
GetMatch = inputString 'or =vbNullString if want to return nothing
End If
End With
End Function
If you are working through an actual table things will become quite easy:
Sub Test()
Dim arr(), x As Long
With ThisWorkbook.Sheets("Sheet1") 'Change accordingly
For Each cl In .Range("Table1[Crude Items]") 'Change Table1 accordingly
ReDim Preserve arr(x)
If InStr(cl, "_") > 0 Then
arr(x) = Split(cl, "_")(1)
Else
arr(x) = ""
End If
x = x + 1
Next cl
.Range("Table1[Refined Ones]").Value = Application.Transpose(arr)
End With
End Sub
There is a check for "_". If not there, the cell will be kept empty.
You can also consider to use formula to do it.
I am not clear about what you want to replace "_" character with. For example, iff you replace the following line of your script:
Sheets("Sheet1").Range(cel(1, 3).Address) = Split(cel, "_")(1)
with this one:
Sheets("Sheet1").Range(cel(1, 3).Address) = WorksheetFunction.Substitute(cel, "_", "")
The above line should replace the "_" character with nothing from the cells in the Crude_Items column
And as Lee said, you can also consider using formula in the worksheet if you do not have significant amount of data

How to highlight substring using LIKE operator in Excel VBA

I have strings that look like this:
DTTGGRKDVVNHCGKKYKDK
RKDVVNHCGKKYKDKSKRAR
What I want to do is to highlight the region with bold and red font.
Resulting this:
I tried the following code using LIKE operator in Excel VBA but it breaks
at this line Set MC = .Execute(C.Text)
Option Explicit
Sub boldSubString()
Dim R As Range, C As Range
Dim MC As Object
Set R = Range(Cells(2, 1), Cells(Rows.Count, 1).End(xlUp))
For Each C In R
C.Font.Bold = False
If C.Text Like "KK*K" Or C.Text Like "KR*R" Then
Set MC = .Execute(C.Text)
C.Characters(MC(0).firstindex + 1, MC(0).Length).Font.Bold = True
End If
Next C
End Sub
What's the right way to do it?
I'm using Mac Excel Version 15.31
Without Regular Expressions, you can try the following. I've not tested it extensively but it does seem to work even with multiple matching substrings within the same string.
Examine VBA HELP for the functions that are being used, so you understand how this works, and also how to construct proper patterns to be used with the Like operator, in case you need to expand the list of possible patterns.
Option Explicit
Sub boldSS()
Dim WS As Worksheet
Dim R As Range, C As Range
Dim sPatterns(1) As String
Dim I As Long, J As Long
sPatterns(0) = "KR?R"
sPatterns(1) = "KK?K"
Set WS = Worksheets("sheet1")
With WS
Set R = .Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp))
End With
For Each C In R
'Reset to default
With C.Font
.Bold = False
.Color = vbBlack
End With
For I = 0 To UBound(sPatterns)
If C Like "*" & sPatterns(I) & "*" Then
For J = 1 To Len(C) - Len(sPatterns(I)) + 1
If Mid(C, J, Len(sPatterns(I))) Like sPatterns(I) Then
With C.Characters(J, Len(sPatterns(I))).Font
.Bold = True
.Color = vbRed
End With
If J < Len(C) - 3 Then
J = J + 3
Else
Exit For
End If
End If
Next J
End If
Next I
Next C
End Sub
Using your regex pattern equivalent instead for the Like operator, you can rewrite the above as below. Note that your Regex pattern will also match KKAR, and KRAK (as does the macro below, but not the one above).
Option Explicit
Sub boldSS()
Dim WS As Worksheet
Dim R As Range, C As Range
Dim sPattern As String
Dim I As Long
sPattern = "K[KR]?[KR]"
Set WS = Worksheets("sheet1")
With WS
Set R = .Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp))
End With
For Each C In R
With C.Font
.Bold = False
.Color = vbBlack
End With
If C Like "*" & sPattern & "*" Then
For I = 1 To Len(C) - 4 + 1
If Mid(C, I, 4) Like sPattern Then
With C.Characters(I, 4).Font
.Bold = True
.Color = vbRed
End With
If I < Len(C) - 3 Then
I = I + 3
Else
Exit For
End If
End If
Next I
End If
Next C
End Sub
SubString problems could be complicated, once one drills a bit in them. E.g., in the OP example, the substring KKYKDKSK also is a correct substring of KK*K, thus, it probably could be color coded as well.
In general, with some limitations the task, like searching for non-overlapping substrings and considering that the substring is present once per string, this is possible:
With some hardcoding of the variables and checking only for KK*K, this is how the main method looks like:
Option Explicit
Sub TestMe()
Dim myRange As Range: Set myRange = Worksheets(1).Range("A1:A2")
Dim myCell As Range
For Each myCell In myRange
myCell.Font.Bold = False
Dim subString As String
subString = findTheSubString(myCell.Value2, "KK*K")
Debug.Print myCell.text, subString
ChangeTheFont subString, myCell, vbBlue
Next myCell
End Sub
The function findTheSubString() takes the 2 strings and returns the substring, which is to be color-coded later:
Public Function findTheSubString(wholeString As String, subString As String) As String
Dim regEx As Object
Dim inputMatches As Object
Dim regExString As String
Set regEx = CreateObject("VBScript.RegExp")
With regEx
.Pattern = Split(subString, "*")(0) & "[\s\S]*" & Split(subString, "*")(1)
.IgnoreCase = True
.Global = True
Set inputMatches = .Execute(wholeString)
If regEx.test(wholeString) Then
findTheSubString = inputMatches(0)
Else
findTheSubString = "Not Found!"
End If
End With
End Function
The last part is to change the font of a specific substring in Excel range, thus the arguments are a string and a range:
Sub ChangeTheFont(lookFor As String, currentRange As Range, myColor As Long)
Dim startPosition As Long: startPosition = InStr(1, currentRange.Value2, lookFor)
Dim endPosition As Long: endPosition = startPosition + Len(currentRange.Value2)
With currentRange.Characters(startPosition, Len(lookFor)).Font
.Color = myColor
.Bold = True
End With
End Sub

Excel VBA - Using shapes as toggle buttons

I'm trying to use a shape instead of a button to toggle hiding rows with blank cells (according to conditions). Is it even possible?
Sub ToggleChevron3_Click()
Dim rng As Range, cell As Range
Set rng = Range("A1:C100")
Application.ScreenUpdating = False
With rng
For Each cell In rng
If cell.Offset(0, 4).Value = "" Then ' Condition 1
If cell.Value = "" Then ' Condition 2
ActiveSheet.Shapes("Chevron 3").cell.EntireRow.Hidden _
= Not ActiveSheet.Shapes("Chevron 3").cell.EntireRow.Hidden
End If
End If
Next
End With
Application.ScreenUpdating = True
End Sub
Yes, it is possible. The code to accomplish what I think you are looking for is below. Both pieces of code below assume you want to just click a button to hide / unhide the rows, depending on the current state.
Sub ToggleChevron3_Click()
Application.ScreenUpdating = False
Dim rng As Range, cell As Range
'Set rng = Range("A1:C100") 'do you really want to loop through every cell in columns A through C
Set rng = Range("A1:A100")
For Each cell In rng
If Len(cell.Offset(, 4).Value) = 0 And Len(cell.Value) = 0 Then
Dim bToggle As Boolean
bToggle = cell.EntireRow.Hidden
cell.EntireRow.Hidden = Not bToggle
End If
Next
Application.ScreenUpdating = True
End Sub
However, there is alternative that is cleaner code and faster execution, as long as filtering is okay for you.
Sub ToggleChevron3_Click()
Application.ScreenUpdating = False
Dim bToggle As Boolean
bToggle = ActiveSheet.AutoFilterMode
If bToggle Then
ActiveSheet.AutoFilterMode = False
Else
Dim rng As Range
Set rng = Range("A1:E100") 'used E because you had an offset of 4 columns
With rng
.AutoFilter 5, "<>"
.AutoFilter 1, "<>"
End With
End If
Application.ScreenUpdating = True
End Sub

Resources