How to pass range variables between subroutines in Excel VBA - vba

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

Related

VBA: Referencing a Range stored as variable in a Sorting sub

Very novice programmer so hope my question isn't too dumb. I'm trying to set make a common sorting subroutine that I can call, but where the sorting subroutine looks at a variable for the range. This way I can set the range elsewhere (such as the button where I call the subroutine) and not have it hard-coded into the sorting routine.
So far my train of thought is this:
Dim Column As Range
Sub SortCodeDsc()
ActiveWorkbook.Worksheets("Benchmark Data").Sort.SortFields.Clear
ActiveWorkbook.Worksheets("Benchmark Data").Sort.SortFields.Add Key:=Range("A4:A100").Value, SortOn:=xlSortOnValues, Order:=xlDescending, DataOption:=xlSortNormal
With ActiveWorkbook.Worksheets("Benchmark Data").Sort
.SetRange Range("A4:HH100")
.Header = xlGuess
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End Sub
Private Sub Year_Click()
Set Column = ActiveSheet.Range("A4:A100")
Call SortCodeDsc
End Sub
So where Range("A4:A100") is in the Sub SortCodeDsc I would instead like to call variable Column which I Set to the range from A4 to A100. How do I properly call that variable when calling the subroutine?
It's probably not a good idea to use the word "Column" as a variable. That should be a saved word. Use Column1 instead and turn your sub into a function, passing it the range parameter.
Function SortCodeDsc(columnRange As Range)
ActiveWorkbook.Worksheets("Benchmark Data").Sort.SortFields.Clear
ActiveWorkbook.Worksheets("Benchmark Data").Sort.SortFields.Add Key:=columnRange, SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
With ActiveWorkbook.Worksheets("Benchmark Data").Sort
.SetRange Range("A4:HH100")
.Header = xlGuess
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End Function
Private Sub Year_Click()
Dim column1 As Range
Set column1 = ActiveWorkbook.Worksheets("Benchmark Data").Range("A1:A100")
Run SortCodeDsc(column1)
End Sub

Automatically Sort Rows in Excel by Date

I'm currently trying to self-teach myself VBA code in Excel, but I've run into a problem.
What I'm wanting Excel to do is to automatically order specific rows according to the date entered in specific cells. For example, dates will be entered into cells E36-E40 only, and as they are entered rows 36-40 (not including column A) will automatically sort themselves according to the oldest date first.
I've done a macro recording of this and it has spat out this code:
Sub AutoSort()
Range("B36:H40").Select
ActiveWorkbook.Worksheets("SHEET NAME").Sort.SortFields.Clear
ActiveWorkbook.Worksheets("SHEET NAME").Sort.SortFields.Add Key:=Range( _
"E37:E40"), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:= _
xlSortNormal
With ActiveWorkbook.Worksheets("SHEET NAME").Sort
.SetRange Range("B36:H40")
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End Sub
I've tried to make this automatic as shown below, however does not work!
Sub Worksheet_Change1(ByVal Target As Range)
If Intersect(Target, Range("E36, E37, E38, E39, E40")) Is Nothing Then
Exit Sub
Else
Sub AutoSort()
Range("B36:H40").Select
ActiveWorkbook.Worksheets("SHEET NAME").Sort.SortFields.Clear
ActiveWorkbook.Worksheets("SHEET NAME").Sort.SortFields.Add Key:=Range( _
"E37:E40"), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:= _
xlSortNormal
With ActiveWorkbook.Worksheets("SHEET NAME").Sort
.SetRange Range("B36:H40")
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End If
End Sub
End Sub
Any help would be greatly appreciated!
MSDN definition of Me: Provides a way to refer to the specific instance of a class or structure in which the code is currently executing.
I used Me instead of ActiveWorkbook.Worksheets("SHEET NAME") because this code is only relevant to the worksheet that calls the event. I originally used ActiveSheet but if a Macro changed the values from a different worksheet than that worksheet would be active and it would be sorted.
Turn off EnableEvents, whenever changing values on the ActiveSheet from the Worksheet_Change event. This will prevent the Worksheet_Change event from triggering itself causing an infinite loop and crashing Excel.
Include an Error Handler that will turn the events back on, if an error is thrown.
The key range started at row 37
.Header = xlYes should be .Header = xlNo
Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
On Error GoTo ResumeEvents
If Not Intersect(Target, Range("E36:E40")) Is Nothing Then
With Me
.Sort.SortFields.Clear
.Sort.SortFields.Add Key:=Range("E36:E40"), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
With .Sort
.SetRange Range("B36:H40")
.Header = xlNo
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End With
End If
ResumeEvents:
Application.EnableEvents = True
End Sub
using Sort() method of Range leads to a more concise code:
Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
On Error GoTo ErrHandler
If Not Intersect(Target, Range("E36:E40")) Is Nothing Then _
Range("B36:H40").Sort key1:=Range("E36"), order1:=xlAscending, Header:=xlNo, SortMethod:=xlPinYin, DataOption1:=xlSortNormal, MatchCase:=False, Orientation:=xlTopToBottom
ErrHandler:
Application.EnableEvents = True
End Sub
or, encapsulating the sorting operation into a specific sub:
Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("E36:E40")) Is Nothing Then AutoSort Range("B36:H40"), Range("E36")
End Sub
Sub AutoSort(dataRng As Range, orderCol As Range)
Application.EnableEvents = False
On Error GoTo ErrHandler
dataRng.Sort key1:=orderCol, order1:=xlAscending, Header:=xlNo, SortMethod:=xlPinYin, DataOption1:=xlSortNormal, MatchCase:=False, Orientation:=xlTopToBottom
ErrHandler:
Application.EnableEvents = True
End Sub
Don't encapsulate your Subprocedure AutoSort() in your other procedure. Put your AutoSort() procedure in module, then call it within worksheet code:
Private Sub Worksheet_Change(ByVal Target As Range)
If Intersect(Target, Range("E36, E37, E38, E39, E40")) Is Nothing Then
Exit Sub
Else
AutoSort
End If
End Sub
Also, change .Header = xlYes to .Header = xlNo if Row 36 doesn't contain header.

Sort the information in a different file excel

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

Automatically sort multiple tables in a sheet upon change

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.

VBA to sort table and ignore total row

I have the range Table3 as shown below:
The rows are not fixed and could increase or decrease, I have thus created it as a table Table3 to accommodate this behavior and also so I could use it in a VBA as a ListObjects.
The VBA below is meant to sort the table, however because the Totals is part of the range, the sort doesn't work as intended.
Sub sort()
ActiveWorkbook.Worksheets("Project 2013").ListObjects("Table3").sort.SortFields _
.Clear
ActiveWorkbook.Worksheets("Project 2013").ListObjects("Table3").sort.SortFields _
.Add Key:=Range("Table3[Description3]"), SortOn:=xlSortOnValues, Order:= _
xlAscending, DataOption:=xlSortNormal
With ActiveWorkbook.Worksheets("Project 2013").ListObjects("Table3").sort
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End Sub
Can someone please help modify the code to ignore the Totals row (i.e to include only the range below the header and above the Totals row) before applying the sort
EDIT
At the moment, this is my attempt at redefining a new range without the last row
Sub sort()
Dim resizedTable As ListObject
Set resizedTable = Sheets("Sheet1").ListObjects("Table1")
With resizedTable
.Resize .Range.Resize(.Range.Rows.Count - 1, .Range.Columns.Count)
End With
resizedTable.sort.SortFields.Clear
resizedTable.sort.SortFields _
.Add Key:=Range("resizedTable[Description]"), SortOn:=xlSortOnValues, Order:= _
xlAscending, DataOption:=xlSortNormal
   
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End Sub
Any help will be appreciated.
Set a new range for your table, just one row shorter » totalRowCount - 1.
Here, x is your input range
Set x = Range(x.Cells(1, 1), x.Cells(x.Rows.Count - 1, x.Columns.Count))
or use the resize method
Sub CutOffLastLine()
With ActiveWorkbook.Worksheets("Project 2013").ListObjects("Table3")
.Resize .Range.Resize(.Range.Rows.Count - 1, .Range.Columns.Count)
End With
End Sub