Use VLookup to see if selected cell is in a range? - vba

I've seen how to say "Is cell x in range y" but since I'm using VLookup I'm not sure how to reconcile the two.
Basically, the code below does a lookup on a table that contains tips and then displays them in a specified cell. It works great. What I would like to do is specify an entire range of cells in the lookup table, then if the user selects any cell within that range the tip is displayed. As it stands, if I have a large area of say 10 cells I have to create 10 duplicate entries in the lookup table (one for each cell).
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim cellTitle As Range
Set cellTitle = Range("J2")
Dim cellTip As Range
Set cellTip = Range("J3")
If Target.Address = "$J$3:$K$5" Or Target.Address = "$J$2:$K$2" Or Target.Address = "$K$1" Then
'leave existing content in case user wants to copy tip
Else
Range("K1").Value = Target.Address
Title = Application.VLookup(Target.Address, Sheets("Settings").Range("TipsDashboard"), 2, False)
If Not IsError(Title) Then
Tip = Application.VLookup(Target.Address, Sheets("Settings").Range("TipsDashboard"), 3, False)
cellTitle.Value = Title
cellTip.Value = Tip
Else
cellTitle.Value = "Tips & Instructions"
cellTip.Value = "Try selecting various fields to get dynamic tips or instructions in this space."
End If
End If
End Sub
Here is a sample of my lookup table:
You'll notice there are ranges here, but they are merged cells.

edited: made so that it's possible to associate different cells in active sheet to the same range value in "cell" column of" Settings" sheet
Option Explicit
Private Sub Worksheet_SelectionChange(ByVal target As Range)
Dim cellTitle As Range, cellTip As Range, found As Range
Set cellTitle = Range("J2")
Set cellTip = Range("J3")
If target.address = "$J$3:$K$5" Or target.address = "$J$2:$K$2" Or target.address = "$K$1" Then
'leave existing content in case user wants to copy tip
Else
Range("K1").Value = target.address
Set found = GetCell(target, Sheets("Settings").Range("TipsDashboard").Columns(1))
If Not found Is Nothing Then
cellTitle.Value = found.Offset(, 1)
cellTip.Value = found.Offset(, 2)
Else
cellTitle.Value = "Tips & Instructions"
cellTip.Value = "Try selecting various fields to get dynamic tips or instructions in this space."
End If
End If
End Sub
Function GetCell(target As Range, sourceRng As Range) As Range
Dim cell As Range, cell2 As Range
With target
For Each cell In sourceRng.SpecialCells(xlCellTypeConstants, xlTextValues)
Set cell2 = GetRangeFromAddress(.Parent, cell.Value)
If Not cell2 Is Nothing Then
If Not Intersect(.cells, cell2) Is Nothing Then
Set GetCell = cell
Exit Function
End If
End If
Next cell
End With
End Function
Function GetRangeFromAddress(sht As Worksheet, address As String) As Range
On Error Resume Next
Set GetRangeFromAddress = sht.Range(address)
On Error GoTo 0
End Function

Related

VBA excel - return the last matching value in a column using VBA

Basically, I have a rather large (and growing) sheet of position details and I'm looking to build in a sub routine that, once a position number is entered into the relevant cell, will auto-populate the corresponding cells in the row. VLOOKUP would do the trick nicely except, when a position has multiple lines, it returns the earliest set of details--I need it to return the latest.
I can produce the answer I need using a LOOKUP function , but I can't seem to translate the function across to VBA.
Example lookup function:
LOOKUP(D17,1/($D$2:$D$10=D17),E2:E10)
This is what I have so far
Sub Worksheet_Change(ByVal Target As Excel.Range)
If Target.Column = 4 Then
actionrow = Target.Row
resulte = Application.WorksheetFunction.Lookup(2, 1 / Range("D2:D10") = Target.Value, Range("E2:E10"))
If Target.Value <> "" Then
Range("E" & actionrow).formula = resulte
End If
End If
End Sub
I think that looking at column D for a matching value with the Range.Find method would do. Start at the Target cell and use the SearchDirection:=xlPrevious option. Something will always be found. If the row it is found is not the same row as Target then use the value in column E to populate the cell right of Target.
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Columns(4), Target) Is Nothing Then
On Error GoTo bm_Safe_Exit
Application.EnableEvents = True
Dim trgt As Range, lastrw As Long
For Each trgt In Intersect(Columns(4), Target)
lastrw = Columns(4).Find(what:=trgt.Value, after:=trgt, _
lookat:=xlWhole, SearchDirection:=xlPrevious).Row
Debug.Print lastrw
If lastrw <> trgt.Row Then
trgt.Offset(0, 1) = Cells(lastrw, trgt.Column + 1).Value
End If
Next trgt
End If
bm_Safe_Exit:
Application.EnableEvents = True
End Sub
This should survive pasting multiple values into column D.
You can use .Find function with parameter SearchDirection:=xlPrevious
For case where you are searching word "AC" in a row 4:
Set FindCell = sh_wb_SF.Range("4:4").Find(What:="AC", LookIn:=xlValues, SearchDirection:=xlPrevious)
If FindCell Is Nothing Then
MsgBox ("Ooooooopppps")
End If

Fill cell in certain column with data from another sheet

I'm using a VBA script to fill a column with some data.
This script checks for the first free cell in a range and fill it with the data from another Excel worksheet.
The script starts when user double clicks on a data-cell in the other sheet.
The code of the VBA script is:
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
Dim ws2 As Worksheet
Dim cognome As Range
Dim ultB As Long
Set cognome = Me.Range("A:A")
Set ws2 = ThisWorkbook.Sheets("PUBBLICO")
ultB = IIf(ws2.Range("E8").Value = "", 8, ws2.Range("E7").End(xlDown).Row + 1)
If Not Intersect(Target, cognome) Is Nothing And Target.Value <> "" Then
ws2.Range("E" & ultB).Value = Me.Range("B" & Target.Row).Value 'ANNO
ws2.Range("F" & ultB).Value = Me.Range("A" & Target.Row).Value 'COGNOME
'ws2.Range("E4").Value = Me.Range("C" & Target.Row).Value NOME SQUADRA
End If
Set ws2 = Nothing
Cancel = True
End Sub
The problem is that this script should be optimized for another use. I've another Excel sheet that into the range of cell to fill contains a cell that is always pre-filled and it is merged.
This is the example of my Excel file:
As you can see, row 19 is always pre-filled.
So, any suggestions to correct my script to jump row 19?
Consider
ultB = IIf(ws2.Range("E8").Value = "", 8, ws2.Range("E27").End(xlUp).Row + 1)
If ultB = 19 Then ultB = 20
If ultB = 27 Then
MsgBox "Form is full"
Exit Sub
End If
If Not Intersect(Target, cognome) Is Nothing And Target.Value <> "" Then
This will find the last populated cell from the bottom up instead of from the top down. It also contains some code to notify when the form is full.

How to fill color in a cell in VBA?

I would like to color cells that have "#N/A" value in the currentsheet. In order to do this i use following macro:
Sub ColorCells()
Dim Data As Range
Dim cell As Range
Set currentsheet = ActiveWorkbook.Sheets("Comparison")
Set Data = currentsheet.Range("A2:AW1048576")
For Each cell In Data
If cell.Value = "#N/A" Then
cell.Interior.ColorIndex = 3
End If
Next
End Sub
But the line If cell.Value = "#N/A" Then gives an error: Type mismatch. Maybe someone can help to understand where is the error? Thanks
Non VBA Solution:
Use Conditional Formatting rule with formula: =ISNA(A1) (to highlight cells with all errors - not only #N/A, use =ISERROR(A1))
VBA Solution:
Your code loops through 50 mln cells. To reduce number of cells, I use .SpecialCells(xlCellTypeFormulas, 16) and .SpecialCells(xlCellTypeConstants, 16)to return only cells with errors (note, I'm using If cell.Text = "#N/A" Then)
Sub ColorCells()
Dim Data As Range, Data2 As Range, cell As Range
Dim currentsheet As Worksheet
Set currentsheet = ActiveWorkbook.Sheets("Comparison")
With currentsheet.Range("A2:AW" & Rows.Count)
.Interior.Color = xlNone
On Error Resume Next
'select only cells with errors
Set Data = .SpecialCells(xlCellTypeFormulas, 16)
Set Data2 = .SpecialCells(xlCellTypeConstants, 16)
On Error GoTo 0
End With
If Not Data2 Is Nothing Then
If Not Data Is Nothing Then
Set Data = Union(Data, Data2)
Else
Set Data = Data2
End If
End If
If Not Data Is Nothing Then
For Each cell In Data
If cell.Text = "#N/A" Then
cell.Interior.ColorIndex = 4
End If
Next
End If
End Sub
Note, to highlight cells witn any error (not only "#N/A"), replace following code
If Not Data Is Nothing Then
For Each cell In Data
If cell.Text = "#N/A" Then
cell.Interior.ColorIndex = 3
End If
Next
End If
with
If Not Data Is Nothing Then Data.Interior.ColorIndex = 3
UPD: (how to add CF rule through VBA)
Sub test()
With ActiveWorkbook.Sheets("Comparison").Range("A2:AW" & Rows.Count).FormatConditions
.Delete
.Add Type:=xlExpression, Formula1:="=ISNA(A1)"
.Item(1).Interior.ColorIndex = 3
End With
End Sub
Use conditional formatting instead of VBA to highlight errors.
Using a VBA loop like the one you posted will take a long time to process
the statement If cell.Value = "#N/A" Then will never work. If you insist on using VBA to highlight errors, try this instead.
Sub ColorCells()
Dim Data As Range
Dim cell As Range
Set currentsheet = ActiveWorkbook.Sheets("Comparison")
Set Data = currentsheet.Range("A2:AW1048576")
For Each cell In Data
If IsError(cell.Value) Then
cell.Interior.ColorIndex = 3
End If
Next
End Sub
Be prepared for a long wait, since the procedure loops through 51 million cells
There are more efficient ways to achieve what you want to do. Update your question if you have a change of mind.
Select all cells by left-top corner
Choose [Home] >> [Conditional Formatting] >> [New Rule]
Choose [Format only cells that contain]
In [Format only cells with:], choose "Errors"
Choose proper formats in [Format..] button
You need to use cell.Text = "#N/A" instead of cell.Value = "#N/A". The error in the cell is actually just text stored in the cell.

Excel VBA validation errors when a spreadsheet is proteced

I'm trying to ensure that data entered into the named range of an Excel spreadsheet is valid. To do this, I've defined a static validation list for column "A" in the range, and enabled the dropdown list for that column. Based on the option selected by the user, I add a validation object in column "B" at runtime, having a list of entries constrained by the entry in column "A". Based on the entries in columns A and B, the cell in column "C" is automatically populated.
This works fine until spreadsheet protection is enabled. At that point, attempting to select an option from the droplist in column "B" generates the following error:
"The cell or chart that you are trying to change is protected and therefore read-only. ... "
However
All cells in the range in question were unlocked prior to adding
worksheet protection.
The code explicitly removes protection prior to updating the
validation object in column "B", then replaces it once the validation
object has been added.
When a list item is selected from the droplist in column "B", the
error message fires immediately before any worksheet events occur,
making it impossible to trap or debug the error.
I have code in both the spreadsheet and in a separate code module, both or which are included below. Any ideas would be greatly appreciated
Here's the code in the Worksheet_Change() event:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim strNm As String
' there will be multiple named ranges eventually. We need to be able to distinguish
' among the various ranges so that our code executes only against the data we expect
' to manipulate - not random cells
If Not Intersect(ActiveCell, ActiveWorkbook.Names("DBAddRange").RefersToRange) Is Nothing Then
Dim rng As Range
Set rng = ActiveWorkbook.Names("DBAddRange").RefersToRange
If Target.Column = 1 Then
If FLAG_CHANGE_IN_PROGRESS = True Then Exit Sub
FLAG_CHANGE_IN_PROGRESS = True
Dim VldnList As String
VldnList = getVldtnList(Target.Value)
unlockSS ActiveSheet
Range("B" & Target.row).Clear
Range("B" & Target.row).Select
With Range("B" & Target.row).Validation
.Delete
.Add Type:=xlValidateList, Operator:=xlValidateList, Formula1:=VldnList
.IgnoreBlank = False
.InCellDropdown = True
End With
lockSS ActiveSheet
Range("B" & Target.row).Select
FLAG_CHANGE_IN_PROGRESS = False
ElseIf Target.Column = 2 Then
If FLAG_CHANGE_IN_PROGRESS = True Then Exit Sub
FLAG_CHANGE_IN_PROGRESS = True
unlockSS ActiveSheet
Dim dbHost As Variant
Dim hNmRng As Range
Set hNmRng = ActiveWorkbook.Names("valid_lookups").RefersToRange
dbHost = Application.VLookup(Target.Value, hNmRng, 2, False)
Range("C" & Target.row).Value = dbHost
lockSS ActiveSheet
FLAG_CHANGE_IN_PROGRESS = False
End If
End If
If Not Intersect(ActiveCell, ActiveWorkbook.Names("HostAddRange").RefersToRange) Is Nothing Then
End If
End Sub
Code in the external module:
Sub lockSS(ByVal sheet As Sheet1)
sheet.Protect Password:=[NOT SHOWN], UserInterfaceOnly:=True, DrawingObjects:=False
Application.EnableEvents = True
End Sub
Function getVldtnList(ByVal dbName As String)
Dim vrtmatchRow As Variant
Dim rng As Range
If dbName = "" Then
getVldtnList = ""
Exit Function
End If
' this is a pre-defined range having entries for:
' DB Name - Column 1
' DB CI ID - Column 2
' DB Host - Column 3
Set rng = ActiveWorkbook.Names("valid_db_nms").RefersToRange
' find the value of the first row in the range that matches the value
' of the dbName parm. NOTE: the final 0 parm tells the match function
' to find an exact match.
vrtmatchRow = Application.Match(dbName, rng, 0)
If IsError(vrtmatchRow) Then
' NOTE: we should NEVER get here due to the way cell validation is set up.
MsgBox "The value entered was not found in the list of valid database values. See xxx for help", vbRetryCancel, "Invalid Entry"
Else
Dim row As Long
Dim strListVals As String
Set rng = ActiveWorkbook.Names("valid_db_info").RefersToRange
row = vrtmatchRow
Do
If Len(strListVals) > 0 Then strListVals = strListVals + ","
strListVals = strListVals + rng.Cells(row, 2).Value
row = row + 1
Loop While (rng.Cells(row, 1).Value = dbName)
End If
getVldtnList = strListVals
End Function
Sub unlockSS(ByVal sheet As Sheet1)
sheet.Unprotect Password:=[NOT SHOWN]
Application.EnableEvents = False
End Sub
Clearing a range will also reset the "locked" checkbox, so you need to reset that each time
Range("B" & Target.row).Clear

Excel VBA Macro Conditional Formatting with Intersect

I wrote an Excel VBA macro to do conditional formatting using an intersect of two columns but I can't get it to work for some reason. If anyone has any idea on what I can do to fix it, I would sincerely appreciate it.
I want to highlight both the source and the target columns in which there is a match or duplicate as follows:
E Column (Target)
0.0000%
0.0000%
11.1803%
12.7775%
13.7190%
13.9841%
13.9841%
14.5698%
14.9071%
15.5746%
15.6492%
16.1355%
16.1355%
16.3123%
16.3123%
19.0693%
19.4511%
21.9089%
21.9089%
21.9089%
V Column (Source)
13.7190%
14.9240%
15.4919%
20.4521%
21.5725%
23.3319%
23.7718%
24.1871%
25.7257%
27.2166%
28.2290%
29.7543%
29.7543%
30.4968%
31.0080%
31.9022%
32.8570%
33.3333%
33.3333%
34.7434%
34.9603%
34.9927%
36.4516%
36.8697%
37.5637%
38.2046%
38.6151%
38.7298%
38.7298%
39.3830%
40.2694%
41.8330%
42.2049%
Sub Highlight_rsd_5batch()
Dim WatchRange As Range, Target As Range, cell As Range
Set Target = Range("E19:E237") 'change column ref as required
Set WatchRange = Range("V19:V237")
For Each cell In Target.Cells
If Intersect(Target, WatchRange) Is Nothing Then
cell.Interior.ColorIndex = xlNone
Else: cell.EntireRow.Interior.ColorIndex = 6
End If
Next cell
End Sub
The Intersect function checks to see if the two ranges have any cells in common, not if they have values in common. You could use the CountIf function instead:
Sub Highlight_rsd_5batch()
Dim WatchRange As Range, Target As Range, cell As Range
Set Target = Range("E19:E237") 'change column ref as required
Set WatchRange = Range("V19:V237")
For Each cell In Target.Cells
If Application.WorksheetFunction.CountIf(WatchRange,cell.Value) > 0 Then
cell.Interior.ColorIndex = 6
For Each watchCell in WatchRange.Cells
If watchCell.value = cell.Value Then: watchCell.Interior.ColorIndex = 6
Next watchCell
Else: cell.EntireRow.Interior.ColorIndex = xlNone
End If
Next cell
End Sub
This task does not really require the use of VBA and could be accomplished using the same formulas in the Conditional Formatting tools under Format>Conditional Formatting. See the linked tutorial for more help.