Insert value based on drop down list from cell next to matched one - excel

I have a sample table (B2:C4) with a couple of defined values "XXX = 10, YYY = 20, ZZZ = 30".
I have the second table (E2:F10) with drop down list in the column "E".
I need to copy value based on drop down list to column "F". It means for example when I select E3 = "XXX" from drop down list it copies appropriate value from column "C". In the example on the attached picture B1 = "XXX" -> C1 = "10" so the value will be copied to F3).
The problem is that the drop down list includes also another items than in the column "B2:B4" so I can customize the entry in the table.
I created working code but the issue is when I change any value in the column C2:C4 the value in the column F2:F10 does not change.
Any help would be appreciated.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Res As Variant
If Target.CountLarge > 1 Then Exit Sub
If Not Intersect(Target, Range("E2:E10")) Is Nothing Then
Res = Evaluate("INDEX(C2:C4,MATCH(" & Target.Address & ",B2:B4,0))")
If Not IsError(Res) Then Target.Offset(, 1) = Res
End If
End Sub
Sample XLSM file
This is how I edited the sample table and the code according #Variatus:
The module code:
Enum Nws ' worksheet where 'Data' values are used
' 060
NwsFirstDataRow = 7 ' change to suit
NwsTrigger = 6 ' Trigger column (5 = column E)
NwsTarget = 8 ' Target column (no value = previous + 1)
End Enum
Enum Nta ' columns of range 'Data'
' 060
NtaId = 1
NtaVal = 3
End Enum
And the sheet code:
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
' 060
Dim Ws As Worksheet ' the sheet on which the "Data" range resides
Dim Rng As Range
Dim Tmp As Variant
' skip action if more than 1 cell was changed
If Target.CountLarge > 1 Then Exit Sub
Set Rng = Range(Cells(NwsFirstDataRow, NwsTrigger), _
Cells(Rows.Count, NwsTrigger).End(xlUp))
If Not Application.Intersect(Target, Rng) Is Nothing Then
Set Ws = ThisWorkbook.Sheets("test") ' change to match your facts
Set Rng = Ws.Range("B2:D4") ' change to match your facts
With Application
Tmp = .VLookup(Target.Value, Rng, 3, False)
If Not IsError(Tmp) Then
.EnableEvents = False ' suppress 'Change' event
Cells(Target.Row, NwsTarget).Value = Tmp
.EnableEvents = True
End If
End With
End If
' skip action if more than one cell was changed
If Target.CountLarge > 1 Then Exit Sub
If Not Application.Intersect(Target, Rng.Columns(NtaVal)) Is Nothing Then
UpdateCategory Target.Offset(0, -1).Resize(, 2).Value
End If
End Sub
Private Sub Worksheet_Deactivate()
' 060
Dim TgtWs As Worksheet ' the Tab on which 'Data' was used
Dim Cat As Variant ' 'Data' category (2 cells as Nta)
Dim R As Long ' loop counter: rows
Set TgtWs = ThisWorkbook.Sheets("test") ' change to match your facts
With Range("B2:D4") ' change to match your facts
For R = 1 To .Rows.Count
Cat = .Rows(R).Value
UpdateCategory Cat
Next R
End With
End Sub
Private Sub UpdateCategory(Cat As Variant)
' 060
Dim TgtWs As Worksheet ' the Tab on which 'Data' was used
Dim Fnd As Range ' matching cell
Dim FirstFound As Long ' row of first match
Dim Rng As Range
Set TgtWs = ThisWorkbook.Sheets("test") ' change to match your facts
Application.EnableEvents = False
With TgtWs
Set Rng = .Range(.Cells(NwsFirstDataRow, NwsTrigger), _
.Cells(.Rows.Count, NwsTrigger).End(xlUp))
With Rng
Set Fnd = .Find(Cat(1, NtaId), LookIn:=xlValues, LookAt:=xlWhole)
If Not Fnd Is Nothing Then
FirstFound = Fnd.Row
Do
TgtWs.Cells(Fnd.Row, NwsTarget).Value = Cat(1, NtaVal)
Set Fnd = .FindNext(Fnd)
If Fnd Is Nothing Then Exit Do
Loop While Fnd.Row <> FirstFound
End If
End With
End With
Application.EnableEvents = True
End Sub

The code below differs from the selected answer in the following respects.
All the action now takes place on one sheet, as per your original question. Therefore all the code must now be placed in one location, on the code sheet of the worksheet on which everything transpires. In consequence thereof all worksheet specification could be removed from the code.
An extra column was interjected in the Data range of which, however, only the first and third columns are used, as identified in the Enum Nta.
Option Explicit
Enum Nws ' worksheet where 'Data' values are used
' 060-2
NwsFirstDataRow = 2 ' change to suit
NwsTrigger = 5 ' Trigger column (5 = column E)
NwsTarget ' Target column (no value = previous + 1)
End Enum
Enum Nta ' columns of range 'Data'
' 060
NtaId = 1 ' 1st column of 'Data' range
NtaVal = 3 ' 3rd column of 'Data' range
End Enum
Private Sub Worksheet_Change(ByVal Target As Range)
' 060-2
Dim Rng As Range
Dim Tmp As Variant
' skip action if more than 1 cell was changed
If Target.CountLarge > 1 Then Exit Sub
Set Rng = Range(Cells(NwsFirstDataRow, NwsTrigger), _
Cells(Rows.Count, NwsTrigger).End(xlUp))
If Not Application.Intersect(Target, Rng) Is Nothing Then
With Application
Tmp = .VLookup(Target.Value, Range("Data"), NtaVal, False)
If Not IsError(Tmp) Then
.EnableEvents = False ' suppress 'Change' event
Cells(Target.Row, NwsTarget).Value = Tmp
.EnableEvents = True
End If
End With
Else
Set Rng = Range("Data") ' change to suit
If Not Application.Intersect(Target, Rng.Columns(NtaVal)) Is Nothing Then
UpdateCategory Cells(Target.Row, Rng.Column).Resize(1, NtaVal).Value
End If
End If
End Sub
Private Sub Worksheet_activate()
' 060-2
Dim TgtWs As Worksheet ' the Tab on which 'Data' was used
Dim Cat As Variant ' 'Data' category (2 cells as Nta)
Dim R As Long ' loop counter: rows
Set TgtWs = Sheet1 ' change to match your facts
With Range("Data") ' change to match your facts
For R = 1 To .Rows.Count
Cat = .Rows(R).Value
UpdateCategory Cat
Next R
End With
End Sub
Private Sub UpdateCategory(Cat As Variant)
' 060-2
Dim Fnd As Range ' matching cell
Dim FirstFound As Long ' row of first match
Dim Rng As Range
Application.EnableEvents = False
Set Rng = Range(Cells(NwsFirstDataRow, NwsTrigger), _
Cells(Rows.Count, NwsTrigger).End(xlUp))
With Rng
Set Fnd = .Find(Cat(1, NtaId), LookIn:=xlValues, LookAt:=xlWhole)
If Not Fnd Is Nothing Then
FirstFound = Fnd.Row
Do
Cells(Fnd.Row, NwsTarget).Value = Cat(1, NtaVal)
Set Fnd = .FindNext(Fnd)
If Fnd Is Nothing Then Exit Do
Loop While Fnd.Row <> FirstFound
End If
End With
Application.EnableEvents = True
End Sub

My answer could be improved if you use Excel Tables
Also some parts of the code could be refactored. For example you should add some error handling.
But, this should get you started:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim cell As Range
Dim watchRange As Range
Dim cellFormula As String
' Define the watched range
Set watchRange = Me.Range("E2:E10")
' Do this for each cell changed in target
For Each cell In Target.Cells
' Check if cell is in watched range
If Not Intersect(cell, watchRange) Is Nothing Then
cellFormula = "=INDEX(C2:C4,MATCH(" & Target.Address & ",B2:B4,0))"
' Check if formula doesn't return an error (this could be imporoved?)
If Not IsError(cellFormula) Then
' Don't fire the change event twice
Application.EnableEvents = False
cell.Offset(, 1).Formula = cellFormula
Application.EnableEvents = False
End If
End If
Next cell
End Sub
Let me know if this is what you needed and if it works.

If you wish to maintain a permanent link between your table B2:C4 and the results in column F you need to establish a robust system for updating changes. In effect, column F must not only change with the selection in column E but also with updates in column C. Presuming that these data are on different sheets in your project different worksheet events must be captured and coordinated. To be safe you should also update all occasionally, such as on Workbook_Open or Worksheet_Activate in case an update was missed due to a system crash.
None of that is particularly difficult to program but Excel offers a solution without VBA that is so stunningly better that it can't be ignored. Here it is.
Create a named range C2:C4. I called it "Data" and made it dynamic so that it can expand without requiring my attention.
Use the first column of this range to feed the data validation drop-down: =INDEX(Data,,1)
Use this formula in column F, =VLOOKUP(E2,Data,2,FALSE)
All conditions laid out above are met.

I'm trying to make it simple. So here is the origin table from my answer above where I just extend Data range and values in the column "C" are now in the column "D". Everything works except when I change value in the column "D" nothing happens:
sample table extended
Module code:
Enum Nws ' worksheet where 'Data' values are used
' 060
NwsFirstDataRow = 2 ' change to suit
NwsTrigger = 5 ' Trigger column (5 = column E)
NwsTarget ' Target column (no value = previous + 1)
End Enum
Enum Nta ' columns of range 'Data'
' 060
NtaId = 1
NtaVal
End Enum
Test sheet code:
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
' 060
Dim Ws As Worksheet ' the sheet on which the "Data" range resides
Dim Rng As Range
Dim Tmp As Variant
' skip action if more than 1 cell was changed
If Target.CountLarge > 1 Then Exit Sub
Set Rng = Range(Cells(NwsFirstDataRow, NwsTrigger), _
Cells(Rows.Count, NwsTrigger).End(xlUp))
If Not Application.Intersect(Target, Rng) Is Nothing Then
Set Ws = ThisWorkbook.Sheets("test") ' change to match your facts
Set Rng = Ws.Range("Data") ' change to match your facts
With Application
Tmp = .VLookup(Target.Value, Rng, 3, False)
If Not IsError(Tmp) Then
.EnableEvents = False ' suppress 'Change' event
Cells(Target.Row, NwsTarget).Value = Tmp
.EnableEvents = True
End If
End With
End If
' skip action if more than one cell was changed
If Target.CountLarge > 1 Then Exit Sub
If Not Application.Intersect(Target, Rng.Columns(NtaVal)) Is Nothing Then
UpdateCategory Target.Offset(0, -1).Resize(, 2).Value
End If
End Sub
Private Sub Worksheet_Deactivate()
' 060
Dim TgtWs As Worksheet ' the Tab on which 'Data' was used
Dim Cat As Variant ' 'Data' category (2 cells as Nta)
Dim R As Long ' loop counter: rows
Set TgtWs = ThisWorkbook.Sheets("test") ' change to match your facts
With Range("Data") ' change to match your facts
For R = 1 To .Rows.Count
Cat = .Rows(R).Value
UpdateCategory Cat
Next R
End With
End Sub
Private Sub UpdateCategory(Cat As Variant)
' 060
Dim TgtWs As Worksheet ' the Tab on which 'Data' was used
Dim Fnd As Range ' matching cell
Dim FirstFound As Long ' row of first match
Dim Rng As Range
Set TgtWs = ThisWorkbook.Sheets("test") ' change to match your facts
Application.EnableEvents = False
With TgtWs
Set Rng = .Range(.Cells(NwsFirstDataRow, NwsTrigger), _
.Cells(.Rows.Count, NwsTrigger).End(xlUp))
With Rng
Set Fnd = .Find(Cat(1, NtaId), LookIn:=xlValues, LookAt:=xlWhole)
If Not Fnd Is Nothing Then
FirstFound = Fnd.Row
Do
TgtWs.Cells(Fnd.Row, NwsTarget).Value = Cat(1, NtaVal)
Set Fnd = .FindNext(Fnd)
If Fnd Is Nothing Then Exit Do
Loop While Fnd.Row <> FirstFound
End If
End With
End With
Application.EnableEvents = True
End Sub

As promised above, the VBA solution is a lot more complicated than the one with VLOOKUP. But you can start in the same way. Create a named range where you store your "Categories" as I came to call them after I named the range "Data". This is a range with 2 columns, exactly as B2:C4 in your example. You can have this range on the same sheet as the action but I programmed in the assumption that it would be on another sheet.
Next, please install these enumerations in a standard code module. The first Enum identifies parts of the worksheet on which the range E:F of your example resides. It specifies row 2 as the first row with data, meaning row 1 will be omitted from scrutiny, and, in fact, assigns the job of columns 5 and 6, (E and F) of your example to the same columns in my code's output. You can change all of these values. The second enum identifies the columns of the 'Data' range. Naming these columns helps read the code. Changing the numbers makes no sense.
Enum Nws ' worksheet where 'Data' values are used
' 060
NwsFirstDataRow = 2 ' change to suit
NwsTrigger = 5 ' Trigger column (5 = column E)
NwsTarget ' Target column (no value = previous + 1)
End Enum
Enum Nta ' columns of range 'Data'
' 060
NtaId = 1
NtaVal
End Enum
The code below must be pasted to the code sheet of the worksheet on which you have the Data Validation drop-down. That is the one holding columns E:F of your example. Don't paste this code in the same module as the enumerations or any other standard code module. It must be the module assigned to the worksheet with these data. The code will check if an entry made in column E is present in 'Data' and get the value from there if it is. Else it will do nothing. Observe that this code needs to know where the category data are, worksheet and range name. I've marked the lines where you can change the specs.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
' 060
Dim Ws As Worksheet ' the sheet on which the "Data" range resides
Dim Rng As Range
Dim Tmp As Variant
' skip action if more than 1 cell was changed
If Target.CountLarge > 1 Then Exit Sub
Set Rng = Range(Cells(NwsFirstDataRow, NwsTrigger), _
Cells(Rows.Count, NwsTrigger).End(xlUp))
If Not Application.Intersect(Target, Rng) Is Nothing Then
Set Ws = Sheet1 ' change to match your facts
Set Rng = Ws.Range("Data") ' change to match your facts
With Application
Tmp = .VLookup(Target.Value, Rng, 2, False)
If Not IsError(Tmp) Then
.EnableEvents = False ' suppress 'Change' event
Cells(Target.Row, NwsTarget).Value = Tmp
.EnableEvents = True
End If
End With
End If
End Sub
Finally, there is code to go into the worksheet on which you have the category data (B2:C4 in your example). This, too, must be the code sheet attached to that worksheet, not a standard code module. There is a procedure called Worksheet_Change which is the same as a corresponding procedure for the other sheet. Since there can't be two procedures of the same name in the same module these two procedures would have to be merged if you eventually need both the 'Data' and the validations on the same worksheet as you have them in your example. The code is laid out to have them on separate sheets.
Option Explicit
Private Sub Worksheet_Deactivate()
' 060
Dim TgtWs As Worksheet ' the Tab on which 'Data' was used
Dim Cat As Variant ' 'Data' category (2 cells as Nta)
Dim R As Long ' loop counter: rows
Set TgtWs = Sheet2 ' change to match your facts
With Range("Data") ' change to match your facts
For R = 1 To .Rows.Count
Cat = .Rows(R).Value
UpdateCategory Cat
Next R
End With
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
' 060
Dim Ws As Worksheet ' the Tab on which 'Data' resides
Dim Rng As Range
' skip action if more than one cell was changed
If Target.CountLarge > 1 Then Exit Sub
Set Ws = Sheet1 ' change to suit
Set Rng = Ws.Range("Data") ' change to suit
If Not Application.Intersect(Target, Rng.Columns(NtaVal)) Is Nothing Then
UpdateCategory Target.Offset(0, -1).Resize(, NtaVal).Value
End If
End Sub
Private Sub UpdateCategory(Cat As Variant)
' 060
Dim TgtWs As Worksheet ' the Tab on which 'Data' was used
Dim Fnd As Range ' matching cell
Dim FirstFound As Long ' row of first match
Dim Rng As Range
Set TgtWs = Sheet2 ' change to match your facts
Application.EnableEvents = False
With TgtWs
Set Rng = .Range(.Cells(NwsFirstDataRow, NwsTrigger), _
.Cells(.Rows.Count, NwsTrigger).End(xlUp))
With Rng
Set Fnd = .Find(Cat(1, NtaId), LookIn:=xlValues, LookAt:=xlWhole)
If Not Fnd Is Nothing Then
FirstFound = Fnd.Row
Do
TgtWs.Cells(Fnd.Row, NwsTarget).Value = Cat(1, NtaVal)
Set Fnd = .FindNext(Fnd)
If Fnd Is Nothing Then Exit Do
Loop While Fnd.Row <> FirstFound
End If
End With
End With
Application.EnableEvents = True
End Sub
These three procedures work to maintain synch between the categories and the data, meaning, if a change occurs in the categories the data should reflect them. The key to this is the procedure UpdateCategory which looks for the category name in the data and ensures that it's the same as in the categories table. This procedure is called in two different ways.
One is when the value of a category is changed. It will then update that particular category. The other I have timed with the deactivation event of the worksheet. At that time all categories are updated, just in case an individual update has failed earlier. If you have a lot of data, or a lot of categories, this may prove slow. If so, there are ways to make it work faster.
I draw your attention to the need to specify both worksheets and the name of the 'Data' range in these procedures as well. The locations are marked. Please look for them.

Related

Sort and copy data based on a date

I'm trying to create a macro that would allow me to extract data from an array to send an email.
The sorting must be done according to the comments. The goal is to detect the date of the day, for example today 22/08/2022, and to extract the line in another page by erasing in the comment box, the comments which are not dated today , ie have the whole line with the last comment in the comment box. On the other hand, if there is no comment dating from today, the line must not be selected or copied.
However, no matter what code I enter, I cannot sort the data according to the date and only retrieve today's comment, knowing that in this excel I only have a few lines but I have to be able to use it for 1000 rows.
How should I go about it?
Thank you and have good day
My example table
The result that I try to have
Solution
Option Explicit
Sub TodaysComments()
Dim srcWs As Worksheet
Dim destWs As Worksheet
Dim myCell As Range
Dim rngToCopy As Range
' Set source and find comments column
Set srcWs = Worksheets("Source")
Set myCell = srcWs.Cells.Find("Commentaires")
If myCell Is Nothing Then
MsgBox "Cannot find column 'Commentaires'!", vbCritical
Exit Sub
End If
' Set and clear destination
Set destWs = Worksheets("Filtered")
destWs.Cells.Clear
' Copy Header
RngCopy CurrentRow(myCell), destWs.Range("A1")
' Loop over comments
NextCell myCell
Do While myCell.Value <> ""
' Search for today's date
If Not myCell.Find(Today) Is Nothing Then
' Aggregate rows to copy
Set rngToCopy = RngUnion(rngToCopy, CurrentRow(myCell))
End If
NextCell myCell
Loop
' No comments today
If rngToCopy Is Nothing Then
MsgBox "No 'Commentaires' rows meet criteria!", vbInformation
Exit Sub
End If
' Copy rows to destination
RngCopy rngToCopy, destWs.Range("A2")
' Clear old comments from destination
Set myCell = destWs.Cells(2, myCell.Column)
Do While myCell.Value <> ""
ClearOldComments myCell
NextCell myCell
Loop
MsgBox "Done!", vbInformation
End Sub
Private Sub RngCopy(SrcRng As Range, DestRng As Range)
SrcRng.Copy
DestRng.PasteSpecial xlPasteAll
DestRng.Range("A1").PasteSpecial xlPasteColumnWidths
Application.CutCopyMode = False
End Sub
Private Function CurrentRow(myCell As Range) As Range
Set CurrentRow = Range(myCell, myCell.Worksheet.Cells(myCell.Row, 1))
End Function
Private Sub NextCell(myCell As Range)
Set myCell = myCell.Offset(1, 0)
End Sub
Function RngUnion(Rng1 As Range, Rng2 As Range) As Range
If Rng2 Is Nothing Then Err.Raise 91 ' Object variable not set
If Rng1 Is Nothing Then
Set RngUnion = Rng2
Exit Function
End If
Set RngUnion = Union(Rng1, Rng2)
End Function
Private Sub ClearOldComments(myCell As Range)
Dim Comments As Variant
Dim i As Long
Comments = VBA.Split(myCell.Value, vbNewLine)
For i = LBound(Comments) To UBound(Comments)
' NOTE: We assume there is only one comment per day.
If InStr(Comments(i), Today) Then
myCell.Value = Comments(i)
Exit Sub
End If
Next
' Should not be possible
Err.Raise 93 ' Invalid pattern string
End Sub
Function Today() As String
Today = FormatDateTime(Date, vbGeneralDate)
End Function

Apply VBA script, to format cells, to multiple rows and cells

I managed to get this code:
Sub ColorChange()
Dim ws As Worksheet
Set ws = Worksheets(2)
clrOrange = 39423
clrWhite = RGB(255, 255, 255)
If ws.Range("D19").Value = "1" And ws.Range("E19").Value = "1" Then
ws.Range("D19", "E19").Interior.Color = clrOrange
ElseIf ws.Range("D19").Value = "0" Or ws.Range("E19").Value = "0" Then
ws.Range("D19", "E19").Interior.Color = clrWhite
End If
End Sub
This works, but now I need this code to work in 50 rows and 314 cells, but every time only on two cells so, D19+E19, D20+E20, etc. Endpoint is DB314+DC314.
Is there a way, without needing to copy paste this code and replacing all the row and cells by hand?
It also would be nice that if the value in the two cells is anything other than 1+1 the cell color changes back to white.
EDIT: The solution thanks to #VBasic2008.
I added the following to the sheet's code to get the solution to work automatically:
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("D19:DC314")) Is Nothing Then
Call ColorChange
End If
End Sub
And because Interior.Color removes borders I added the following sub:
Sub vba_borders()
Dim iRange As Range
Dim iCells As Range
Set iRange = Range("D19:DC67,D70:DC86,D89:DC124,D127:DC176,D179:DC212,D215:DC252,D255:DC291,D294:DC314")
For Each iCells In iRange
iCells.BorderAround _
LineStyle:=xlContinuous, _
Weight:=xlThin
Next iCells
End Sub
The Range is a bit different to exclude some rows.
Compare Values in the Two Cells of Column Pairs
Option Explicit
Sub ColorChange()
Const rgAddress As String = "D19:DC314"
Const Orange As Long = 39423
Const White As Long = 16777215
Dim wb As Workbook ' (Source) Workbook
Set wb = ThisWorkbook ' The workbook containing this code.
Dim rg As Range ' (Source) Range
Set rg = wb.Worksheets(2).Range(rgAddress) ' Rather use tab name ("Sheet2").
Dim cCount As Long ' Columns Count
cCount = rg.Columns.Count
Dim brg As Range ' Built Range
Dim rrg As Range ' Row Range
Dim crg As Range ' Two-Cell Range
Dim j As Long ' (Source)/Row Range Columns Counter
For Each rrg In rg.Rows
For j = 2 To cCount Step 2
Set crg = rrg.Cells(j - 1).Resize(, 2)
If crg.Cells(1).Value = 1 And crg.Cells(2).Value = 1 Then
If brg Is Nothing Then
Set brg = crg
Else
Set brg = Union(brg, crg)
End If
End If
Next j
Next rrg
Application.ScreenUpdating = False
rg.Interior.Color = White
If Not brg Is Nothing Then
brg.Interior.Color = Orange
End If
Application.ScreenUpdating = True
End Sub

How to calculate formula and insert value to respective cell using change event

I was asking for help with the code in the following question:
Insert value based on drop down list from cell next to matched one
With a big effort of #Variatus who helped me to find the solution I have working code to "insert value based on drop down list from cell next to matched one" which works in both ways. When I was playing around to to get deep in the code I tried to figure out how to use Worksheet_Change for formula calculation. I wanted to avoid complex code so I'm checking column "D" with dropdown list values and when this is changed then calculated formula value in the column "E" is copied to matched cell in the next table. Everything works like a charm on my "Sheet1". But when I tried to replicate the code to my "Sheet2" it stopped working this way even I didn't change anything. Maybe I'm missing something but I can't figure out what it is. I tried start over from the beginning but still nothing.
And here are two PrtScns of "Sheet1" and "Sheet2":
Sheet1
Sheet2
And this the code I used for Sheet1 which works with no issue:
Option Explicit
Enum Nws ' worksheet where 'Data' values are used
' 060-2
NwsFirstDataRow = 10 ' change to suit
NwsTrigger = 8 ' Trigger column (5 = column E)
NwsTarget = 10 ' Target column (no value = previous + 1)
End Enum
Enum Nta ' columns of range 'Data'
' 060
NtaId = 1 ' 1st column of 'Data' range
NtaVal = 4 ' 3rd column of 'Data' range
End Enum
Private Sub Worksheet_Change(ByVal Target As Range)
' 060-2
Dim Rng As Range
Dim Tmp As Variant
' skip action if more than 1 cell was changed
If Target.CountLarge > 1 Then Exit Sub
Set Rng = Range(Cells(NwsFirstDataRow, NwsTrigger), _
Cells(Rows.Count, NwsTrigger).End(xlUp))
If Not Application.Intersect(Target, Rng) Is Nothing Then
With Application
Tmp = .VLookup(Target.Value, Range("Data"), NtaVal, False)
If Not IsError(Tmp) Then
.EnableEvents = False ' suppress 'Change' event
Cells(Target.Row, NwsTarget).Value = Tmp
.EnableEvents = True
End If
End With
Else
Set Rng = Range("B2:E4") ' change to suit
If Not Application.Intersect(Target, Rng.Columns(NtaVal - 1)) Is Nothing Then
' If Not Application.Intersect(Target, Range("D2:D4")) Is Nothing Then
UpdateCategory Cells(Target.Row, Rng.Column).Resize(1, NtaVal).Value
End If
End If
End Sub
Private Sub Worksheet_activate()
' 060-2
Dim TgtWs As Worksheet ' the Tab on which 'Data' was used
Dim Cat As Variant ' 'Data' category (2 cells as Nta)
Dim R As Long ' loop counter: rows
Set TgtWs = Sheet1 ' change to match your facts
With Range("Data") ' change to match your facts
For R = 1 To .Rows.Count
Cat = .Rows(R).Value
UpdateCategory Cat
Next R
End With
End Sub
Private Sub UpdateCategory(Cat As Variant)
' 060-2
Dim Fnd As Range ' matching cell
Dim FirstFound As Long ' row of first match
Dim Rng As Range
Application.EnableEvents = False
Set Rng = Range(Cells(NwsFirstDataRow, NwsTrigger), _
Cells(Rows.Count, NwsTrigger).End(xlUp))
With Rng
Set Fnd = .Find(Cat(1, NtaId), LookIn:=xlValues, LookAt:=xlWhole)
If Not Fnd Is Nothing Then
FirstFound = Fnd.Row
Do
Cells(Fnd.Row, NwsTarget).Value = Cat(1, NtaVal)
Set Fnd = .FindNext(Fnd)
If Fnd Is Nothing Then Exit Do
Loop While Fnd.Row <> FirstFound
End If
End With
Application.EnableEvents = True
End Sub
And the code for Sheet2 which doesn't:
Option Explicit
Enum Nws1 ' worksheet where 'Data1' values are used
' 060-2
Nws1FirstData1Row = 16 ' change to suit
Nws1Trigger = 18 ' Trigger column (5 = column E)
Nws1Target = 20 ' Target column (no value = previous + 1)
End Enum
Enum Nta1 ' columns of range 'Data1'
' 060
Nta1Id = 1 ' 1st column of 'Data1' range
Nta1Val = 5 ' 3rd column of 'Data1' range
End Enum
Private Sub Worksheet_Change(ByVal Target As Range)
' 060-2
Dim Rng As Range
Dim Tmp As Variant
' skip action if more than 1 cell was changed
If Target.CountLarge > 1 Then Exit Sub
Set Rng = Range(Cells(Nws1FirstData1Row, Nws1Trigger), _
Cells(Rows.Count, Nws1Trigger).End(xlUp))
If Not Application.Intersect(Target, Rng) Is Nothing Then
With Application
Tmp = .VLookup(Target.Value, Range("Data1"), Nta1Val, False)
If Not IsError(Tmp) Then
.EnableEvents = False ' suppress 'Change' event
Cells(Target.Row, Nws1Target).Value = Tmp
.EnableEvents = True
End If
End With
Else
Set Rng = Range("M19:M25") ' change to suit
If Not Application.Intersect(Target, Rng.Columns(Nta1Val - 2)) Is Nothing Then
UpdateCategory Cells(Target.Row, Rng.Column).Resize(1, Nta1Val).Value
End If
End If
End Sub
Private Sub Worksheet_activate()
' 060-2
Dim TgtWs As Worksheet ' the Tab on which 'Data1' was used
Dim Cat As Variant ' 'Data1' category (2 cells as Nta1)
Dim R As Long ' loop counter: rows
Set TgtWs = Sheet2 ' change to match your facts
With Range("Data1") ' change to match your facts
For R = 1 To .Rows.Count
Cat = .Rows(R).Value
UpdateCategory Cat
Next R
End With
End Sub
Private Sub UpdateCategory(Cat As Variant)
' 060-2
Dim Fnd As Range ' matching cell
Dim FirstFound As Long ' row of first match
Dim Rng As Range
Application.EnableEvents = False
Set Rng = Range(Cells(Nws1FirstData1Row, Nws1Trigger), _
Cells(Rows.Count, Nws1Trigger).End(xlUp))
With Rng
Set Fnd = .Find(Cat(1, Nta1Id), LookIn:=xlValues, LookAt:=xlWhole)
If Not Fnd Is Nothing Then
FirstFound = Fnd.Row
Do
Cells(Fnd.Row, Nws1Target).Value = Cat(1, Nta1Val)
Set Fnd = .FindNext(Fnd)
If Fnd Is Nothing Then Exit Do
Loop While Fnd.Row <> FirstFound
End If
End With
Application.EnableEvents = True
End Sub
Any help would be well appreciated!
This is an excerpt from the original code.
Set Rng = Range("Data") ' change to suit
If Not Application.Intersect(Target, Rng.Columns(NtaVal)) Is Nothing Then
UpdateCategory Cells(Target.Row, Rng.Column).Resize(1, NtaVal).Value
End If
Below is the corresponding part from your code behind Sheet1.
Set Rng = Range("B2:E4") ' change to suit
If Not Application.Intersect(Target, Rng.Columns(NtaVal - 1)) Is Nothing Then
UpdateCategory Cells(Target.Row, Rng.Column).Resize(1, NtaVal).Value
End If
And here is the exact same part from your code behind Sheet2.
Set Rng = Range("M19:M25") ' change to suit
If Not Application.Intersect(Target, Rng.Columns(Nta1Val - 2)) Is Nothing Then
UpdateCategory Cells(Target.Row, Rng.Column).Resize(1, Nta1Val).Value
End If
Now you can analyse what happened.
The Data range was declared by name to relieve you of the chore to check the address multiple times. You need it on the sheet and you need it in the code. You set it once and it will be correct wherever you use the name.
In your own rendering of the same code you changed the name to a sheet address: Range("B2:E4"). It's true that it makes no difference, except that you have to check to be sure that Range("B2:E4") really is the same as Data. It's extra work but it works.
with Set Rng = Range("M19:M25") you walked into the trap which you set for yourself. By your design this is supposed to be the named range Data1. But it isn't. Data1 has 5 columns and the range you declare in its place has only 1.
From the above analysis it's very clear by which logic you arrived at the mistake. You didn't "own" the named range. Therefore you strove to replace it with coordinates. In the process you gave up the safety that comes from using named variables and then failed to put in the extra checking needed when you take extra risk.
Please observe the missing intent for the line UpdateCategory Cells(Target... in your code for Sheet2. The indent serves to show the beginning and End of the IF statement. One would expect a beginner to need more of such help reading code than an expert. Truth is however that all beginners (your good-self included) think it makes no difference, and it really doesn't, but more advanced programmers know that they need clarity above all else. You can tell the experience of a programmer from the indenting he applies in his code. It's a very reliable indicator.

Get row number of first empty cell in column and store that value in other cell

I want to find row number of first empty cell in column and store that row number in Cell Z1.
I tried with Following macro code but it goes into loop forever.
As soon as it tries to set the value in Cell Z1 it again goes into worksheet_change event again and then again in for loop.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim ws As Worksheet
Set ws = ActiveSheet
For Each cell In ws.Columns(3).Cells
If IsEmpty(cell) = True Then Range("$Z$1").Value = cell.Row: Exit For
Next cell
End Sub
Please help to resolve this.
Thanks
Maybe this code is of any help
Option Explicit
Function firstEmptyCell(col As Long, Optional ws As Worksheet) As Range
If ws Is Nothing Then
Set ws = ActiveSheet
End If
Dim rg As Range
Set rg = ws.Cells(1, col)
If Len(rg.Value) = 0 Then
Set rg = rg.Offset
Else
If Len(rg.Offset(1).Value) = 0 Then
Set rg = rg.Offset(1)
Else
Set rg = rg.End(xlDown)
Set rg = rg.Offset(1)
End If
End If
Set firstEmptyCell = rg
End Function
And the Event code is
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo EH
If Target.Column <> 12 Then
Exit Sub
End If
Application.EnableEvents = False
Range("Z1").Value = firstEmptyCell(12).Row
EH:
Application.EnableEvents = True
End Sub
Update: Based on the comments regarding the pitfalls of the change event one could change firstEmptyCell slightly and use a UDF only
Function firstEmptyCellA(col As Long, Optional ws As Worksheet) As Long
On Error GoTo EH
If ws Is Nothing Then
Set ws = ActiveSheet
End If
Application.Volatile
Dim rg As Range
Set rg = ws.Cells(1, col)
If Len(rg.Value) = 0 Then
Set rg = rg.Offset
Else
If Len(rg.Offset(1).Value) = 0 Then
Set rg = rg.Offset(1)
Else
Set rg = rg.End(xlDown)
Set rg = rg.Offset(1)
End If
End If
firstEmptyCellA = rg.Row
Exit Function
EH:
firstEmptyCellA = 0
End Function
Tricky Enable Events
This is triggered only when a cell in the 12th column (L) is changed, otherwise there is no need for it. If you have formulas there, then this will not work and you'll have to use the Worksheet_Calculate event.
Row of First Empty Cell in Column
Option Explicit
' Row of First Empty Cell in Column
Private Sub Worksheet_Change(ByVal Target As Range)
Const TargetCell As String = "Z1"
Const TargetColumn As Variant = 12 ' (or "L")
Dim rng As Range
If Intersect(Columns(TargetColumn), Target) Is Nothing Then Exit Sub
Application.EnableEvents = False
Set rng = Columns(TargetColumn).Find(What:="", _
After:=Cells(Rows.Count, TargetColumn), LookIn:=xlValues)
If rng Is Nothing Then
Range(TargetCell).Value = 0 ' Full column. No empty cells.
Else
Range(TargetCell).Value = rng.Row
End If
Application.EnableEvents = True
End Sub
Row of First Empty Cell After Last Non-Empty Cell in Column
Option Explicit
' Row of First Empty Cell After Last Non-Empty Cell in Column
Private Sub Worksheet_Change(ByVal Target As Range)
Const TargetCell As String = "Z1"
Const TargetColumn As Variant = 12 ' (or "L")
Dim rng As Range
If Intersect(Columns(TargetColumn), Target) Is Nothing Then Exit Sub
Application.EnableEvents = False
Set rng = Columns(TargetColumn).Find(What:="*", LookIn:=xlFormulas, _
SearchDirection:=xlPrevious)
If rng Is Nothing Then ' Empty column. No non-empty cells.
Range(TargetCell).Value = 1
Else
If rng.Row = Rows.Count Then ' Last (bottom-most) cell is not empty.
Range(TargetCell).Value = 0
Else
Range(TargetCell) = rng.Offset(1).Row
End If
End If
Application.EnableEvents = True
End Sub
Dont need a loop. Paste this in a module not in a worksheet event unless you want it for every worksheet change.
Sub Macro1()
ActiveSheet.Range("Z1") = ActiveSheet.Columns(3).SpecialCells(xlCellTypeBlanks)(1).Row
End Sub
if you want it after every change then put it in a worksheet as. This code will not run everytime. It will check if Z1 is empty then enter the valu. Then if Z1 is not empty it will check if the target cell is in column C
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Rng As Range
Set Rng = ActiveSheet.Columns(3)
If IsEmpty(Range("Z1")) Then
Range("Z1") = Rng.SpecialCells(xlCellTypeBlanks)(1).Row
Else
If Not Intersect(Range("C1:C" & Range("Z1").Value), Target) Is Nothing Then
Range("Z1") = Rng.SpecialCells(xlCellTypeBlanks)(1).Row
End If
End If
End Sub

worksheet change event only works when region selected - how to adjust to automatic update

the combination of this sub in a module
Sub hithere3()
Dim Rng As Range
Dim Unique As Boolean
For Each Rng In Worksheets("Sheet8").Range("FS3:FS30") 'for each cell in your B1 to B30 range, sheet1
Unique = True 'we'll assume it's unique
Lastunique = Worksheets("TRADES").Range("C:C").Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
For i = 3 To Lastunique 'for each cell in the unique ID cache
If Rng.Value = Worksheets("TRADES").Cells(i, 3).Value Then 'we check if it is equal
Unique = False 'if yes, it is not unique
End If
Next
If Unique Then Worksheets("TRADES").Cells(Lastunique + 1, 3) = Rng 'adds if it is unique
Next
End Sub
with the loop check in a worksheet change events
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Intersect(Target, Range("FS3:FS33")) Is Nothing Then
'Do nothing '
Else
Call hithere3
End If
End Sub
works except it only updates when I select one of the cells in FS3:FS33
Can anyone suggest how this can be overcome?
maybe with a workchange change range selection type from below?
Private Sub Worksheet_Change(ByVal Target As Range)
Dim Rng As Range, Dn As Range, n As Long
Dim RngB As Range, RngC As Range
If Target.Column = 2 And Target.Count = 1 And Target.Row > 1 Then
With CreateObject("scripting.dictionary")
.CompareMode = vbTextCompare
Set RngB = Range(Range("B2"), Range("B" & Rows.Count).End(xlUp))
Set RngC = Range(Range("C2"), Range("C" & Rows.Count).End(xlUp))
ray = Array(RngB, RngC)
For n = 0 To 1
For Each Dn In ray(n)
If Not Dn.Address(0, 0) = "C1" And Not Dn.Value = "" Then
.Item(Dn.Value) = Empty
End If
Next Dn
Next n
Range("C2").Resize(.Count) = Application.Transpose(.Keys)
End With
End If
Use either the worksheet Calculate event or the worksheet Change event:
use Calculate if the range contains formulas
use Change if the cells in the range are changed manually
If Intersect(Target, Range("FS3:FS33")) Is Nothing is the culprit. You must change Range("FS3:FS33") to whatever range you want to affect this change.
Private Sub Worksheet_Change(ByVal Target As Range) '<<delete the "Selection" from the name of event
If Intersect(Target, Range("FS3:FS33")) Is Nothing Then
'Do nothing '
Else
Call hithere3
End If
End Sub
Finally figured it out, the following code works :
Private Sub Worksheet_calculate()
If Range("FS3:FS33") Is Nothing Then
'Do nothing'
Else
Call hithere3
End If
End Sub

Resources