I'm only about three weeks into learning how to use Excel, and I have it so that all of the tables on my worksheet will sort, but not when there's a change, only when I actually visit the worksheet.
So if I input data from another source like a UserForm, it won't sort the tables again until I go back to the worksheet. Is there a way to automatically sort them so the extra visit isn't needed?
This is what I have so far:
Private Sub Worksheet_Activate()
Dim tbl As ListObject
Dim SortCol As Long
Application.ScreenUpdating = False
For Each tbl In ActiveSheet.ListObjects
If tbl.Name = "TableSORT2" Then
SortCol = 2
Else
SortCol = 1
End If
With tbl.Sort
.SortFields.Clear
.SortFields.Add Key:=tbl.DataBodyRange.Columns(SortCol), _
SortOn:=xlSortOnValues, Order:=xlAscending
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
Next tbl
Application.ScreenUpdating = True
End Sub
I tried changing Private Sub Worksheet_Activate() to Private Sub Worksheet_Change() but to no avail, I'm assuming because there's more references or integration needed.
You can either take on D_Zab's advise or try below:
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo halt
Application.EnableEvents = False
Dim tbl As ListObject
For Each tbl In Me.ListObjects
If Not Intersect(Target, Me.Range(tbl.Name)) Is Nothing Then
MsgBox "Table Updated"
SortTables Me 'call the sort table routine
End If
Next
moveon:
Application.EnableEvents = True
Exit Sub
halt:
MsgBox Err.Description
Resume moveon
End Sub
So above detects any changes made in any table in the sheet you put the event. Now, all you have to do is to create a sub that sorts all the tables and call it. A sample code (which is actually what you have) is below.
Private Sub SortTables(sh As Worksheet)
Dim tbl As ListObject
Dim SortCol As Long
Application.ScreenUpdating = False
For Each tbl In sh.ListObjects
If tbl.Name = "TableSORT2" Then
SortCol = 2
Else
SortCol = 1
End If
With tbl.Sort
.SortFields.Clear
.SortFields.Add Key:=tbl.DataBodyRange.Columns(SortCol), _
SortOn:=xlSortOnValues, Order:=xlAscending
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
Next tbl
Application.ScreenUpdating = True
End Sub
Is this what you're trying? Btw, for some reason, this also detects changes made from UserForms. Even a simple line like Range("A2").Value = "something" is detected so long as the target range is within the Table Range. Moreover, this also detects the addition of data to tables when it auto-resize. HTH.
Related
I am creating a code that opens another file, performs some action and closes it. In the file I am opening, there is a function that organizes the data upon closing.
I do not know how to code the filter in VBA, so I recorded a macro and pasted it into my function. The code works when I run it by itself, but when I call the main function the '.Select' doesn't appear to select the cells/columns, causing a failure.
The first function is from the first workbook, and the second is being called when the first function closes the file.
'*********First Function************
Sub AddDrawing_Button() 'activated by button in worksheet
PN = Sheets("New Drawing").Range("C5").Cells(1, 1).Value 'Part Number, D
Rev = Sheets("New Drawing").Range("C5").Cells(3, 1).Value 'Revision, E
Application.ScreenUpdating = False
Workbooks.Open ("C:\Users\Desktop\MasterDataFile.xlsm") 'Finds the file
Workbooks("MasterDataFile").Worksheets("DATA").Activate
t = Sheets("DATA").Range("D65536").End(xlUp).Row + 1 'finds the bottom row + 1
Sheets("DATA").Range("D1").Cells(t, 1).Value = PN 'Part Number, D
Sheets("DATA").Range("D1").Cells(t, 8).Value = Rev 'Revision, E
Workbooks("MasterDataFile").Close SaveChanges:=True
'upon closing this file, it jumps to the following code
Application.ScreenUpdating = True
End Sub
'*********Second Function in Second Workbook************
Sub Workbook_BeforeClose(Cancel As Boolean)
Dim ThisWs As Worksheet
Dim value1 As String
Dim value2 As String
Set ThisWs = Workbooks("MasterDataFile").Worksheets("DATA")
t = ThisWs.Range("D65536").End(xlUp).Row 'end
'Application.ScreenUpdating = False
'The following 6 lines creates a new column and populates
' each row with the part number and revision combined.
Cells(1, 24) = "Order"
For s = 2 To t
value1 = Cells(s, 4)
value2 = Cells(s, 11)
ThisWs.Cells(s, 24) = value1 + "Rev" + value2
Next s
'The following was generated by recording a macro, and uses
' the filter to organize the data. The error is occurring
' because the columns are not being selected. Why?
ThisWs.Columns("D:X").Select
Selection.AutoFilter
ThisWs.AutoFilter.Sort.SortFields.Clear
ThisWs.AutoFilter.Sort.SortFields.Add Key:=Range( _
"X1:X19519"), SortOn:=xlSortOnValues, Order:=xlDescending, DataOption:=xlSortNormal
With ThisWs.AutoFilter.Sort
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
'This turns off the filter
ThisWs.Range("A1").Select
ThisWs.Columns("D:X").Select
Selection.AutoFilter
ThisWs.Range("A1").Select
'This deletes the generated column after it has been sorted
ThisWs.Columns("X:X").ClearContents
'Application.ScreenUpdating = True
End Sub
Can someone help me understand why the cells are not being selected, with a way to fix it?
Or if all else fails, can someone post a way to filter the columns without selecting anything.
Thank you.
I have rebuilt your function, It is untested and should work, but it is not completely optimised. Everything is handled in the original function and nothing is handled in the OnClose Event.
'*********First Function************
Sub AddDrawing_Button() 'activated by button in worksheet
Dim wbMasterDataFile as Workbook
Dim shtData as Worksheet
Dim t as long
Dim s as long
'PN = Sheets("New Drawing").Range("C5").Value 'Part Number, D
'Rev = Sheets("New Drawing").Range("C7").Value 'Revision, E
Application.ScreenUpdating = False
set wbMasterDataFile = Workbooks.Open ("C:\Users\Desktop\MasterDataFile.xlsm") 'Finds the file
set shtData = wbMasterDataFile.Worksheets("DATA")
with shtData
t = .Range("D65536").End(xlUp).Row + 1 'finds the bottom row + 1
.Range("D1").Cells(t, 1).Value = Sheets("New Drawing").Range("C5").Value 'Part Number, D
.Range("K1").Cells(t, 1).Value = Sheets("New Drawing").Range("C7").Value 'Revision, E
.Cells(1, 24).Value2 = "Order"
For s = 2 To t
.Cells(s, 24) = .Cells(s, 4) + "Rev" + .Cells(s, 11)
Next s
.Columns("D:X").AutoFilter
With .AutoFilter.Sort
.SortFields.Clear
.SortFields.Add Key:=Range("X1:X19519"), SortOn:=xlSortOnValues, Order:=xlDescending, DataOption:=xlSortNormal
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
.Columns("D:X").AutoFilter
.Columns("X:X").ClearContents
End with
set shtData = nothing
wbMasterDataFile.Close SaveChanges:=True
set wbMasterDataFile = Nothing
'upon closing this file, it jumps to the following code
Application.ScreenUpdating = True
End Sub
I have also made some more direct references to the ranges that you are using not through the Cell function
I have to run so can't explain more but will edit with more detail later.
Thank you #Bullfrog for providing me the solution to my problem, I am only posting this because I do not want other vba users to get hung up on code that may not run.
Sub AddDrawing_Button()
Dim ThisWb As Workbook, wbMasterDataFile As Workbook
Dim ThisWs As Worksheet, shtData As Worksheet
Dim t As Long, s As Long
Dim value1 As String, value2 As String
Application.ScreenUpdating = False
Set ThisWb = Workbooks("CombinationIndex")
Set ThisWs = ThisWb.Worksheets("New Drawing")
Set wbMasterDataFile = Workbooks.Open("C:\Users\Desktop\MasterDataFile.xlsm")
Set shtData = Workbooks("MasterDataFile").Worksheets("FinalDATA")
With shtData
t = .Range("D65536").End(xlUp).Row + 1 'Finds the bottom row
.Range("D1").Cells(t, 1).Value = ThisWs.Range("C5").Value 'Part Number, D
.Range("D1").Cells(t, 8).Value = ThisWs.Range("C13").Value 'Revision , E
.Cells(1, 24).value2 = "Order" 'header to new column
For s = 2 To t
value1 = .Cells(s, 4) 'originally Bullfrog's code was giving me an error due to a type mismatch
value2 = .Cells(s, 11) 'I defined a variable above, and filled it so that it was always a string
.Cells(s, 24) = value1 + "0Rev" + value2 'will use the combined values to sort data by latest rev
Next s
.Columns("D:X").AutoFilter 'using the with function to apply the filter
With .AutoFilter.Sort
.SortFields.Clear
.SortFields.Add Key:=Range("X1:X19519"), SortOn:=xlSortOnValues, Order:=xlDescending, DataOption:=xlSortNormal
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
.Columns("D:X").AutoFilter 'Turns off the filter
.Columns("X:X").ClearContents 'deletes the data
End With
Set shtData = Nothing
wbMasterDataFile.Close SaveChanges:=True 'closes the file
Set wbMasterDataFile = Nothing
Application.ScreenUpdating = True
End Sub
I have a spreadsheet with three columns; A B and C. Values in column C are calculated using a formula, and I want to sort Z->A on column C, expanding the selection to the other two columns as well. My goal is to have the ranking automatically updated anytime a value in column C changes. I'm looking for someone who can write this for me, I know JS but not VBA and I'm having trouble figuring it out because it's not an easy one (at least for me). Thanks!
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 1 Then
Application.EnableEvents = False
Columns("C").Sort Key1:=Range("C2"), Order1:=xlDescending, _
Header:=xlGuess, OrderCustom:=1, MatchCase:=False, Orientation:=xlTopToBottom
Application.EnableEvents = True
End If
End Sub
=IFERROR(((ABS((((SUMIF(Orders!$B$1:$BJ$1, DATE((YEAR(TODAY())), (MONTH(TODAY())), ("<"&DAY(TODAY()))), Orders!B2:BJ2))-(SUMIF(Forecast!$B$1:$BJ$1, DATE((YEAR(TODAY())), (MONTH(TODAY())), ("<"&DAY(TODAY()))), Forecast!B2:BJ2)))/(SUMIF(Orders!$B$1:$BJ$1, DATE((YEAR(TODAY())), (MONTH(TODAY())), ("<"&DAY(TODAY()))), Orders!B2:BJ2))))*(SUMIF(Orders!$B$1:$BJ$1, DATE((YEAR(TODAY())), (MONTH(TODAY())), ("<"&DAY(TODAY()))), Orders!B2:BJ2)))/(SUMIF(Orders!$B$1:$BJ$1, DATE((YEAR(TODAY())), (MONTH(TODAY())), ("<"&DAY(TODAY()))), Orders!B2:BJ2))), 0)
Place this code in the ThisWorkbook module in the Microsoft Excel Objects folder in the VBE.
This will sort by column C in the sheet (change name in code below to suit your actual sheet) anytime the workbook is recalculated. Obviously it will happen more times than needed, but it's the only way to ensure that it fires automatically.
If you have performance issues with this because of file size or amount of "recalcs" it's possible it can be tweaked a bit, or the design of the spreadsheet can be tweaked.
Private Sub Workbook_SheetCalculate(ByVal Sh As Object)
If sh.Name = "myABCSheet" or sh.Name = "Orders" or sh.Name = "Forecast"
Dim ws As Worksheet
Set ws = Worksheets("myABCSheet") 'change name as needed
With ws
Dim lRow As Long
lRow = ws.Range("A" & ws.Rows.Count).End(xlUp).Row
With .Sort
With .SortFields
.Clear
.Add ws.Range("C1:C" & lRow), 1, xlAscending
End With
.SetRange ws.Range("A1:C" & lRow)
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
Application.Calculation = xlManual
.Apply
Application.Calculation = xlCalculationAutomatic
End With
End With
Application.EnableEvents = True
End If
End Sub
I have got a problem with my macro and I would like to know if someone could help me.
I am doing a macro in a file, and that macro will go accessing other file and sort the information existing there. Until now I have the following code:
Sub Macro()
Dim xl As New Application
Dim xlw As Workbook
Dim xls As Worksheet
a = ThisWorkbook.Path & "\A.csv"
On Error GoTo bm:
Set xlw = xl.Workbooks.Open(a)
Set xls = xlw.Sheets(1)
' Windows(a).Activate
a = xls.Name
Columns("C:C").Select
xlw.Worksheets(a).Sort.SortFields.Clear
xlw.Worksheets(a).Sort.SortFields.Add Key:=Range("C2"), SortOn _
:=xlSortOnValues, Order:=xlDescending, DataOption:=xlSortNormal
With xlw.Worksheets(a).Sort
.SetRange Range("A2:K297594")
.Header = xlNo
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
bm:
xlw.Saved = True
xlw.Close True
xl.Quit
Set xls = Nothing
Set xlw = Nothing
Set xl = Nothing
End Sub
When put it running , when it reaches the instruction ".SetRange Range("A2:K297594")" it gives "Run-time error 5" and I don't understand why. So could anyone explain me how resolve this or why is giving this error?
Thanks :)
Your range is not referenced and Excel doesNOT know of which sheet you are talking about, it should be .SetRange xlw.Worksheets(a).Range("A2:K297594") :
Sub Macro()
Dim xl As New Application
Dim xlw As Workbook
Dim xls As Worksheet
a = ThisWorkbook.Path & "\A.csv"
On Error GoTo bm:
Set xlw = xl.Workbooks.Open(a, Local:=True)
Set xls = xlw.Sheets(1)
' Windows(a).Activate
a = xls.Name
Columns("C:C").Select
xlw.Worksheets(a).Sort.SortFields.Clear
xlw.Worksheets(a).Sort.SortFields.Add Key:=xlw.Worksheets(a).Range("C2"), SortOn _
:=xlSortOnValues, Order:=xlDescending, DataOption:=xlSortNormal
With xlw.Worksheets(a).Sort
.SetRange xlw.Worksheets(a).Range("A2:K297594")
.Header = xlNo
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
bm:
xlw.Save
xlw.Close True
xl.Quit
Set xls = Nothing
Set xlw = Nothing
Set xl = Nothing
End Sub
You need to qualify all your workbook, worksheet and range references, especially since you are running a macro against another workbook from where the macro runs.
You were really almost there (99%). This will clean it up for you:
Dim wName as String 'since you already use a to get the file name
wName = xls.Name
With xlw.Worksheets(wName).Sort
With .SortFields
.Clear
'note . (period) in front of range and I am pretty sure you need to set the
'whole range reference ... hence the C297597 ... but maybe just C2 is enough
.Add Key:=.Range("C2:C297594"), SortOn:=xlSortOnValues, Order:=xlDescending, DataOption:=xlSortNormal
End With
'note . (period) in front of range
.SetRange .Range("A2:K297594")
.Header = xlNo
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
I'm trying to pass a range to a subroutine, but its throwing up a "Method 'Range' of object '_Global' failed" error.
In the main I declare and define the range variable I want to use:
Sub maintest()
Dim ScheduledSort As Range
Set ScheduledSort = Range("F4:F321")
Call test(ScheduledSort)
End Sub
Then in the subroutine test I want it to sort using the range I passed it from the routine above:
Sub test(RangeForSort)
Sheets("SheetTest").Select
' Sort in descending order
ActiveWorkbook.Worksheets("SheetTest").AutoFilter.Sort.SortFields.Add _
Key:=Range("RangeForSort"), SortOn:=xlSortOnValues, Order:=xlDescending, _
DataOption:=xlSortTextAsNumbers
With ActiveWorkbook.Worksheets("SheetTest").AutoFilter.Sort
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End Sub
I think its going wrong at the Key:=Range("RangeForSort") but I can't work out why and how to fix it.
What is it I'm doing wrong with the Range and how do I fix it such that I can pass it any Range to sort on?
And if you have a better suggestion for what I'm trying to do, feel free to add! :-)
Shorter version would look like this:
Sub test(rng As Range)
' Sort in descending order
Worksheets(rng.Parent.Name).AutoFilter.Sort.SortFields.Add _
Key:=rng, SortOn:=xlSortOnValues, Order:=xlDescending, _
DataOption:=xlSortTextAsNumbers
With Worksheets(rng.Parent.Name).AutoFilter.Sort
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End Sub
To run:
Call test(Worksheets("YOUR WORKSHEET NAME").Range("YOUR RANGE")).
If you pass a Range object to the sub, you are passing an object that is already associated with some worksheet. The sub selects a potentially different worksheet and then has trouble handling the passed range.
If you want to pass a specific block of cells to a sub that needs to change worksheets, then pass a String variable instead.
UNTESTED
Sub maintest()
Dim ScheduledSort As String
ScheduledSort = "F4:F321"
Call test(ScheduledSort)
End Sub
Sub test(RangeForSort As String)
Sheets("SheetTest").Select
ActiveWorkbook.Worksheets("SheetTest").AutoFilter.Sort.SortFields.Add _
Key:=Range(RangeForSort), SortOn:=xlSortOnValues, Order:=xlDescending, _
DataOption:=xlSortTextAsNumbers
With ActiveWorkbook.Worksheets("SheetTest").AutoFilter.Sort
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End Sub
I set this up and now I can pass the "ActiveCell/Range" whatever and call the function throughout my project if needed.
Public colLetter As Variant
Sub Test()
Dim rng As Range
Set rng = ActiveWorkbook.ActiveSheet.Range("A1:A1")
Call GetColLet(rng)
End Sub
Public Sub GetColLet(var As Range)
colLetter = Split(var.Address, "$")(1)
MsgBox colLetter
End Sub
I have a table of clients in Excel, and I want to be able to add new client into the last row of the table and excel will sort the table automatically so that the client's name will be sorted in alphabetical order.
Also, that the format will be similar to the previous line. for example, the second column is DOB, so I want the format to be the same as the previous row MM/DD/YYYY
Thanks
Put the attached code in your worksheet module and it will sort your column A automatically.
Private Sub Worksheet_Change(ByVal Target As Range)
'turn off updates to speed up code execution
With Application
.ScreenUpdating = False
.EnableEvents = False
.Calculation = xlCalculationManual
.DisplayAlerts = False
End With
If Not Intersect(Target, Columns(1)) Is Nothing Then
With ActiveSheet.Sort
.SetRange Range("A1:X" & Cells(Rows.Count, 1).End(xlUp).Row)
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
Columns("B").NumberFormat = "MM/DD/YYYY"
End If
With Application
.ScreenUpdating = True
.EnableEvents = True
.Calculation = xlCalculationAutomatic
.DisplayAlerts = True
End With
End Sub
Here's a piece of VBA that would auto-add your table as soon as the first cell on the last row gets typed in. You would have to provide IsChangeInLastLineOfStrRange function and call AddEmptyRowWhenFull from the change-event. It might need tweaking since I removed some code from it. The original has a recursion timer to prevent ... well ... recursion.
Public Sub AddEmptyRowWhenFull(SheetName As String, Area As String, Target As Range)
Dim rngDatabase As Range
With Sheets(SheetName)
If IsChangeInLastLineOfStrRange(SheetName, Area, Target) _
And Target.Value <> "" Then
Set rngDatabase = .Range(Area)
AddEmptyRow rngDatabase, rngDatabase.Rows.Count
End If
End With
End Sub
Public Sub AddEmptyRow(Database As Range, RowPosition As Long, Optional ClearLine As Boolean = True)
Dim bScreenupdate, iCalculation As Integer
Dim colnum As Long, markrow As Long
Dim bUpdate As Boolean
bScreenupdate = Application.ScreenUpdating
iCalculation = Application.Calculation
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
With Database
If RowPosition < .Rows.Count Then
.Rows(RowPosition - 0).Copy 'Insert in and after data
.Rows(RowPosition + 1).Insert shift:=xlDown
Else
.Rows(RowPosition - 0).Copy 'Add line at end by inserting before last line
.Rows(RowPosition - 0).Insert shift:=xlDown ' to prevent cell formatting below it to be copied too
RowPosition = RowPosition + 1 'Clear last of the copies
End If
If ClearLine = False Then 'Move cursor down
ActiveSheet.Cells(ActiveCell.row + 1, ActiveCell.column).Activate
Else
For colnum = 1 To .Columns.Count 'Preserve formula's
If Not .Rows(RowPosition).Cells(1, colnum).HasFormula Then 'changed
.Rows(RowPosition).Cells(1, colnum).ClearContents
End If
Next colnum
End If
'Fix rowheight if we shift into other heights
.Rows(RowPosition + 1).RowHeight = .Rows(RowPosition + 0).RowHeight
End With
If bScreenupdate = True Then Application.ScreenUpdating = True
If Not iCalculation = xlCalculationManual Then Application.Calculation = iCalculation
End Sub
Arjen.