Sort ascending/descending in Excel using VBA sub - excel

I want to sort an amount of data in Excel. It should toggle between ascending and descending on every click.
I'd found this problem solved in the next thread:
sort ascending/descending vba excel.
But I want to do some changes in the code.
I want to sort using the current column where I clicked (the headers). I don't know if this is possible using just one macro and sending the cell where I call the event.
Here is the code that I'm using:
Worksheet (where I call the Sub):
Option Explicit
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Selection.Count = 1 Then
If Not Intersect(Target, Range("A2:C2")) Is Nothing Then
Call sort_table(Target)
End If
End If
End Sub
Sub:
Sub sort_by_letters(Order As Range)
Dim dataRange As Range
Dim fieldOrder As Range
Dim xlSort As XlSortOrder
Dim LastRow As Long
With ActiveSheet
Set LastRow = .Cells(.Rows.Count, Order).End(xlUp).Row
End With
If (Order.Value > Range(Column(Order) & CStr(LastRow))) Then
xlSort = xlAscending
Else
xlSort = xlDescending
End If
Set dataRange = Range("A2:C" & LastRow)
Set campoOrden = Order
dataRange.Sort key1:=fieldOrder, order1:=xlSort, Header:=xlYes
End Sub

Sort on Selection Change
Sheet Module e.g. Sheet1
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Selection.Count = 1 Then
If Not Intersect(Target, Range(strHeaders)) Is Nothing Then
SortTable Target
End If
End If
End Sub
Standard Module e.g. Module1
Public Const strHeaders As String = "A2:C2"
Sub SortTable(Target As Range)
Dim LuCell As Range ' Last Used Cell Range
Dim rngS As Range ' Sort Range
Dim xlSort As XlSortOrder ' Sort Order
' In Target Worksheet
With Target.Worksheet
' Calculate last used cell in Target Column.
Set LuCell = .Cells(.Rows.Count, Target.Column).End(xlUp)
' Check if value in first row below Headers in Target Column is greater
' than value in Last Used Cell Range.
If Target.Offset(1) > LuCell Then
xlSort = xlAscending
Else
xlSort = xlDescending
End If
' In Headers Range
With .Range(strHeaders)
' Calculate Sort Range.
' Create a reference to Sort Range.
Set rngS = .Resize(LuCell.Row - .Row + 1)
End With
End With
' Sort Sort Range.
rngS.Sort Key1:=Target, Order1:=xlSort, Header:=xlYes
End Sub

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

automatic static date and time when a value was copied from another worksheet and pasted to a sheet with macro

I am working on a worksheet that will enter static date and time in an excel worksheet when a value is typed in a target cell. However, the worksheet will be used where values are copied from a downloaded file and pasted to the macro worksheet. When values are typed, the date and time worked as expected but if values are pasted, the VBA code does not work, it has to be typed. How can I make that possible?
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Cells.Count > 1 Then Exit Sub
If Not Intersect(Target, Range("C2:C100")) Is Nothing Then
With Target(1, -1)
.Value = Date
.EntireColumn.AutoFit
End With
End If
End Sub
Something like this:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rng As Range, c As Range
'any updates to C2:C100 ?
Set rng = Application.Intersect(Target, Me.Range("C2:C100"))
If Not rng Is Nothing Then
'loop over all updated cells
For Each c In rng.Cells
c.Offset(0, -2).Value = Date
Next c
rng.Offset(0, -2).EntireColumn.AutoFit
End If
End Sub
Add Date Stamp on Cell Change
Pick one.
Easy
Private Sub Worksheet_Change(ByVal Target As Range)
Const cFirst As String = "C2"
Const dCol As String = "A"
Dim rg As Range
Set rg = Range(cFirst).Resize(Rows.Count - Range(cFirst).Row + 1)
Set rg = Intersect(Target, rg)
If Not rg Is Nothing Then
' Since you cannot manually paste a non-contiguous range
' (you can copy one), you can get away with the following line:
rg.EntireRow.Columns(dCol).Value = Date
rg.EntireColumn.AutoFit
End If
End Sub
Hard
Private Sub Worksheet_Change(ByVal Target As Range)
Const cFirst As String = "C2"
Const dCol As String = "A"
' Create a reference to the column range from 'cFirst'
' to the bottom-most cell in the worksheet.
Dim rg As Range: Set rg = Intersect(Target, _
Range(cFirst).Resize(Rows.Count - Range(cFirst).Row + 1))
If rg Is Nothing Then Exit Sub
' If you plan to populate the cells via VBA, then you could write
' non-contiguously to the column range,
' e.g. with 'Range("C3,C5:C7,C10:20").value = 1'.
' Then you could use the following:
Dim dDate As Date: dDate = Date
Dim arg As Range
For Each arg In rg.Areas
arg.EntireRow.Columns(dCol).Value = dDate
Next arg
rg.EntireColumn.AutoFit
End Sub
Tough
Private Sub Worksheet_Change(ByVal Target As Range)
addDateStamp Target, "C2", "A"
End Sub
' This is usually, but not necessarily, located in a standard module.
Sub addDateStamp( _
ByVal TargetRange As Range, _
ByVal FirstCellAddress As String, _
ByVal DateStampColumn As String)
If Not TargetRange Is Nothing Then
Dim rg As Range
With TargetRange.Worksheet.Range(FirstCellAddress)
Set rg = .Resize(.Worksheet.Rows.Count - .Row + 1)
End With
Set rg = Intersect(TargetRange, rg)
If Not rg Is Nothing Then
Dim dDate As Date: dDate = Date
Dim arg As Range
For Each arg In rg.Areas
arg.EntireRow.Columns(DateStampColumn).Value = dDate
Next arg
rg.EntireColumn.AutoFit
End If
End If
End Sub

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

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.

How can I set the value of a cell in Cells().Select command to the value of a cell that has a formula in it?

I'm trying to make a macro that copies the values inside certain cells of sheet1 and pastes then in sheet2.
This is a formula that i wrote inside cell "AI2":
=IFERROR(SUM(1+AH:AH),"0")
and it produces a number that I want to use in the macro as a variable row coordinate.
This is the code i have in my worksheet in order to trigger the macro:
Private Sub Worksheet_Change(ByVal Target As Excel.Range)
If Range("AI2") <> 0 Then
Call macro1
End If
End Sub
And this is the macro:
Sub macro1()
Dim RV As Integer
RV = Sheets("sheet1").Range("AI2").Value
Cells(RR, 33).Select
Range(ActiveCell.Offset(0, -6), ActiveCell.Offset(0, -1)).Select
Selection.Copy
Sheets("sheet2").Select
Range("A1048576").Select
Selection.End(xlUp).Select
ActiveCell.Offset(1, 0).Range("A1").Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
End Sub
If I delete the first 3 lines of code, the macro works, but I have to manually select the cell for the offsets to reference from.
I need to make it so the value of cell "AI2" is used as the first coordinate in this line of code:
Cells(RR, 33).Select
I am very new to any kind of programming, but I want to learn this in order to achieve my goals for this spreadsheet and future ones with similar functions.
I am limiting the scope of your Worksheet_Change to only fire when a change is registered in Column AH since this is the column that will trigger a formula change in Column AI
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 34 Then
If Range("AI2") <> 0 Then
Macro2
End If
End If
End Sub
Sub Macro2()
Dim cs As Worksheet: Set cs = ThisWorkbook.Sheets("Sheet1")
Dim ps As Worksheet: Set ps = ThisWorkbook.Sheets("Sheet2")
Dim xRow As Long, LR As Long
LR = ps.Range("A" & ps.Rows.Count).End(xlUp).Offset(1).Row
xRow = cs.Range("AI2").Value
cs.Range(cs.Cells(xRow, "AB"), cs.Cells(xRow, "AG")).Copy
ps.Range("A" & LR).PasteSpecial xlPasteValues
End Sub
Copy Range to First Empty Cell
Calculate
If you are using a formula in the cell range AI2 you should use the Worksheet Calculate event which will occur everytime the formula is being calculated.
Standard Module
Option Explicit
Public Const strRange As String = "AI2"
Public vntValue As Variant
Sub macro1()
Dim rng As Range ' Target Cell Range
Dim RV As Long ' Row Value
' In Target Worksheet
With ThisWorkbook.Sheets("Sheet2")
' Calculate the first empty (unused) cell in column A (A1 not included).
Set rng = .Cells(.Rows.Count, "A").End(xlUp).Offset(1)
End With
' In Source Worksheet
With ThisWorkbook.Worksheets("Sheet1")
' Write the value of Row Cell to Row Value.
RV = .Range(strRange).Value
With .Cells(RV, "AH") ' or 33
' Copy range from "AB" to "AG" in row defined by Row Value in
' Source Worksheet to the range from "A" to "F" in row of Target
' Cell Range in Target Worksheet.
rng.Resize(, 6) = Range(.Offset(0, -6), .Offset(0, -1)).Value
End With
End With
End Sub
Sheet1
Option Explicit
Private Sub Worksheet_Calculate()
If vntValue <> Range(strRange).Value Then
vntValue = Range(strRange).Value
If Range(strRange).Value <> "0" Then macro1
End If
End Sub
ThisWorkbook
Option Explicit
Private Sub Workbook_Open()
vntValue = Worksheets("Sheet1").Range(strRange).Value
End Sub
Change
If you are manually changing the values in the cell range AI2, you have to use the Worksheet Change event.
Standard Module
Option Explicit
Sub macro1()
Dim rng As Range ' Target Cell Range
Dim RV As Long ' Row Value
' In Target Worksheet
With ThisWorkbook.Sheets("Sheet2")
' Calculate the first empty (unused) cell in column A (A1 not included).
Set rng = .Cells(.Rows.Count, "A").End(xlUp).Offset(1)
End With
' In Source Worksheet
With ThisWorkbook.Worksheets("Sheet1")
' Write the value of Row Cell to Row Value.
RV = .Range("AI2").Value
' In cell at the intersection of Row Value and column "AH".
With .Cells(RV, "AH") ' or 33
' Copy range from "AB" to "AG" in row defined by Row Value in
' Source Worksheet to the range from "A" to "F" in row of Target
' Cell Range in Target Worksheet.
rng.Resize(, 6) = Range(.Offset(0, -6), .Offset(0, -1)).Value
End With
End With
End Sub
Sheet1
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Const cCell As String = "AI2"
If Target = Range(cCell) Then
If Range(cCell).Value <> "0" Then macro1
End If
End Sub
Like in the Calculate version, you might also want to use a public variable (vntValue) to prevent triggering macro1 in case the value in cell range AI2 hasn't actually changed.

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