Datavalidation with IF stament in VBA for differenet rows - excel

I would like to create a dropdown in Excel on Sheet1 if in the row any cells conatins a an expression (Here is the example of "PBE"), then an extended dropdown list will be available.
(The extension worked without if)
Option Explicit
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim i As Long
Dim a$, el As Range
Dim a1 As Range
Dim rng1 As Range, rng2 As Range
Set rng1 = Worksheets("OptionList").Range("E8:E48") 'Base list
Set rng2 = Worksheets("OptionList").Range("K2:K3") 'IF in the row the list contains PBE add to the selection this list as
If Not Intersect(Target, Target.Worksheet.Range("A2")) Is Nothing Then
For Each el In rng1 'first range
a = a & el.Value & ","
Next
For Each el In rng2 '2nd range but only if the row contains
a1 = a & el.Value & ","
Next
For i = 68 To 78
If Worksheets("Sheet1").Range(Cells(i, 19), Cells(i, 48)).Find("PBE") Is Nothing Then
With Worksheets("Sheet1").Range(Cells(i, 19), Cells(i, 48)).Validation 'destination val.list (without PBA)
.Delete
.Add Type:=xlValidateList, Formula1:=a
End With
Else
With Worksheets("Sheet1").Range(Cells(i, 19), Cells(i, 48)).Validation 'destination val.list with PBA
.Delete
.Add Type:=xlValidateList, Formula1:=a1
End If
Next i
End If
Set rng1 = Nothing
Set rng2 = Nothing
End Sub
It is not working line by line, but for all the lines which are targeted by i.
So I mean if anywere in the big range there is a PBE word not line, by line it trigers the exteneded dropdown menu.

There are some simplications we can make to the code in order to make it more maintainable and straightforward.
My first suggestion is to make your very first statement the check if your SelectionChange is your target cell. This is more efficient because no other logic or code is executed when it's not necessary.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
'--- add data validation only if the XXXXXX cell isn't empty
If Not Intersect(Target, Range("A2")) Is Nothing Then
...
End If
End Sub
Next, you're confusing yourself (and me) with references to worksheets. This code is intended to execute when the user changes the selected cell on Sheet1, but then you're referring to the OptionList worksheet. Use descriptive variable names and explicitly define variables so there's no confusion in the worksheet or range you're referencing.
Dim optionWS As Worksheet
Set optionWS = ThisWorkbook.Sheets("OptionList")
Dim listData As Range
Set listData = optionWS.Range("E8:E48")
Instead of looping to create your comma separated list, you can use the Join function in a single line:
Dim baseList As String
baseList = Join(Application.Transpose(listData.Value), ",")
Dim extendedList As String
Set listData = optionWS.Range("K2:K3")
extendedList = baseList & "," & Join(Application.Transpose(listData.Value), ",")
One source of confusion when reading your code was the continued/repeated reference to Worksheets("Sheet1").Range(Cells(i, 19), Cells(i, 48)). When this happens, it's far easier to create a specific variable that refers to that range. It also makes it very clear what the size of the range is...
Dim dropDownRange As Range
Set dropDownRange = ActiveSheet.Range("S68").Resize(10, 29)
So now it's just a matter of looping over each row in that range and checking for your PBE string.
Dim checkRow As Range
For Each checkRow In dropDownRange.Rows
With checkRow
If .Find("PBE") Is Nothing Then
.Validation.Delete
.Validation.Add Type:=xlValidateList, Formula1:=baseList
Else
.Validation.Delete
.Validation.Add Type:=xlValidateList, Formula1:=extendedList
End If
End With
Next checkRow
Here's the whole module:
Option Explicit
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
'--- add data validation only if the XXXXXX cell isn't empty
If Not Intersect(Target, Range("A2")) Is Nothing Then
Dim optionWS As Worksheet
Set optionWS = ThisWorkbook.Sheets("OptionList")
Dim listData As Range
Set listData = optionWS.Range("E8:E48")
Dim baseList As String
baseList = Join(Application.Transpose(listData.Value), ",")
Dim extendedList As String
Set listData = optionWS.Range("K2:K3")
extendedList = baseList & "," & Join(Application.Transpose(listData.Value), ",")
Dim dropDownRange As Range
Set dropDownRange = ActiveSheet.Range("S68").Resize(10, 29)
Dim checkRow As Range
For Each checkRow In dropDownRange.Rows
With checkRow
If .Find("PBE") Is Nothing Then
.Validation.Delete
.Validation.Add Type:=xlValidateList, Formula1:=baseList
Else
.Validation.Delete
.Validation.Add Type:=xlValidateList, Formula1:=extendedList
End If
End With
Next checkRow
End If
End Sub

Related

change first 3 characters to bold format

How do I change the first 3 characters and "CLEARANCE" Font to BOLD of cells containing "T##-" and loop it until the last row of STANDARD and NON-STANDARD tables
Sub Formatting()
Dim StartCell As Range
Set StartCell = Range("A15")
Dim myList As Range
Set myList = Range("A15:A" & Range("A" & Rows.Count).End(xlUp).Row)
Dim x As Range
For Each x In myList
'myList.ClearFormats
x.Font.Bold = False
If InStr(1, x.Text, "CLEARANCE") > 0 Or InStr(1, x.Text, "clearance") > 0 Then
x.Font.Bold = True
Else
x.Font.Bold = False
End If
Next
For Each x In myList
'myList.ClearFormats
x.Font.Bold = False
If InStr(1, x.Text, "T*") > 0 Then
x.Font.Bold = True
Else
x.Font.Bold = False
End If
Next
End Sub
ORIG
FORMATTED
Here is one way to achieve what you want which I feel is faster (I could be wrong). This way lets Excel do all the dirty work :D.
Let's say our data looks like this
LOGIC:
Identify the worksheet you are going to work with.
Remove any autofilter and find last row in column A.
Construct your range.
Filter the range based on "=T??-*" and "=*CLEARANCE*".
Identify the filtered range.
Check if there was anything filtered and if it was, then do a Find and Replace
Search for "CLEARANCE" and replace with bold tags around it as shown in the code.
Loop through the filtered range to create an html string and then copy to clipboard
Finally paste them back.
CODE:
Is this what you are trying? I have commented the code so you should not have a problem understanding it but if you do them simply ask :)
Option Explicit
Sub Sample()
Dim ws As Worksheet
Dim lRow As Long
Dim rng As Range, rngFinal As Range, aCell As Range
Dim htmlString As Variant
'~~> Set this to the relevant Sheet
Set ws = Sheet1
With ws
'~~> Remove any autofilter
.AutoFilterMode = False
'~~> Find last row in Col A
lRow = .Range("A" & .Rows.Count).End(xlUp).Row
'~~> Construct your range
Set rng = .Range("A1:A" & lRow)
'~~> Filter the range
With rng
.AutoFilter Field:=1, Criteria1:="=T??-*", _
Operator:=xlAnd, Criteria2:="=*CLEARANCE*"
'~~> Set the filtered range
Set rngFinal = .Offset(1, 0).SpecialCells(xlCellTypeVisible).EntireRow
End With
End With
'~~> Check if there was anything filtered
If Not rngFinal Is Nothing Then
rngFinal.Replace What:="CLEARANCE", Replacement:="<b>CLEARANCE</b>", _
LookAt:=xlPart, SearchOrder:=xlByRows, MatchCase:=False, SearchFormat:= _
False, ReplaceFormat:=False, FormulaVersion:=xlReplaceFormula2
'~~> Loop through the filtered range and add
'~~> ending html tags and copy to clipboard and finally paste them
For Each aCell In rng.SpecialCells(xlCellTypeVisible)
If aCell Like "T??-*" Then
htmlString = "<html><b>" & _
Left(aCell.Value2, 4) & "</b>" & _
Mid(aCell.Value2, 5) & "</html>"
With CreateObject("htmlfile")
With .parentWindow.clipboardData
Select Case True
Case Len(htmlString): .setData "text", htmlString
Case Else: .GetData ("text")
End Select
End With
End With
DoEvents
aCell.PasteSpecial xlPasteAll
End If
Next aCell
End If
'~~> Remove any filters
ws.AutoFilterMode = False
End Sub
OUTPUT:
NOTE: If you want to bold either of the text when one of them is absent then change Operator:=xlAnd to Operator:=xlOr in the above code.
I thought I'd chuck in this solution based on regex. I was fiddling around a long time trying to use the Submatches attributes, but since they do not have the FirstIndex() and Lenght() properties, I had no other option than just using regular matching objects and the Like() operator:
Sub Test()
Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets("Sheet1")
Dim rng As Range, cl As Range, lr As Long
lr = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
Set rng = ws.Range("A1:A" & lr)
With CreateObject("vbscript.regexp")
.Global = True
.Pattern = "\bCLEARANCE\b"
For Each cl In rng
If cl.Value Like "T[0-9][0-9]-*" Then
cl.Characters(0, 3).Font.Bold = True
If .Test(cl.Value) Then
Set M = .Execute(cl.Value)
cl.Characters(M(0).firstindex + 1, M(0).Length).Font.Bold = True
End If
End If
Next
End With
End Sub
The Like() operator is there just to verify that a cell's value starts with a capital "T", two digits followed by an hyphen. This syntax is close to what regular expressions looks like but this can be done without a call to the regex-object.
When the starting conditions are met, I used a regex-match to test for the optional "CLEARANCE" in between word-boundaries to assert the substring is not part of a larger substring. I then used the FirstIndex() and Lenght() properties to bold the appropriate characters.
The short and easy, but not fast and flexible approach. "Bare minimum"
No sheet specified, so uses active sheet. Will ignore multiple instances of "CLEARANCE", will loop everything (slow), ingores starting pattern (only cares if it starts with "T"), doesn't remove any bold text from things that shouldn't be bold.
Sub FormattingLoop()
Dim x As Range
For Each x In Range("A15:A" & Cells(Rows.Count, "A").End(xlUp).Row)
If Left(x, 1) = "T" Then x.Characters(, 3).Font.FontStyle = "Bold"
If InStr(UCase(x), "CLEARANCE") > 0 Then x.Characters(InStr(UCase(x), "CLEARANCE"), 9).Font.FontStyle = "Bold"
Next x
End Sub

How to get excel drop down list source in vba

Im using VBA code to create multiple selection drop down list. The code will make each drop down list in target cell become multuple selection list with function:
If Target.SpecialCells(xlCellTypeAllValidation) Is Nothing Then GoTo Exitsub
The source of drop down list in target cell is =indirect(b14), and b14 is another drop down list (single selection). Now if b14's value will become list1, Id like to make my target's cell list become multiple selection list. In any other case I want it work in normal excel way. I've tried to precache the list source with if Evaluate(Target.Validation.Formula1) = "=list1" then
but I get mismatch error for Evaluate(Target.Validation.Formula1).
How can I do it?
EDIT:
There are some example screenshots from my worksheet, not to misunderstand it's construction.
A1:A5 named range list1, B1:B5 named range list2, B14 data validation list =list1
D14 data validation list with =INDIRECT(B14) formula
First of all, using the Worksheet_Change event means that every worksheet change is going to run your code, so Target could be any range not just B14. The assumption that you can use the Target.Validation.Formula1 property on any cell is wrong because cells that do not have validation will not have this property available.
Secondly, you are doing this:
If Target.SpecialCells(xlCellTypeAllValidation) Is Nothing Then GoTo Exitsub
I believe that you are making the assumption that this is referring to cells within the Target range but it really refers to all the cells with validation within the entire sheet. Try this code to clarify that:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rngValidation As Range
Set rngValidation = Target.SpecialCells(xlCellTypeAllValidation)
Debug.Print Target.Address
If Not rngValidation Is Nothing Then Debug.Print rngValidation.Address
End Sub
You can see in your Immediate window that no matter what cell you are editing the rngValidation will always point to all the validation cells within the worksheet.
Thirdly, you are doing this:
If Evaluate(Target.Validation.Formula1) = "=list1"
which won't work because Evaluate("=Indirect(B14)") simply returns an array and not a String as you are assuming.
Finally, if I read the question I understand that you want the list in cell D14 to be changed based on value in B14 but you keep referring to the Target as D14. If B14 is changed then B14 is the Target, not D14. D14 can only be the Target if you change D14. That's just how the Event works.
Since I am not clear on what you want, I am assuming two scenarios:
Cell B14 is changed and you want to update D14
Cell D14 is selected and you want the list to be updated before you click the dropdown
Scenario 1 - Cell B14 is changed and you want to update D14 (or other cells)
Private Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
Dim rngValidation As Range
Dim rngValidTarget As Range
Dim rngCell As Range
Dim rngArea As Range
Set rngValidation = Target.Worksheet.Cells.SpecialCells(xlCellTypeAllValidation)
Set rngValidTarget = Intersect(Target, rngValidation)
If rngValidTarget Is Nothing Then GoTo ExitSub
'validTarget could still be a multi-cell range
On Error Resume Next
For Each rngArea In rngValidTarget.Areas
For Each rngCell In rngArea
If rngCell.Validation.Type = xlValidateList Then
If rngCell.Validation.Formula1 = "=List1" Then
Debug.Print rngCell.Address & " - Validation: " & rngCell.Validation.Formula1
'Do whatever logic you need to update other cells linking to this one
'
'
'
End If
End If
Next rngCell
Next rngArea
On Error GoTo 0
ExitSub:
Application.EnableEvents = True
End Sub
Scenario 2 - Cell D14 (or equivalent) is selected and you want the list to be updated before you click the dropdown
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Application.EnableEvents = False
Dim rngValidation As Range
Dim rngValidTarget As Range
Dim rngCell As Range
Dim rngArea As Range
Dim rngList As Range
Dim listFound As Boolean
Set rngValidation = Target.Worksheet.Cells.SpecialCells(xlCellTypeAllValidation)
Set rngValidTarget = Intersect(Target, rngValidation)
If rngValidTarget Is Nothing Then GoTo ExitSub
'validTarget could still be a multi-cell range
On Error Resume Next
For Each rngArea In rngValidTarget.Areas
For Each rngCell In rngArea
If rngCell.Validation.Type = xlValidateList Then
Set rngList = Nothing
Set rngList = Evaluate(rngCell.Validation.Formula1)
listFound = False
If Not rngList Is Nothing Then
listFound = (rngList.Name.Name = "List1")
End If
If listFound Then
Debug.Print rngCell.Address & " - list found"
'Do whatever logic you need to update rngCell
'
'
Else
Debug.Print rngCell.Address & " - list not found"
'Do whatever logic you need to update rngCell
'
'
End If
End If
Next rngCell
Next rngArea
On Error GoTo 0
ExitSub:
Application.EnableEvents = True
End Sub
EDIT 1
You can use the following code to translate formulas:
Private Function TranslateFormulaToUS(ByVal formulaText As String) As String
On Error Resume Next
With GetBlankEditableCell
.Formula2Local = formulaText
TranslateFormulaToUS = .Formula
.Formula = vbNullString
End With
On Error GoTo 0
End Function
Private Function GetBlankEditableCell() As Range
Dim wSheet As Worksheet
Static blankCell As Range
'
'Re-use, if still blank
If Not blankCell Is Nothing Then
If IsEmpty(blankCell.Value2) Then
Set GetBlankEditableCell = blankCell
Exit Function
End If
End If
'
'Find a Blank cell
For Each wSheet In ThisWorkbook.Worksheets
Set blankCell = GetEditableBlankCellFromSheet(wSheet)
If Not blankCell Is Nothing Then Exit For
Next wSheet
Set GetBlankEditableCell = blankCell
End Function
Private Function GetEditableBlankCellFromSheet(wSheet As Worksheet) As Range
Dim rngBlanks As Range
Dim rngCell As Range
'
On Error Resume Next
Set rngBlanks = wSheet.UsedRange.SpecialCells(xlCellTypeBlanks)
On Error GoTo 0
If rngBlanks Is Nothing Then Set rngBlanks = wSheet.Cells(1, 1)
'
'Check if Worksheet is Macro Protected
If (wSheet.ProtectContents Or wSheet.ProtectDrawingObjects _
Or wSheet.ProtectScenarios) And Not wSheet.ProtectionMode _
Then
For Each rngCell In rngBlanks
If Not rngCell.Locked Is Nothing Then
Set GetEditableBlankCellFromSheet = rngCell
Exit Function
End If
Next rngCell
Else
Set GetEditableBlankCellFromSheet = rngBlanks.Cells(1, 1)
End If
End Function
And now you can replace something like:
Set rngList = Evaluate(rngCell.Validation.Formula1)
with:
Set rngList = Evaluate(TranslateFormulaToUS(rngCell.Validation.Formula1))
EDIT 2
If you would like to avoid the translation mentioned in EDIT 1 then you could use a dynamic relative named range as mentioned in the comments.
Let's start with the current layout (I presume I got it right):
Named range List1 is a local scope range:
Named range List2 is also a local scope range:
The B column (rows could vary from sheet to sheet) has data validation set to List1:
Let's create a third named range called RemoteDV:
Select first cell in column D that has the validation
Create a LOCAL named range and add the formula =INDIRECT(Sheet1!$B8) (or whatever row you are on - i.e. first row in both B and D column that has validation - I have 8 here). NOTE! Do not use an absolute address (i.e. locking the row with =INDIRECT(Sheet1!$B$8)) because we want the named range to work for the entire D column
Now, let's link the new named range to validation:
Select all cells in column D that have validation
Link to the named range you have just created
The end result is that you do not have to translate the formula anymore.
You also do not need Evaluate anymore:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Application.EnableEvents = False
Dim rngValidation As Range
Dim rngValidTarget As Range
Dim rngCell As Range
Dim rngArea As Range
Dim rngList As Range
Dim listFound As Boolean
Dim formulaText As String
Dim nameList As Name
Set rngValidation = Target.Worksheet.Cells.SpecialCells(xlCellTypeAllValidation)
Set rngValidTarget = Intersect(Target, rngValidation)
If rngValidTarget Is Nothing Then GoTo ExitSub
'validTarget could still be a multi-cell range
On Error Resume Next
For Each rngArea In rngValidTarget.Areas
For Each rngCell In rngArea
If rngCell.Validation.Type = xlValidateList Then
Set rngList = Nothing
formulaText = rngCell.Validation.Formula1
If Left$(formulaText, 1) = "=" Then
formulaText = Right$(formulaText, Len(formulaText) - 1)
End If
Set nameList = Nothing
Set nameList = rngCell.Worksheet.Names(formulaText)
Set rngList = nameList.RefersToRange
listFound = False
If Not rngList Is Nothing Then
listFound = (rngList.Name.Name = "'" & rngList.Worksheet.Name & "'!" & "List1") _
Or (rngList.Name.Name = rngList.Worksheet.Name & "!" & "List1")
End If
If listFound Then
Debug.Print rngCell.Address & " - list found"
'Do whatever logic you need to update rngCell
'
'
Else
Debug.Print rngCell.Address & " - list not found"
'Do whatever logic you need to update rngCell
'
'
End If
End If
Next rngCell
Next rngArea
On Error GoTo 0
ExitSub:
Application.EnableEvents = True
End Sub
EDIT:
Below is a simple block of code that should do what you need. First, I created a data validation drop down in cell A1. Next, I created a list named List1 and pointed it to a range of values. Next, I set the List -> Formula of the data validation to be =INDIRECT(B14). And finally I entered the text List1 in cell B14.
I ran the below test script to see what my output was.
Sub Test()
Dim rangeWithDropdown As Range
Set rangeWithDropdown = Sheets("Sheet1").Range("A1")
Debug.Print rangeWithDropdown.Validation.Formula1
Debug.Print Evaluate(rangeWithDropdown.Validation.Formula1).Name
Debug.Print Evaluate(rangeWithDropdown.Validation.Formula1).Name = ThisWorkbook.Names("List1").Value
End Sub
My output was the following:
=INDIRECT(B14)
=Sheet1!$D$1:$D$4
True
When requesting the formula alone, it returns =INDIRECT(B14). When evaluating the formula, and returning the name, it returns the range that I established. And finally, when testing for equality against the named range, it returns true.
Is my understanding correct? Can you try running this code against your workbook (update the data validation cell reference), and then tell me which line throws an error?
END EDIT
The reason that your code isn't working is that Evaluate(=indirect(B14)) does not return the name of the range, but rather the address of the range. So, if List1 refers to Range("A1:A10"), then the Evaluate function will return Sheet1!Range("A1:A10"). When you try comparing a string ("list1") to a range, you get the type mismatch error.
One option is to compare the range returned against the expected range of "List1". For example, the following code might work:
If evaluate(activecell.validation.formula1).name = activeworkbook.Names("List1").Value
I see there has been a lot of work by others. I didn't want to "steal" their solutions so i didn't read them completely. I hope my contribution won't be out of place. I humbly proceed to post my answer.
If in the column with the first drop-down lists (column B) the said drop-down list are already present, then our "List1" outcome will be a possible value. This solution checks if such value is "List1" and creates the second drop-down list accodingly:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
'Declarations.
Dim DblStartingRow As Double
Dim DblEndingRow As Double
Dim RngFirstDropDownList As Range
Dim RngSecondDropDownList As Range
Dim RngRange01
Dim StrTrigger As String
''''''''''''''''''''''''''''
'VARIABLES SETTINGS - Start'
''''''''''''''''''''''''''''
'StrTrigger will be the value that if found in the first drop down _
list will trigger the creation of the second drop down list.
StrTrigger = "List1"
'DblStartingRow is the first row that will possibly contain one of _
our drop down list.
DblStartingRow = 14
'DblStartingRow is the last row that will possibly contain one of _
our drop down list.
DblEndingRow = Rows.Count
'Setting RngFirstDropDownList and RngSecondDropDownList to match _
the entire columns where our lists of drop-down lists will be found.
Set RngFirstDropDownList = Range("B:B")
Set RngSecondDropDownList = Range("D:D")
''''''''''''''''''''''''''
'VARIABLES SETTINGS - End'
''''''''''''''''''''''''''
'Resetting RngSecondDropDownList to cover only the rows we need to _
cover according to DblStartingRow and DblEndingRow
Set RngSecondDropDownList = RngSecondDropDownList.Resize(DblEndingRow - DblStartingRow + 1, 1).Offset(DblStartingRow - 1, 0)
'Checking if Target intersects with RngSecondDropDownList. If there _
is no intersection, the subroutine is terminated. Otherwise RngRange01 _
is set as such intersection.
On Error Resume Next
Set RngRange01 = Intersect(Target, RngSecondDropDownList)
On Error GoTo 0
If RngRange01 Is Nothing Then Exit Sub
'Covering each cell in RngRange01
For Each RngSecondDropDownList In RngRange01
'Setting RngFirstDropDownList as the cell in the column of first _
drop-down lists at the same row of our (possible) second drop-down _
list.
Set RngFirstDropDownList = Cells(RngSecondDropDownList.Row, RngFirstDropDownList.Column)
'Focusing RngSecondDropDownList.
With RngSecondDropDownList.Validation
'Removing validation.
.Delete
'Checking if RngFirstDropDownList contains StrTrigger.
If RngFirstDropDownList.Formula = StrTrigger Then
'Adding the dropdown list.
.Add Type:=xlValidateList, _
AlertStyle:=xlValidAlertStop, _
Operator:=xlBetween, _
Formula1:="=INDIRECT(" & RngFirstDropDownList.Address & ")"
.IgnoreBlank = True
.InCellDropdown = True
.InputTitle = ""
.ErrorTitle = ""
.InputMessage = ""
.ErrorMessage = ""
.ShowInput = True
.ShowError = True
End If
End With
Next
End Sub
To be put in the sheets' module, it will activate everytime the selection is changed. If the selection intersect with the range with the second drop-down list, it will insert such drop-down list for each cell in said intersection. Works for single and multiple cell selection. I've set every possible parameter i could think of as a variable that can be changed in the first part of the subroutine after declarations. This should do what the question was asking.
Then again, if the question wanted the second drop-down list to be created only when:
there is a first drop-down list in the proper cell and
said first drop-down list had a specific Validation.Formula1
then the code i'd suggest is this one:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
'Declarations.
Dim DblStartingRow As Double
Dim DblEndingRow As Double
Dim RngFirstDropDownList As Range
Dim RngSecondDropDownList As Range
Dim RngRange01
Dim StrTrigger As String
Dim StrValidation As String
''''''''''''''''''''''''''''
'VARIABLES SETTINGS - Start'
''''''''''''''''''''''''''''
'StrTrigger will be the formula that if found in Validation.Formula1 _
of the first drop-down list will trigger the creation of the second _
drop down list.
StrTrigger = "=List1"
'DblStartingRow is the first row that will possibly contain one of _
our drop down list.
DblStartingRow = 14
'DblStartingRow is the last row that will possibly contain one of _
our drop down list.
DblEndingRow = Rows.Count
'Setting RngFirstDropDownList and RngSecondDropDownList to match _
the entire columns where our lists of drop-down lists will be found.
Set RngFirstDropDownList = Range("B:B")
Set RngSecondDropDownList = Range("D:D")
''''''''''''''''''''''''''
'VARIABLES SETTINGS - End'
''''''''''''''''''''''''''
'Resetting RngSecondDropDownList to cover only the rows we need to _
cover according to DblStartingRow and DblEndingRow
Set RngSecondDropDownList = RngSecondDropDownList.Resize(DblEndingRow - DblStartingRow + 1, 1).Offset(DblStartingRow - 1, 0)
'Checking if Target intersects with RngSecondDropDownList. If there _
is no intersection, the subroutine is terminated. Otherwise RngRange01 _
is set as such intersection.
On Error Resume Next
Set RngRange01 = Intersect(Target, RngSecondDropDownList)
On Error GoTo 0
If RngRange01 Is Nothing Then Exit Sub
'Covering each cell in RngRange01
For Each RngSecondDropDownList In RngRange01
'Setting RngFirstDropDownList as the cell in the column of first _
drop-down lists at the same row of our (possible) second drop-down _
list.
Set RngFirstDropDownList = Cells(RngSecondDropDownList.Row, RngFirstDropDownList.Column)
'Focusing RngSecondDropDownList.
With RngSecondDropDownList.Validation
'Removing validation.
.Delete
'Checking if RngFirstDropDownList contains a drop-down list _
based on StrTrigger.
On Error GoTo CP_No_Drop_down_List
If RngFirstDropDownList.Validation.Formula1 = StrTrigger Then
'Adding the dropdown list.
.Add Type:=xlValidateList, _
AlertStyle:=xlValidAlertStop, _
Operator:=xlBetween, _
Formula1:="=INDIRECT(" & RngFirstDropDownList.Address & ")"
.IgnoreBlank = True
.InCellDropdown = True
.InputTitle = ""
.ErrorTitle = ""
.InputMessage = ""
.ErrorMessage = ""
.ShowInput = True
.ShowError = True
End If
CP_No_Drop_down_List:
On Error GoTo 0
End With
Next
End Sub
This code is similar to the previous one but it will in fact check if there is a first drop-down list based on the Validation.Formula1 specified. Note that if you want the second drop-down list be created acconding to StrTrigger and not to the actual indirect reference of the first drop-down list value, you can substitute the line
Formula1:="=INDIRECT(" & RngFirstDropDownList.Address & ")"
with the line
Formula1:=StrTrigger

Conditional formatting/testing headers against prescribed header list (Excel-VBA)

I use VBA rarely and am always re-learning. This is my first posting.
I am using OCR to pull in tables from PDFs to individual worksheets (usually 100-200 tabs) and I have VBA programming ready to consolidate the data based on header values. But the headers are error prone and need to be reviewed first. I want to run a VBA macro that tests headers in row 1 against a set list and highlight those headers that exactly match.
I found a great start with Conditional formatting over huge range in excel, using VBA (Dictionary Approach) which tests lists, but I am struggling to convert the code to handle rows instead of columns. (Next I plan to have it run on every tab in the workbook, but am stuck at the testing stage).
Here is my current edit of the original code to pull from rows, but I get a subscript out of range on If dict2.Exists(vals(i)) Then
Option Explicit
Sub main3()
Dim mainRng As Range, list1Rng As Range
Dim mainDict As New Scripting.Dictionary, list1Dict As New
Scripting.Dictionary 'Main is Header and list1 is prescribed header list
Set mainRng = GetRange(Worksheets("Main"), "1") '<--| get "Main" sheet row "1" range from column A right to last non empty column
Set list1Rng = GetRange(Worksheets("list1"), "1") '<--| get "list1" sheet row "1" range from column A right to last non empty column
Set mainDict = GetDictionary(mainRng)
Set list1Dict = GetDictionary(list1Rng)
ColorMatchingRange2 list1Rng, list1Dict, mainDict
End Sub
Sub ColorMatchingRange2(rng1 As Range, dict1 As Scripting.Dictionary, dict2 As Scripting.Dictionary)
Dim unionRng As Range
Dim vals As Variant
Dim i As Long
vals = rng1.Value 'oringinal code transposed with = Application.Transpose(rng1.Value)
Set unionRng = rng1.Offset(rng1.Rows.Count).Resize(1, 1)
For i = LBound(vals) To UBound(vals)
If dict2.Exists(vals(i)) Then Set unionRng = Union(unionRng, rng1(1, i))
Next i
Set unionRng = Intersect(unionRng, rng1)
If Not unionRng Is Nothing Then
With unionRng.Interior
.PatternColorIndex = xlAutomatic
.Color = 65535
.TintAndShade = 0
End With
End If
End Sub
Function GetDictionary(rng As Range) As Scripting.Dictionary
Dim dict As New Scripting.Dictionary
Dim vals As Variant
Dim i As Long
vals = rng.Value 'oringinal code transposed with=Application.Transpose(rng.Value)
On Error Resume Next
For i = LBound(vals) To UBound(vals)
dict.Add vals(i), rng(1, i).Address
Next i
On Error GoTo 0
Set GetDictionary = dict
End Function
Function GetRangeRow(ws As Worksheet, rowIndex As String) As Range
With ws '<--| reference passed worksheet
Set GetRangeRow = .Range("A" & rowIndex, .Cells(1, .Columns.Count).End(xlToLeft)) '<--| set its row "rowIndex" range from row 1 right to last non empty column
End With
End Function
More background, the VBA will be in a Control Workbook with the set header list, and the code will run on the ActiveWorkbook which will be the data across many worksheets, but I believe I've got that figured out.
Simpler approach:
Sub HighlightMatchedHeaders()
Dim rngList As Range, c As Range, v
Dim sht As Worksheet, wb As Workbook
Set wb = ActiveWorkbook 'or whatever
'set the lookup list
With wb.Sheets("list")
Set rngList = .Range("A1:A" & .Cells(Rows.Count, 1).End(xlUp).Row)
End With
For Each sht In wb.Worksheets
'ignore the "list" sheet
If sht.Name <> rngList.Worksheet.Name Then
'checking row 1
For Each c In Application.Intersect(sht.Rows(1), sht.UsedRange).Cells
v = Trim(c.Value)
If Len(v) > 0 Then
'has a header: check for match
If Not IsError(Application.Match(v, rngList, 0)) Then
c.Interior.Color = vbRed 'show match
End If
End If
Next c
End If
Next sht
End Sub

Button multiplying range data and button removing this multiplication

I have two buttons, "multiply by 0" and "show original value".
For the "multiply by 0" button, I have the below code, which works fine. What I need help with is the code for the second button, which would make the range that is multiplied by 0 back to its original number).
Public Sub MultiplyByZero()
Dim rngData As Range
Set rngData = ThisWorkbook.Worksheets("Input Sheet LC").Range("I76:O103")
rngData = Evaluate(rngData.Address & "*0")
End Sub
Thanks for your help!
The below code may helps you.
Declare a global variable named arr so you can call it from anywhere - Dim arr As Variant
Before you multiple by zero we store the values in that array - arr = .Range("A1:A5")
At any time we can bring the values back - .Range("B1:B5").Value = arr
Option Explicit
Dim arr As Variant
Public Sub MultiplyByZero()
Dim rngData As Range
Dim cell As Range
With ThisWorkbook.Worksheets("Sheet1")
arr = ""
Set rngData = .Range("A1:A5")
arr = .Range("A1:A5")
rngData = Evaluate(rngData.Address & "*0")
End With
End Sub
Public Sub RestoreValues()
With ThisWorkbook.Worksheets("Sheet1")
If Not IsEmpty(arr)=True Then
.Range("A1:A5").Value = arr
Else
MsgBox "Array is empty."
End If
End With
End Sub
Using a formula instead of a constant:
Public Sub MultiplyByZero()
Dim rngData As Range, rngWork AS Range
Set rngData = ThisWorkbook.Worksheets("Input Sheet LC").Range("I76:O103")
For Each rngWork In rngData.Cells
With rngWork
If .HasFormula Then
If Right(.Formula,2) <> "*0" Then .Formula = .Formula & "*0"
Else
.Formula = "=" & .Value & "*0"
End If
End With
Next rngWork
End Sub
Public Sub DivideByZero()
Dim rngData As Range, rngWork AS Range
Set rngData = ThisWorkbook.Worksheets("Input Sheet LC").Range("I76:O103")
For Each rngWork In rngData.Cells
With rngWork
If .HasFormula Then
If Right(.Formula,2) = "*0" Then .Formula = Mid(.Formula, 1, Len(.Formula)-2)
End If
End With
Next rngWork
End Sub
This will change 10 into =10*0 and then back into =10

Code seems to run forever and Error: block variable not set (VBA)

I am completely new to VBA so please bear with me.
I am trying to write a sub-procedure that will loop through each row in a certain column and compare to another sheet's criteria. if it contains "x", for example, then the value will be returned. However, when I try running the code, the codes run forever and causes the computer to hang.
Here's the code that I have written so far. It keeps prompting an error: Object variable and with block variable not set. PS: I have obtained errors when using 'Application.WorksheetFunction.Index' and when reading other threads, it was suggested to delete 'WorksheetFunction'. I'm not sure if that causes the problem and I would also like to clarify the rationale behind removing the words 'WorksheetFunction'
Thank you so much in advance!
Sub sub_inputData()
Dim ws As Worksheet: Set ws = ActiveSheet
Dim lastrow as range
lastrow = ws.Cells (ws.Rows.Count, 17).End (xlUp).row
Dim rng As Range
Set rng = ws.Range("Q4:Q" & lastrow)
Dim rngCell As Range
On Error Resume Next
For Each rngCell In rng
If rngCell.Offset(0, -13) = "x" Then
rngCell = Application.Index(Sheets("Data").Range _
("D805:D813"), Application.Match(rngCell.Offset(0, -15), Sheets("Data").Range _
("D805:D813"), 1))
ElseIf rngCell.Offset(0, -13) = "y" Then
rngCell = Application.Index(Sheets("Data").Range _
("D27:D34"), Application.Match(rngCell.Offset(0, -15), Sheets("Data").Range _
("D27:D34"), 1))
ElseIf rngCell.Offset(0, -13) = "z" Then
rngCell = Application.Index(Sheets("Data").Range _
("D718:D726"), Application.Match(rngCell.Offset(0, -15), Sheets("Data").Range _
("D718:D726"), 1))
Else: rngCell = vbNullString
End If
Next rngCell
Call sub_code2
Call sub_code3
Set rngCell = Nothing
Set rng = Nothing
End Sub
Couple issue with your code that has been modified here.
1) Dim lastrow As Long, not Range
2) Else: is not necessary, just use Else
3) Set rngCell = Nothing & Set rng = Nothing is not necessary. See this link for explanation
4) Since you are only checking the value of 1 cell, you can use Select Case for a moderately cleaner code.
5) On Error Resume Next is no good for de-bugging code. You want to see the errors so you can handle them. I recommend looking up the do's and dont's of that bit of code.
Sub sub_inputData()
Dim ws As Worksheet: Set ws = ActiveSheet
Dim lastrow As Long: lastrow = ws.Range("Q" & ws.Rows.Count).End(xlUp).Row
Dim rng As Range: Set rng = ws.Range("Q4:Q" & lastrow)
Dim rngCell As Range
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
For Each rngCell In rng
Select Case rngCell.Offset(0, -13)
Case "x"
rngCell = Application.Index(Sheets("Data").Range _
("D805:D813"), Application.Match(rngCell.Offset(0, -15), Sheets("Data").Range _
("D805:D813"), 1))
Case "y"
rngCell = Application.Index(Sheets("Data").Range _
("D27:D34"), Application.Match(rngCell.Offset(0, -15), Sheets("Data").Range _
("D27:D34"), 1))
Case "z"
rngCell = Application.Index(Sheets("Data").Range _
("D718:D726"), Application.Match(rngCell.Offset(0, -15), Sheets("Data").Range _
("D718:D726"), 1))
Case Else
rngCell = ""
End Select
Next rngCell
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Call sub_code2
Call sub_code3
End Sub
another possibility is using Switch() function:
Sub sub_inputData()
Dim rngCell As Range, rangeToSearch As Range
Dim val As Variant
With ActiveSheet ' reference data sheet (better: With Worksheets("MyDataSheetName"))
For Each rngCell In .Range("Q4", .Cells(.Rows.Count, "Q").End(xlUp)) ' loop throughreferenced sheet column Q cells from row 4 down to last not empty one
val = rngCell.Offset(, -13).Value2 ' store column D current cell row value
Set rangeToSearch = Sheets("Data").Range(Switch(val = "x", "D805:D813", val = "y", "D27:D34", val = "z", "D718:D726", True, "A1")) ' set range to search into with respect to stored value. set it to "A1" to signal no search is needed
If rangeToSearch.Address <> "$A$1" Then ' if search is needed
rngCell.Value = Application.Index(rangeToSearch, Application.Match(rngCell.Offset(, -15).Value2, rangeToSearch, 1)) 'do the lookup
Else
rngCell.ClearContents ' clear current cell
End If
Next
End With
sub_code2 ' no need for 'Call' keyword
sub_code3 ' no need for 'Call' keyword
End Sub
It looks like you are effectively picking a lookup range based on the value in column D, and then doing a lookup against that range based on the value in column B.
If so, you can do this entirely with formulas, which will be more efficient because it will only run on particular cells when needed (i.e. only when their inputs change).
Here's an example, using Tables and Table Notation. Tables are perfect for this, as you never have to amend your formulas to handle new data.
The formula in C2 is =VLOOKUP([#ID],CHOOSE(VLOOKUP([#Condition],Conditions,2,FALSE),X,Y,Z),2,FALSE)
That formula uses the 'Conditions' Table in E1:F3 to work out which of the other tables to do the lookup on. I've named those other tables X, Y, and Z.

Resources