Sort a ListObject by Columns - excel

I am working with ListObject objects in VBA. In particular I am creating a table dynamically (variable number of rows and columns, and variable column headers). I have the need to sort the table's columns in ascending alphabetical order.
For instance if I have the table:
I want it to be sorted like so,
Is there any way to do this? I have tried to use the Range.Sort method but it seems that this is not allowed if the range is part of a ListObject.
I also tried to record a macro to find code, but found that when I right clicked the table to sort it, the selected range left the header out and I was not able to select the "Sort left to right" option...
Any ideas?

This seems to work. It takes the name of a table (e.g. "Table1"), converts it to a range, sorts it, then reconverts it to a table with the same name:
Sub SortByCol(tableName As String)
Dim sh As Worksheet
Dim myTable As ListObject
Dim myStyle As TableStyle
Dim myRange As Range
Set sh = ActiveSheet
Set myTable = sh.ListObjects(tableName)
Set myStyle = myTable.TableStyle
Set myRange = myTable.Range
myTable.Unlist
On Error Resume Next
myRange.Sort key1:=myRange.Rows(1), Orientation:=xlSortRows
If Err.Number > 0 Then Debug.Print "Range couldn't be sorted"
On Error GoTo 0
sh.ListObjects.Add(xlSrcRange, myRange, , xlYes).Name = tableName
sh.ListObjects(tableName).TableStyle = myStyle
End Sub
On edit: I added a bit of error handling around the sort method call. Experiments showed that my original code didn't preserve the style (if the table didn't have the default style then the result was a weird blend of the default style and the original table style). I added coded to save and then restore the original table style, but I don't know much about table formatting and might have missed some subtleties. At the very least, it seems to preserve the style if it was chosen from Excel's built-in list of table styles.

Related

I'm trying to work out a way to create and IF function to determine if filters applied to a table in VBA return an Empty table

I'm creating an excel workbook that has a dashboard on the front page of it. when two dates are selected from a drop down and the "refresh data" button is pressed, it runs a macro that fills in all the charts and tables on the dashboard page based on data tables in other worksheets within the workbook.
I'm trying to add a layer of dynamism so that if a date is selected where not all information, or even any information, is to be found, it lets the user know. (some information can be late coming to the work book)
So far I've tried a few ways with the closest maybe being the following options, but none of them work properly.
The beginning code is
Sub Macro3()
Dim DataM As Variant
Dim DataY As Variant
Dim ws1 As Worksheet
Dim DD1 As OLEObject
Dim DD2 As OLEObject
Dim i As Byte
Set ws1 = Worksheets("KPI Dashboard")
Set DD1 = ws1.OLEObjects("DMonth") 'this is the month input
Set DD2 = ws1.OLEObjects("Dyear") 'this is the year input
Set DataM = DD1.Object
Set DataY = DD2.Object
'part 1 is for utilisation
Set ws2 = Worksheets("People Info")
ws2.ListObjects("Utilisation").Range.AutoFilter Field:=2, Criteria1:=DataM.Value
ws2.ListObjects("Utilisation").Range.AutoFilter Field:=1, Criteria1:=DataY.Value
My first attempt was to count the rows in the databodyrange, but every time I tried that I would get an error saying there was no cells. I know I oculd normally put an "on error" line of code, but I want it to highlight to the user that there is a table missing data and then go onto the next table and keep going. I don't know of any ways in which I could dynamically have error responses in one function to the same error.
My second attempt was to just count the tables overall rows, with a msgbox to say there was no data like below:
If ws2.ListObjects("Utilisation").Range.SpecialCells(xlCellTypeVisible).Rows.Count = 1 Then
If MsgBox("It Looks like we have no data for Utilisation, please fill it in and run it again. do you want to continue?", vbYesNo) = vbNo Then
Exit Sub
End If
ws2.ListObjects("Utilisation").AutoFilter.ShowAllData
GoTo S1P2
End If
But it always returns the count of rows as 1, even when I can see that it clearly has 5 or 6 rows in it.
So the last thing I tried was to select the area, and see if I could count that selection to greater success, but again it would still return 1, even on the count
Can anyone think of a better way to try it?
I have also tried using the IsEmpty() function and a handful of others like that but can't seem to get it to work. it either says there are no cells in the databody range, because they have been queried out or it says there is 1 row in the table range, even when there isn't.
Any help on how to make it work, or even a different avenue with which to look down would be really helpful.

selecting a field from pivot table using vba macro

I have created a pivot table using vba. I need help with the understanding of below-mentioned points.
1: I want to select(copy) values with certain filters (eg: Underlying price
for Instrument Type = OPTCUR, Symbol = GBPUSD). Basically a VBA alternative for formula
GETPIVOTDATA("Underlying_price",$C$4,"Instrument Type","OPTCUR","Symbol","GBPUSD")
2: I want to set "show detail=True" without knowing cell details but the criteria as mentioned above.
3: when we set "show detail=True" a new sheet opens. i want to asign this sheet to a variable of type worksheet.
below is the SS of my pivot table. and TableName:="My_Pivot"
You get the relevant cell with PivotTable.GetPivotData
The new worksheet with details of this cell is shown by Range.ShowDetail = True.
Directly after that, the new ActiveSheet is the wanted one.
Here is a function to get the wanted worksheet with the details for a specified data field:
Private Function GetDetailSheet(pt As PivotTable, Val1 As String, Val2 As String) As Worksheet
Dim myCell As Range
With pt
Set myCell = .GetPivotData(.DataFields(1).Name, _
.RowFields(1).Name, Val1, _
.RowFields(2).Name, Val2)
End With
myCell.ShowDetail = True
Set GetDetailSheet = ActiveSheet
End Function
It can be used like this:
Private Sub Test()
Dim ws as Worksheet
Set ws = GetDetailSheet(ActiveSheet.PivotTables("My_Pivot"), "OPTCUR", "GBPUSD")
ws.Name = "Details OPTCUR GBPUSD"
End Sub
If you don't want to use the (hideos) GETPIVOTDATA there's a solution for you!
It's called CUBEVALUE. It's a bit difficult to master but once it's done you reports & Pivot Table get to a whole new level.
See here: https://www.excelcampus.com/cubevalue-formulas/
Yes, it's a long article, but it's definitely worth the effort, as it would enable you to point to a specific data point, not to a specific cell.
Once mastered, adding a VBA code to a "changed" cell event and changing the visibility status of a certain sheet is just a matter of minutes.

VBA coding to identify and clear data in a specific table

I am new to using VBA and would like to add coding to a button that will identify the size of a table called "QA_Activity" and clear all but the header row on clicking the button. I was hoping that this would be quite simple but am struggling as to how to identify a table that could be a different size each month. Many thanks in advance.
Tables are called ListObjects in VBA. A ListObject has a property called a .DataBodyRange that contains everything under the header. That has a .Clear method.
I generally use this syntax to clear the body of a ListObject:
Range("Table1").ListObject.DataBodyRange.Clear
The Range("Table1") bit allows me to find the ListObject even if I don't know what sheet it's on. This works because Tables are also Named Ranges that you can address by name. So you just say "Go to the Named Range called Table1, and get me the ListObject that lives there". Otherwise you would need to know in advance what sheet the ListObject is on, and use the following:
Worksheets("SomeSheet").Listobjects("Table1").DataBodyRange.Clear
...which works just fine until one day you move the Table to a different sheet, and forget to update your code.
Note that a table is not guaranteed to actually have a .DataBodyRange, because someone may have deleted all the rows under the header. For instance, take this Table:
How many rows does it have in the DataBodyRange?
? Range("Table1").ListObject.DataBodyRange.Rows.Count
3
Okay, now I'm going to delete those rows:
...leaving this:
How many rows in that DataBodyRange now?
? Range("Table1").ListObject.DataBodyRange.Rows.Count
Whoops...you can't reference a .DataBodyRange if it don't exist.
So to be safe, stick an On Error Resume Next before you try to reference a .DataBodyRange, and an On Error Goto 0 afterwards. Or something fancier.
First, create a named range. If required, you can make this dynamic. In this example the named range is Name "Data".
Then Trigger the Sub "Test" from the following VBA code.
Option Explicit
Sub ClearRange(ByVal rngCell As Range)
Dim rngRange As Range
Set rngRange = rngCell.CurrentRegion
rngRange.Range(rngRange.Parent.Cells(2, 1), rngRange.Parent.Cells(rngRange.Rows.Count, rngRange.Columns.Count)).ClearContents
End Sub
Sub test()
Dim rngCell As Range
Set rngCell = ActiveSheet.Range("Data").Cells(1, 1)
ClearRange rngCell
End Sub
This should clear the range except for the first row (headers).
Important: The header row and the first column of your range must be filled completely (no empty cells) for the above to work smoothly.

Structured reference changes to absolute when copied across sheets

I have a monster of a workbook that I'm trying to make more manageable for those that use it after me. I have a ton of code that is ran when buttons are pressed to make it more user friendly to those that know little to nothing of Excel. So here is where I need help.
I have several sheets with similar tables. My first sheet contains a Master List of customer information and pressing a button, copies this information to each other sheet and sorts it to categorize these customers on their respective sheets. This allows me to enter new information only on the first sheet and have it auto-populate the sheets correctly to minimize human error.
To cut down on a lot of the errors, I utilized structured referencing in tables. I didn't originally have it this way, but I've been trying to improve this workbook over time.
Anyway, so I have a column "Charge Type" in each table, and the total column references it as
[#[Charge Type]]
which is great, considering customers will be added and removed pretty regularly and this cuts down on errors.
However, when this formula gets copied to one of the other sheets, it's converted over to
All_List[#[Charge Type]]
which adds the name of the table on sheet1, which is "All_List". Now I want it to refer to the column "Charge Type" specifically in the new table on the new sheet, and I cannot for the life of me figure out how.
This solution uses a variable to hold the ListObject "Field" formula then loops trough all other ListObjects in the same workbook with the same "Field" and applies the formula to that "Field".
ListObjects before
Sub ListObjects_Formula_Copy()
Dim wsh As Worksheet
Dim lob As ListObject
Dim rTrg As Range
Dim sFld As String
Dim sFmlR1C1 As String
Rem Get Formula from Primary ListObject
sFld = "Price" 'Change as required
Set lob = ThisWorkbook.Sheets("Sht(0)").ListObjects(1) 'Change as required
sFmlR1C1 = lob.ListColumns(sFld).DataBodyRange.Cells(1).FormulaR1C1
Rem Apply Formula to Other ListObjects
For Each wsh In ThisWorkbook.Worksheets
If wsh.Name <> "Sht(0)" Then
For Each lob In wsh.ListObjects
Rem Validate Field
Set rTrg = Nothing
On Error Resume Next
Set rTrg = lob.ListColumns(sFld).DataBodyRange
On Error GoTo 0
Rem Applies Formula
If Not (rTrg Is Nothing) Then rTrg.FormulaR1C1 = sFmlR1C1
Next: End If: Next
End Sub
ListObjects after

ListObject Table Row Count

I have an excel table with one header row and one data body row. I want to count the data body rows. When I'm trying to check how many rows my table has with
Set myWorkSheet= ActiveWorkbook.Worksheets("Sheet1")
Set myTable= myWorkSheet.ListObjects("Table1")
countrows = myTable.ListRows.Count
countrows contains 0. If the has 2 or more rows, it gives the right row number. Why does it say 0 for one row ans is it the best way to count the rows or are there better ones?
EDIT:
Found out whats causing the problem. I use this lines to clear the table before i fill it again:
If tblChart.ListRows.Count >= 1 Then
myTable.DataBodyRange.Delete
End If
After that operation the table looks like I described it. Without it and modifying the table to look like I described the table it worked. But why is it causing this problem?
The ListObject property you are looking for is the .DataBodyRange.
Dim myWorkSheet As Worksheet, myTable As ListObject, countRows As Long
Set myWorkSheet = ActiveWorkbook.Worksheets("Sheet1")
Set myTable = myWorkSheet.ListObjects("Table1")
countRows = myTable.DataBodyRange.Rows.Count
Debug.Print countRows
A comprehensive list of the ListObject properties is available at: ListObject Interface.
Responding late in-case Google search brings user here:
I found this problem as well. DataBodyRange object does not exist unless there are two rows of data. Not just two empty rows, but two rows of data in at least one column.
What I have found to work reliably is to test if DataBodyRange exists:
If Not TableListObject.DataBodyRange is Nothing Then
Debug.Print "Data Rows Count=", TableListObject.DataBodyRange.Rows.Count
Else
Debug.Print "No Data in Table detected"
End if

Resources