Hide rows conditionally in excel table with VBA - vba

I need to be able to hide rows in a table if the first column is blank. I need the macro to work on tables in different sheets so I search for the table name first using listobjects, I have no problem getting the table name. I have seen how to accomplish this with a general range of cells, but not within a Table. Any help is appreciated.
I have a similar macro to unhide rows in the table and it works fine because it simiply loops through all rows in the ListObject variable 'MyTable' and does not have the IF statement.
HideBlankTableRows()
Application.ScreenUpdating = False
Dim ws As Worksheet
Dim myTable As ListObject
Dim row As Range
Set ws = ActiveSheet
Set myTable = ws.ListObjects(1)
For Each row In myTable.DataBodyRange
If row.Columns(1, 1).Value = "" Then ' Error is caused by this row
row.Hidden = True
End If
Next
End Sub

Each row In myTable.DataBodyRange will actually loop through each cell in the body of the table, which you probably don't want. Since you're only checking the first column in each row, it would be faster to loop through each row in the table using Each row In myTable.DataBodyRange.Rows.
Also, the Range object doesn't have a Columns property, so you'll have to you can use the Cells property and provide the row and column number of the cell you want to reference (row 1, column 1).
The updated code would be as follows:
For Each row In myTable.DataBodyRange.Rows
If row.Cells(1, 1).Value = "" Then
row.Hidden = True
End If
Next

In addition to the fix provided by JayCal, you can utilise the ListObject properties to reference the column by name:
For Each rw In myTable.ListColumns("ColumnName").DataBodyRange
If rw.Value = vbNullString Then
rw.EntireRow.Hidden = True
End If
Next
You could also use the ListObject AutoFilter method
myTable.Range.AutoFilter Field:=lo.ListColumns("ColumnName").Index, Criteria1:="<>"

Related

Get row and column number of first cell in Excel table

I use a lot of tables in my code
My table is somewhere in my worksheet.
I know I can go to the first cell with the following code:
Worksheets("sheet").ListObjects("table").Range.Cells(1, 1).Activate
But I would like to store the row and column number in 2 integers ie. column = 3 and row = 4 if first cell of table is C4.
Worksheets("sheet").ListObjects("table").Row and Column are not working unfortunately
This prints the row and the column of the first cell of the table:
Public Sub TestMe()
Dim tbl As ListObject
Set tbl = Worksheets(1).ListObjects("Table1")
Debug.Print tbl.Range.Cells(1, 1).Row
Debug.Print tbl.Range.Cells(1, 1).Column
'As a bonus:
Debug.Print tbl.Range.Rows.Count 'total number of rows
Debug.Print tbl.Range.Columns.Count 'total number of columns
End Sub
Very dirty way, using your code, which is activating the Cells(1,1):
Debug.Print ActiveCell.Row
Debug.Print ActiveCell.Column
You're nearly there. You need:
Worksheets("sheet").ListObjects("table").Range.Cells(1, 1).Row
... to return the absolute row number within the spreadsheet, of your table's first row.
Obviously, the same syntax to return the column number.

How do i get autofilter field indicator to indicate the correct column?

I have a spreadsheet w/30 or so columns. My goal is to filter the set based on 1 column and my approach was the specify a range being just that column and then filter that range. I do have autofilters on every column and when i specify a field:=1 excel picks the first column...whcih is outside of my range. So it's always trying to filter on column "A"...not column "U" as desired. Am i mis-understanding how to use this field? i though it was an offset w/in a range.
here's a simple example
Dim r As Range
Dim sheet As Worksheet
Set sheet = ThisWorkbook.Worksheets("test")
sheet.Range("u1:u9").AutoFilter field:=1, Criteria1:="Will"
as an aside...is there a way to get a column number associated with the column letter? for example U --> 21. if so i could select the entire spreadsheet as the range and do an offset of 21
If you are applying filters to one column, you will have to clear the existing filters first.
Your tweaked code would be something like this and it will apply the filter to column U only...
Sub test()
Dim r As Range
Dim sheet As Worksheet
Set sheet = ThisWorkbook.Worksheets("test")
sheet.AutoFilterMode = False
sheet.Range("u1:u9").AutoFilter field:=1, Criteria1:="Will"
End Sub
OR
You may also apply the filters to all the columns and specify the field criteria correctly.
Give this a try...
Sub test2()
Dim r As Range
Dim sheet As Worksheet
Set sheet = ThisWorkbook.Worksheets("test")
Set r = sheet.Range("U1")
sheet.AutoFilterMode = False
sheet.Range("A1").CurrentRegion.AutoFilter field:=r.Column, Criteria1:="Will"
End Sub

Copy/Paste rows to matching named sheet

I have a worksheet "List" which has rows of data that I need to copy to other worksheets. In column "J" of "List", there is a name (Matthew, Mark, Linda, etc.) that designates who's data that row is.
Each of those names (22 in all) has a matching spreadsheet with the same name. I want all rows that say "Linda" in column "J" to paste to worksheet "Linda", all rows with "Matthew" to paste to worksheet "Matthew", etc.
I have some code below, which mostly works, but I'd have to rewrite it for all 22 names/sheets.
Is there a way to loop through all the sheets, pasting the rows with matching names? Also, the code below works really slowly, and I'm using data sets with anywhere from 200 to 60,000 rows that need sorted and pasted, which means that if its slow on a small data set like the one I'm currently working on, and only for one sheet, it's going to be glacially slow for the big data sets.
Sub CopyMatch()
Dim c As Range
Dim j As Integer
Dim Source As Worksheet
Dim Target As Worksheet
Set Source = Worksheets("List")
Set Target = Worksheets("Linda")
j = 4 ' Start copying to row 1 in target sheet
For Each c In Source.Range("J4:J1000") ' Do 1000 rows
If c = "Linda" Then
Source.Rows(c.Row).Copy Target.Rows(j)
j = j + 1
End If
Next c
End Sub
Unless you've turned calculation off somewhere we can't see here, then every time you copy a row, Excel is recalculating - even if your sheets contain no formulas.
If you're not doing so already, simply putting:
application.calculation=xlcalculationmanual
before you start your loop and:
application.calculation=xlcalculationautomatic
after exiting the loop will massively speed up your loop. For extra swank, you can use a variable to store the calculation setting before you turn it off and restore that setting at the end, e.g.
dim lCalc as long
lCalc = application.calculation
application.calculation = xlcalculationmanual
for ... next goes here
application.calculation = lCalc
Also consider other settings, e.g.: application.screenupdating=False|True.
Sort the data by the name you're selecting on, then by any other sorts you want. That way you can skip through any size sheet in 22 steps (since you say you have 22 names).
How you copy the data depends on preference and how much data there is. Copying one row at a time is economical on memory and pretty much guaranteed to work, but is slower. Or you can identify the top and bottom rows of each person's data and copy the whole block as a single range, at the risk of exceeding the memory available on large blocks in large sheets.
Assuming the value in your name column, for the range you're checking, is always one of the 22 names, then if you've sorted first by that column you can use the value in that column to determine the destination, e.g.:
dim sTarget as string
dim rng as range
sTarget = ""
For Each c In Source.Range("J4:J1000") ' Do 1000 rows
if c <> "" then ' skip empty rows
if c <> sTarget then ' new name block
sTarget = c
Set Target = Worksheets(c)
set rng = Target.cells(Target.rows.count, 10).end(xlup) ' 10="J"
j = rng.row + 1 ' first row below last name pasted
end if
Source.Rows(c.Row).Copy Target.Rows(j)
j = j + 1
end if
Next
This is economical of memory because you're going row by row, but still reasonably fast because you're only recalculating Target and resetting j when the name changes.
you could use:
Dictionary object to quickly build the list of unique names out of column J names
AutoFilter() method of Range object for filtering on each name:
as follows
Option Explicit
Sub CopyMatch()
Dim c As Range, namesRng As Range
Dim name As Variant
With Worksheets("List") '<--| reference "List" worskheet
Set namesRng = .Range("J4", .Cells(.Rows.count, "J").End(xlUp)) '<--| set the range of "names" in column "J" starting from row 4 down to last not empty row
End With
With CreateObject("Scripting.Dictionary") '<--| instance a 'Dictionary' object
For Each c In namesRng.SpecialCells(xlCellTypeConstants, xlTextValues) '<--| loop through "names" range cells with text content only
.item(c.Value) = c.Value '<--| build the unique list of names using dictionary key
Next
Set namesRng = namesRng.Resize(namesRng.Rows.count + 1).Offset(-1) '<--| resize the range of "names" to have a "header" cell (not a name to filter on) in the first row
For Each name In .Keys '<--| loop through dictionary keys, i.e. the unique names list
FilterNameAndCopyToWorksheet namesRng, name '<--| filter on current name and copy to corresponding worksheet
Next
End With '<--| release the 'Dictionary' object
End Sub
Sub FilterNameAndCopyToWorksheet(rangeToFilter As Range, nameToFilter As Variant)
Dim destsht As Worksheet
Set destsht = Worksheets(nameToFilter) '<--| set the worksheet object corresponding to passed name
With rangeToFilter
.AutoFilter Field:=1, Criteria1:=nameToFilter
Intersect(.Parent.UsedRange, .Resize(.Rows.count - 1).Offset(1).SpecialCells(xlCellTypeVisible).EntireRow).Copy destsht.Cells(destsht.Rows.count, "J").End(xlUp)
.Parent.AutoFilterMode = False
End With
End Sub

How to delete row based on cell value

I have a worksheet, I need to delete rows based on cell value ..
Cells to check are in Column A ..
If cell contains "-" .. Delete Row
I can't find a way to do this .. I open a workbook, copy all contents to another workbook, then delete entire rows and columns, but there are specific rows that has to be removed based on cell value.
Need Help Here.
UPDATE
Sample of Data I have
The easiest way to do this would be to use a filter.
You can either filter for any cells in column A that don't have a "-" and copy / paste, or (my more preferred method) filter for all cells that do have a "-" and then select all and delete - Once you remove the filter, you're left with what you need.
Hope this helps.
The screenshot was very helpful - the following code will do the job (assuming data is located in column A starting A1):
Sub RemoveRows()
Dim i As Long
i = 1
Do While i <= ThisWorkbook.ActiveSheet.Range("A1").CurrentRegion.Rows.Count
If InStr(1, ThisWorkbook.ActiveSheet.Cells(i, 1).Text, "-", vbTextCompare) > 0 Then
ThisWorkbook.ActiveSheet.Cells(i, 1).EntireRow.Delete
Else
i = i + 1
End If
Loop
End Sub
Sample file is shared: https://www.dropbox.com/s/2vhq6vw7ov7ssya/RemoweDashRows.xlsm
You could copy down a formula like the following in a new column...
=IF(ISNUMBER(FIND("-",A1)),1,0)
... then sort on that column, highlight all the rows where the value is 1 and delete them.
if you want to delete rows based on some specific cell value.
let suppose we have a file containing 10000 rows, and a fields having value of NULL.
and based on that null value want to delete all those rows and records.
here are some simple tip.
First open up Find Replace dialog, and on Replace tab, make all those cell containing NULL values with Blank.
then press F5 and select the Blank option, now right click on the active sheet, and select delete, then option for Entire row.
it will delete all those rows based on cell value of containing word NULL.
If you're file isn't too big you can always sort by the column that has the - and once they're all together just highlight and delete. Then re-sort back to what you want.
You can loop through each the cells in your range and use the InStr function to check if a cell contains a string, in your case; a hyphen.
Sub DeleteRowsWithHyphen()
Dim rng As Range
For Each rng In Range("A2:A10") 'Range of values to loop through
If InStr(1, rng.Value, "-") > 0 Then 'InStr returns an integer of the position, if above 0 - It contains the string
rng.Delete
End If
Next rng
End Sub
This is the autofilter macro you could base a function off of:
Selection.AutoFilter
ActiveSheet.Range("$A$1:$A$10").AutoFilter Field:=1, Criteria1:="=*-*", Operator:=xlAnd
Selection.AutoFilter
I use this autofilter function to delete matching rows:
Public Sub FindDelete(sCol As String, vSearch As Variant)
'Simple find and Delete
Dim lLastRow As Integer
Dim rng As Range
Dim rngDelete As Range
Range(sCol & 1).Select
[2:2].Insert
Range(sCol & 2) = "temp"
With ActiveSheet
.usedrange
lLastRow = .Cells.SpecialCells(xlCellTypeLastCell).Row
Set rng = Range(sCol & 2, Cells(lLastRow, sCol))
rng.AutoFilter Field:=1, Criteria1:=vSearch, Operator:=xlAnd
Set rngDelete = rng.SpecialCells(xlCellTypeVisible)
rng.AutoFilter
rngDelete.EntireRow.Delete
.usedrange
End With
End Sub
call it like:
call FindDelete "A", "=*-*"
It's saved me a lot of work. Good luck!

Add new row to excel Table (VBA)

I have an excel which serves to record the food you ingest for a specific day and meal. I hav a grid in which each line represent a food you ate, how much sugar it has, etc.
Then i've added an save button to save all the data to a table in another sheet.
This is what i have tried
Public Sub addDataToTable(ByVal strTableName As String, ByRef arrData As Variant)
Dim lLastRow As Long
Dim iHeader As Integer
Dim iCount As Integer
With Worksheets(4).ListObjects(strTableName)
'find the last row of the list
lLastRow = Worksheets(4).ListObjects(strTableName).ListRows.Count
'shift from an extra row if list has header
If .Sort.Header = xlYes Then
iHeader = 1
Else
iHeader = 0
End If
End With
'Cycle the array to add each value
For iCount = LBound(arrData) To UBound(arrData)
**Worksheets(4).Cells(lLastRow + 1, iCount).Value = arrData(iCount)**
Next iCount
End Sub
but i keep getting the same error on the highlighted line:
Application-defined or object-defined error
What i am doing wrong?
Thanks in advance!
You don't say which version of Excel you are using. This is written for 2007/2010 (a different apprach is required for Excel 2003 )
You also don't say how you are calling addDataToTable and what you are passing into arrData.
I'm guessing you are passing a 0 based array. If this is the case (and the Table starts in Column A) then iCount will count from 0 and .Cells(lLastRow + 1, iCount) will try to reference column 0 which is invalid.
You are also not taking advantage of the ListObject. Your code assumes the ListObject1 is located starting at row 1. If this is not the case your code will place the data in the wrong row.
Here's an alternative that utilised the ListObject
Sub MyAdd(ByVal strTableName As String, ByRef arrData As Variant)
Dim Tbl As ListObject
Dim NewRow As ListRow
' Based on OP
' Set Tbl = Worksheets(4).ListObjects(strTableName)
' Or better, get list on any sheet in workbook
Set Tbl = Range(strTableName).ListObject
Set NewRow = Tbl.ListRows.Add(AlwaysInsert:=True)
' Handle Arrays and Ranges
If TypeName(arrData) = "Range" Then
NewRow.Range = arrData.Value
Else
NewRow.Range = arrData
End If
End Sub
Can be called in a variety of ways:
Sub zx()
' Pass a variant array copied from a range
MyAdd "MyTable", [G1:J1].Value
' Pass a range
MyAdd "MyTable", [G1:J1]
' Pass an array
MyAdd "MyTable", Array(1, 2, 3, 4)
End Sub
Tbl.ListRows.Add doesn't work for me and I believe lot others are facing the same problem. I use the following workaround:
'First check if the last row is empty; if not, add a row
If table.ListRows.count > 0 Then
Set lastRow = table.ListRows(table.ListRows.count).Range
For col = 1 To lastRow.Columns.count
If Trim(CStr(lastRow.Cells(1, col).Value)) <> "" Then
lastRow.Cells(1, col).EntireRow.Insert
'Cut last row and paste to second last
lastRow.Cut Destination:=table.ListRows(table.ListRows.count - 1).Range
Exit For
End If
Next col
End If
'Populate last row with the form data
Set lastRow = table.ListRows(table.ListRows.count).Range
Range("E7:E10").Copy
lastRow.PasteSpecial Transpose:=True
Range("E7").Select
Application.CutCopyMode = False
Hope it helps someone out there.
I had the same error message and after lots of trial and error found out that it was caused by an advanced filter which was set on the ListObject.
After clearing the advanced filter .listrows.add worked fine again.
To clear the filter I use this - no idea how one could clear the filter only for the specific listobject instead of the complete worksheet.
Worksheets("mysheet").ShowAllData
I actually just found that if you want to add multiple rows below the selection in your table
Selection.ListObject.ListRows.Add AlwaysInsert:=True works really well. I just duplicated the code five times to add five rows to my table
I had the same problem before and i fixed it by creating the same table in a new sheet and deleting all the name ranges associated to the table, i believe whene you're using listobjects you're not alowed to have name ranges contained within your table hope that helps thanks
Ran into this issue today (Excel crashes on adding rows using .ListRows.Add).
After reading this post and checking my table, I realized the calculations of the formula's in some of the cells in the row depend on a value in other cells.
In my case of cells in a higher column AND even cells with a formula!
The solution was to fill the new added row from back to front, so calculations would not go wrong.
Excel normally can deal with formula's in different cells, but it seems adding a row in a table kicks of a recalculation in order of the columns (A,B,C,etc..).
Hope this helps clearing issues with .ListRows.Add
As using ListRow.Add can be a huge bottle neck, we should only use it if it can’t be avoided.
If performance is important to you, use this function here to resize the table, which is quite faster than adding rows the recommended way.
Be aware that this will overwrite data below your table if there is any!
This function is based on the accepted answer of Chris Neilsen
Public Sub AddRowToTable(ByRef tableName As String, ByRef data As Variant)
Dim tableLO As ListObject
Dim tableRange As Range
Dim newRow As Range
Set tableLO = Range(tableName).ListObject
tableLO.AutoFilter.ShowAllData
If (tableLO.ListRows.Count = 0) Then
Set newRow = tableLO.ListRows.Add(AlwaysInsert:=True).Range
Else
Set tableRange = tableLO.Range
tableLO.Resize tableRange.Resize(tableRange.Rows.Count + 1, tableRange.Columns.Count)
Set newRow = tableLO.ListRows(tableLO.ListRows.Count).Range
End If
If TypeName(data) = "Range" Then
newRow = data.Value
Else
newRow = data
End If
End Sub
Just delete the table and create a new table with a different name. Also Don't delete entire row for that table. It seems when entire row containing table row is delete it damages the DataBodyRange is damaged