Deleting Preceding Blank Columns and Rows - excel

I am trying to write a VBA code to cycle through each sheet in the active workbook and delete all blank columns and rows leading up to the first cell with data. For example, if the first cell with data is D5, columns A-C and Rows 1-4 would be deleted leaving the data starting in A1. I have the code below which works for the active sheet but I can't figure out how to get it to loop through the other sheets.
Sub DeleteRowsColumns()
' This will delete all Blank Columns and Rows before any data
Dim ColCounter As Long
Dim RowCounter As Long
Dim SafeCount As Integer
Dim ws As Worksheet
SafeCount = 0
' Check Column A is empty if Yes then Delete till A is populated
For Each ws In ActiveWorkbook.Worksheets
Do While ColCounter = 0
SafeCount = SafeCount + 1
ColCounter = Application.CountA(Columns(1).EntireColumn)
If ColCounter = 0 Then
Columns(1).EntireColumn.Delete
End If
If SafeCount = 50 Then
Exit Do
End If
Loop
Next ws
' Check Row 1 is empty if Yes then Delete till 1 is populated
For Each ws In ActiveWorkbook.Worksheets
SafeCount = 0
Do While RowCounter = 0
SafeCount = SafeCount + 1
RowCounter = Application.CountA(Rows(1).EntireRow)
If RowCounter = 0 Then
Rows(1).EntireRow.Delete
End If
If SafeCount = 50 Then
Exit Do
End If
'Loop
Next ws
MsgBox "Removed Preceding Blank Rows and Columns"
End Sub

Within each loop you need to specify which worksheet you are performing the operations on. Just looping through doesn't solve the problem. For instance:
ColCounter = Application.CountA(ws.Columns(1).EntireColumn)
If ColCounter = 0 Then
ws.Columns(1).EntireColumn.Delete
This ensures you are working in the correct worksheet.
Add it to a loop.
For X = 1 To 50
For i = 1 To 50
ColCounter = Application.CountA(ws.Columns(i).EntireColumn)
If ColCounter = 0 Then
ws.Columns(i).EntireColumn.Delete
End If
rowCounter = Application.CountA(ws.Rows(i).EntireRow)
If rowCounter = 0 Then
ws.Rows(i).EntireRow.Delete
End If
Next i
Next X

You could avoid any looping by first finding where the content starts (by row and then by column)
Sub RemoveEmpties()
Dim f As Range, f2 As Range, ws As Worksheet
For Each ws In ActiveWorkbook.Worksheets
'first occupied cell on sheet (by row)
Set f = ws.Cells.Find(What:="*", After:=ws.Cells(ws.Rows.Count, ws.Columns.Count), _
LookAt:=xlPart, LookIn:=xlFormulas, SearchOrder:=xlByRows, _
SearchDirection:=xlNext, MatchCase:=False)
If Not f Is Nothing Then
'have content, so find first-occupied column
Set f2 = ws.Cells.Find(What:="*", After:=ws.Cells(ws.Rows.Count, ws.Columns.Count), _
LookAt:=xlPart, LookIn:=xlFormulas, SearchOrder:=xlByColumns, _
SearchDirection:=xlNext, MatchCase:=False)
'remove rows/columns as required
If f.Row > 1 Then ws.Cells(1, 1).Resize(f.Row - 1).EntireRow.Delete
If f2.Column > 1 Then ws.Cells(1, 1).Resize(, f2.Column - 1).EntireColumn.Delete
End If
Next ws
End Sub
Alternatively (again only max of two deletes):
Sub RemoveEmpties2()
Dim ws As Worksheet, r As Long, c As Long
For Each ws In ActiveWorkbook.Worksheets
'first make sure there's some content on the sheet...
If Application.CountA(ws.Cells) > 0 Then
r = 1: c = 1
Do While Application.CountA(ws.Rows(r)) = 0
r = r + 1
Loop
If r > 1 Then ws.Rows(1).Resize(r - 1).Delete
Do While Application.CountA(ws.Columns(c)) = 0
c = c + 1
Loop
If c > 1 Then ws.Columns(1).Resize(, c - 1).Delete
End If
Next ws
End Sub

Using the Find Method
The Flow
In the procedure delFirstBlank the workbook is defined. A worksheet variable is declared. In the following For Each Next loop, for each worksheet in the workbook, the procedure deleteFirstBlank is called. When the loop exits, by a message box, the user is informed that the code has finished.
In the deleteFirstBlank procedure, the result of the function getFirstRow is written to a variable. The variable is then tested if it is equal to 0 i.e. the worksheet is blank. If so, then the procedure is exited. If not, the variable is tested if it is greater than 1 i.e. if at least the first row is empty. If so, the rows from the first row to the row defined by the variable decreased by one are deleted. Then the result of the function getFirstRow is written to a variable which is tested if it is greater than 1 i.e. if at least the first column is empty. If so, the columns from the first column to the column defined by the variable decreased by one are deleted.
In the getFirstRow procedure (function) a range variable is declared. Using the Find method, searching by rows, the first found non-blank cell (range) in the supplied worksheet, is assigned to the range variable. If the result of the Find method was a cell range, its row is written as the result of the function. If not, 0 is written as the result i.e. the worksheet is blank.
In the getFirstColumn procedure (function) a range variable is declared. Using the Find method, searching by columns, the first found non-blank cell (range) in the supplied worksheet, is assigned to the range variable. If the result of the Find method was a cell range, its column is written as the result of the function. If not, 0 is written as the result i.e. the worksheet is blank (the latter will never happen, because the worksheet was already tested if it is blank in the 'getFirstRow' procedure).
The Code
Option Explicit
Sub delFirstBlank()
Dim wb As Workbook
Set wb = ActiveWorkbook
Dim ws As Worksheet
For Each ws In wb.Worksheets
deleteFirstBlank ws
Next ws
MsgBox "Removed first blank rows and columns.", vbInformation, "Success"
End Sub
Sub deleteFirstBlank(Sheet As Worksheet)
Dim Current As Long
Current = getFirstRow(Sheet)
If Current = 0 Then GoTo ProcExit ' Blank sheet.
If Current > 1 Then
Sheet.Range(Sheet.Rows(1), Sheet.Rows(CLng(Current) - 1)).Delete
End If
Current = getFirstColumn(Sheet)
If Current > 1 Then
Sheet.Range(Sheet.Columns(1), Sheet.Columns(CLng(Current) - 1)).Delete
End If
ProcExit:
End Sub
Function getFirstRow(Sheet As Worksheet) As Long
Dim rng As Range
Set rng = Sheet.Cells.Find(What:="*", _
After:=Sheet.Cells(Sheet.Rows.Count, _
Sheet.Columns.Count), _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows)
If Not rng Is Nothing Then
getFirstRow = rng.Row
Else
getFirstRow = 0 ' Blank Sheet
End If
End Function
Function getFirstColumn(Sheet As Worksheet) As Long
Dim rng As Range
Set rng = Sheet.Cells.Find(What:="*", _
After:=Sheet.Cells(Sheet.Rows.Count, _
Sheet.Columns.Count), _
LookIn:=xlFormulas, _
SearchOrder:=xlByColumns)
If Not rng Is Nothing Then
getFirstColumn = rng.Column
Else
getFirstColumn = 0 ' Blank Sheet
End If
End Function

Related

Reliably get Last Column in Excel with or without Merged Cells

I recently ran into an issue where my get_lcol function returned A1 as the cells in A1:D1 were merged. I adapted my function to account for this, but then I had some other data with cells merged in A1:D1 but another column in G and my function returned D1 so I adjusted it again. The problem is I don't trust it still to work with all data types as its only checking merged cells in row 1.
Take a look at the below data, how can I reliably get the function to return D or 4 regardless of where I move the merged row and/or any other issues I haven't foreseen?
Current Function:
Public Sub Test_LCol()
Debug.Print Get_lCol(ActiveSheet)
End Sub
Public Function Get_lCol(WS As Worksheet) As Integer
Dim sEmpty As Boolean
On Error Resume Next
sEmpty = IsWorksheetEmpty(Worksheets(WS.Name))
If sEmpty = False Then
Get_lCol = WS.Cells.Find(What:="*", after:=[A1], SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Column
If IsMerged(Cells(1, Get_lCol)) = True Then
If Get_lCol < Cells(1, Get_lCol).MergeArea.Columns.Count Then
Get_lCol = Cells(1, Get_lCol).MergeArea.Columns.Count
End If
End If
Else
Get_lCol = 1
End If
End Function
Update:
Try this data w/ function:
This is a twist on the classic "Find Last Cell" problem
To state the aim:
find the column number of the right most cell containing data
consider merged cell areas that extend beyond other cells containing data. Return the right most column of a merged area should that extend beyond other data.
exclude formatted but empty cells and merged areas
The approach:
Use Range.Find to locate the last data cell
If the last column of the Used Range = Found last data cell column, return that
Else, loop from the last column of the Used Range back to the found data cell column
test for data in that column (.Count > 0), if true return that
test for merged cells in that column (IsNull(.MergeCells))
if found, loop to find the merged area
test the left most cell of the merged area for data
if found return the search column
Note
this may still be vulnerable to other "Last data" issues, eg Autofilter, Hidden rows/columns etc. I haven't tested those cases.
Has the advantage of limiting the search for merged cells to the relavent right most columns
Function MyLastCol(ws As Worksheet) As Long
Dim ur As Range
Dim lastcell As Range
Dim col As Long
Dim urCol As Range
Dim urCell As Range
Set ur = ws.UsedRange
Set lastcell = ws.Cells.Find("*", ws.Cells(1, 1), xlFormulas, , xlByColumns, xlPrevious)
For col = ur.Columns.Count To lastcell.Column - ur.Column + 2 Step -1
Set urCol = ur.Columns(col)
If Application.CountA(urCol) > 0 Then
MyLastCol = urCol.Column
Exit Function
End If
If IsNull(urCol.MergeCells) Then
For Each urCell In urCol.Cells
If urCell.MergeCells Then
If Not IsEmpty(urCell.MergeArea.Cells(1, 1)) Then
MyLastCol = urCol.Column
Exit Function
End If
End If
Next
End If
Next
MyLastCol = lastcell.Column
End Function
#Toddleson got me on the right track, here is what I ended with:
Public Sub Test_LCol()
Debug.Print Get_lCol(ActiveSheet)
End Sub
Public Function Get_lCol(WS As Worksheet) As Integer
On Error Resume Next
If Not IsWorksheetEmpty(WS) Then
Get_lCol = WS.Cells.Find(What:="*", after:=[A1], SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Column
Dim Cell As Range
For Each Cell In WS.UsedRange
If Cell.MergeCells Then
With Cell.MergeArea
If .Cells(.Cells.Count).Column > Get_lCol Then Get_lCol = .Cells(.Cells.Count).Column
End With
End If
Next Cell
Else
Get_lCol = 1
End If
End Function
The Find Method Backed Up by the UsedRange Property: What?
Talking about wasting time...
Option Explicit
Function GetLastColumn( _
ByVal ws As Worksheet) _
As Long
If ws Is Nothing Then Exit Function
' Using the 'Find' method:
'If ws.AutoFilterMode Then ws.AutoFilterMode = False ' (total paranoia)
Dim lcCell As Range
Set lcCell = ws.Cells.Find("*", , xlFormulas, , xlByColumns, xlPrevious)
If Not lcCell Is Nothing Then
GetLastColumn = lcCell.Column
End If
Debug.Print "After 'Find': " & GetLastColumn
' Using the 'UsedRange' property (paranoia):
Dim rg As Range: Set rg = ws.UsedRange
Dim clColumn As Long: clColumn = rg.Columns.Count + rg.Column - 1
If clColumn > GetLastColumn Then
If rg.Address(0, 0) = "A1" Then
If IsEmpty(rg) Then
Exit Function
End If
End If
GetLastColumn = clColumn
'Else ' clColumn is not gt GetLastColumn
End If
Debug.Print "Final (if not 0): " & GetLastColumn
End Function
Sub GetLastColumnTEST()
Debug.Print "Sub Result: " & GetLastColumn(Sheet1)
Debug.Print Sheet1.UsedRange.Address(0, 0)
End Sub
' It works for a few (?) cells, otherwise it returns 'Null'.
Sub TestMergeCells() ' Useless?! Could someone confirm.
Debug.Print Sheet1.Cells.MergeCells ' Null for sure
Debug.Print Sheet1.UsedRange.MergeCells
End Sub

How to set a range using values from referenced cells?

I am trying to construct a macro to fill a certain number of blank cells after the end of my filled cells with zeros. This macro cycles through all of the sheets in the workbook (except the first sheet, which is named in the code). My code is below:
NumSh = ThisWorkbook.Worksheets.Count
For Each sh In ActiveWorkbook.Worksheets
First = LastRow(sh)
If IsError(Application.Match(sh.Name, _
Array("ECG_Log (root)"), 0)) Then
For i = 1 To (NumSh - 1)
LastVal = i + 4
Last = Worksheets("ECG_Log (root)").Cells(LastVal, 12).Value
'MsgBox (First)
Set ZerRng = sh.Range(Cells(First, 1), Cells(Last, 2))
'^^this is where is throws the 1004: range of object failed
'error
Range(ZerRng).Value = 0
Next i
End If
Next
End Sub
Function LastRow(sh As Worksheet)
On Error Resume Next
LastRow = sh.Cells.Find(What:="*", _
After:=sh.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
On Error GoTo 0
End Function
Ideally, I would like to open each worksheet, find the last row ("First"), and define a range from that last row to the value ("Last") from a cell on the first sheet. It seems that the correct values are being found for First and Last, but I am still catching an error when trying to define my range for zeros. Any suggestions?
Fill zeros
Sub FillZeros()
Const cSheet As String = "ECG_Log (root)" ' Source Worksheet Name
Const cFer As Long = 4 ' Source Above First Row Number
Const cCol As Variant = 12 ' Source Column Letter/Number ("L")
Const cCols As String = "A:B" ' Target Columns Address
Dim ws As Worksheet ' Target Worksheet
Dim Fer As Long ' Target First Empty Row
Dim Lr As Long ' Target Last Row
Dim i As Long ' Source Row Counter
' Loop through worksheets in this workbook (workbook containing this code).
For Each ws In ThisWorkbook.Worksheets
' Check if name of current Target Worksheet is NOT equal to Source
' Worksheet Name.
If ws.Name <> cSheet Then
' Increase (count) Source Row.
i = i + 1
' Calculate current Target First Empty Row using LastRow function.
Fer = LastRow(ws) + 1
' Calculate Target Last Row i.e. retrieve value from cell at
' current row and Source Column of Source Worksheet.
Lr = ThisWorkbook.Worksheets(cSheet).Cells(i + cFer, cCol).Value
' In Current Target Columns
With ws.Columns(cCols)
' Prevent error if already done.
On Error Resume Next
' Calcutate Target Range.
' Write zeros to Target Range.
.Rows(Fer).Resize(Lr - Fer + 1) = 0
' Reset error.
On Error GoTo 0
End With
End If
Next
End Sub
Function LastRow(ws As Worksheet) As Long
On Error Resume Next
' The After argument's default parameter is the left upper cell of
' Expression (range) i.e. A1 in this case.
' The LookAt and MatchCase arguments are not important because of
' What:="*".
LastRow = ws.Cells.Find("*", , xlFormulas, , xlByRows, xlPrevious).Row
On Error GoTo 0
End Function

How to delete blank rows?

I have a macro inherited from my coworker who left.
I have a sheet created from a source sheet, consisting of 30000 rows. Including the main data, over a million blank rows are created.
There are no blank rows between. It is 30k+ rows of data without a break.
I made a separate macro that deletes the blank rows after the fact.
I have to run the macro twice.
The first time, the black borders (carried over from the first sheet) are deleted, leaving a million borderless rows.
I run it a second time, which leaves the last used cell.
Sub DeleteUnused()
Dim myLastRow As Long
Dim myLastCol As Long
Dim wks As Worksheet
Dim dummyRng As Range
For Each wks In ActiveWorkbook.Worksheets
With wks
myLastRow = 0
myLastCol = 0
Set dummyRng = .UsedRange
On Error Resume Next
myLastRow = _
.Cells.Find("*", after:=.Cells(1), _
LookIn:=xlFormulas, lookat:=xlWhole, _
searchdirection:=xlPrevious, _
searchorder:=xlByRows).Row
myLastCol = _
.Cells.Find("*", after:=.Cells(1), _
LookIn:=xlFormulas, lookat:=xlWhole, _
searchdirection:=xlPrevious, _
searchorder:=xlByColumns).Column
On Error GoTo 0
If myLastRow * myLastCol = 0 Then
.Columns.Delete
Else
.Range(.Cells(myLastRow + 1, 1), _
.Cells(.Rows.Count, 1)).EntireRow.Delete
.Range(.Cells(1, myLastCol + 1), _
.Cells(1, .Columns.Count)).EntireColumn.Delete
End If
End With
Next wks
End Sub
Here is the macro I use to clean-up all blank rows as well as blank columns.
You can decide if you only want to remove empty rows, and keep empty columns.
Sub Remove_Empty_Rows_And_Columns()
Dim wks As Worksheet
Dim row_rng As Range 'All empty rows will be collected here
Dim col_rng As Range 'All empty columns will be collected here
Dim last_row As Long 'points to the last row in the used range
Dim last_column As Long 'points to the last column in the used range
Dim i As Long 'iterator
Set wks = ActiveSheet
With wks
'finding last row in used range
last_row = .UsedRange.Rows(.UsedRange.Rows.Count).Row
'finding last column
last_column = .UsedRange.Columns(.UsedRange.Columns.Count).Column
'loop through all rows in the used range and
'find if current row is blank or not
For i = 1 To last_row
If Application.WorksheetFunction.CountA(.Rows(i)) = 0 Then
'current row is blank..
If row_rng Is Nothing Then
'this is the first blank row. Lets create a new range for it
Set row_rng = .Rows(i)
Else
'this is not the first. Let's add it to the previous others
Set row_rng = Excel.Union(row_rng, .Rows(i))
End If
End If
Next
'same logic applies for empty rows
For i = 1 To last_column
If Application.WorksheetFunction.CountA(.Columns(i)) = 0 Then
If col_rng Is Nothing Then
Set col_rng = .Columns(i)
Else
Set col_rng = Excel.Union(col_rng, .Columns(i))
End If
End If
Next
End With
'lets check if we managed to find any blank rows
If Not row_rng Is Nothing Then
row_rng.EntireRow.Delete
Else
MsgBox "no rows to delete"
End If
'checking if we found any empty columns
If Not col_rng Is Nothing Then
col_rng.EntireColumn.Delete
Else
MsgBox "no columns to delete"
End If
End Sub
Per my comment this will delete blank rows. Just put this as the last line of the macro that created the blank rows.
Columns("A").SpecialCells(xlCellTypeBlanks).EntireRow.Delete

Splitting a cell column value before comparison

I have two spreadsheets, vda.xlsx and main.xlsm. At the moment I'm comparing the values in:
main.xlsm column J
with
vda.xlsx column A
To see if there is a match. If a match is found then the value in column gets highlighted in red.
However the format of the data in vda.xlsx column A has changed .
It used to look like this
1234
Now it looks like this
Test\1234 or Best\1234 or Jest\1234 - it could be anything...
Sp I need to split Test\1234 by the "\" and extract 1234 for comparison.
Any idea how I can accomplish this. This is my code so far:
Sub VDA_Update()
Dim wshT As Worksheet
Dim wbk As Workbook
Dim wshS As Worksheet
Dim r As Long
Dim m As Long
Dim cel As Range
Application.ScreenUpdating = False
Set wshT = ThisWorkbook.Worksheets("Master")
On Error Resume Next
' Check whether vda.xlsx is already open
Set wbk = Workbooks("vda.xlsx")
On Error GoTo 0
If wbk Is Nothing Then
' If not, open it
Set wbk = Workbooks.Open("C:\Working\vda_test.xlsx")
End If
' Set worksheet on vda.xlsx
Set wshS = wbk.Worksheets("imac01")
m = wshT.Cells(wshT.Rows.Count, 1).End(xlUp).Row
' Loop though cells in column J on main.xlsm
For r = 1 To m
' Can we find the value in column C of vda.xlsx?
Set cel = wshS.Columns(1).Find(What:=wshT.Cells(r, 10).Value, _
LookAt:=xlWhole, MatchCase:=False)
If Not cel Is Nothing Then
' If we find a match, then change the text to red
wshT.Cells(r, 10).Font.ColorIndex = 3
End If
Next r
Application.ScreenUpdating = True
End Sub
Use Split(CellValue, "\") to get an array and then retrieve the last item in the array.
Change:
' Loop though cells in column J on main.xlsm
For r = 1 To m
' Can we find the value in column C of vda.xlsx?
Set cel = wshS.Columns(1).Find(What:=wshT.Cells(r, 10).Value, _
LookAt:=xlWhole, MatchCase:=False)
If Not cel Is Nothing Then
' If we find a match, then change the text to red
wshT.Cells(r, 10).Font.ColorIndex = 3
End If
Next r
To something like:
' Loop though cells in column A on vda.xlsx
For r = 1 To m
' Can we find the value in column J of main.xlsm?
cellSplit = Split(wshS.Cells(r, 1).Value, "\")
Set cel = wshT.Columns(10).Find(cellSplit(UBound(cellSplit)), _
LookAt:=xlWhole, MatchCase:=False)
If Not cel Is Nothing Then
' If we find a match, then change the text to red
cel.Cells(1, 1).Font.ColorIndex = 3
End If
Next r

Trying to delete a row if no data in row A:J

I am trying to delete a row if there is no data from A:J
I have found this code and been trying to edit it, but this is deleting the whole sheet's data eventually.
Any help would be greatly appreciated
Sub DeleteRows()
Dim rngBlanks As Range
Dim i As Integer
For i = 1 To 10
On Error Resume Next
Set rngBlanks = Columns(i).SpecialCells(xlCellTypeBlanks)
On Error GoTo 0
If Not rngBlanks Is Nothing Then
rngBlanks.EntireRow.Delete
End If
Next
End Sub
Trying to delete a row if no data in row A:J
What code is doing is individually checking the columns and not the range A:J as your title suggests. It is very much possible that your entire data is getting deleted because of this. Lets say A1 has some data but B1 doesn't. So your code will delete Row 1. What you have to do is to check if say A1:J1 is blank.
I think this is what you are trying?
Option Explicit
Sub Sample()
Dim ws As Worksheet
Dim rngBlanks As Range
Dim i As Long, lRow As Long, Ret As Long
'~~> Set this to the relevant worksheet
Set ws = ThisWorkbook.Sheets("Sheet2")
With ws
'~~> Get the last row in that sheet
If Application.WorksheetFunction.CountA(.Cells) <> 0 Then
lRow = .Cells.Find(What:="*", _
After:=.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
Else
lRow = 1
End If
'~~> Loop through the rows to find which range is blank
For i = 1 To lRow
Ret = Application.Evaluate("=COUNTA(A" & i & ":J" & i & ")")
If Ret = 0 Then
If rngBlanks Is Nothing Then
Set rngBlanks = .Rows(i)
Else
Set rngBlanks = Union(rngBlanks, .Rows(i))
End If
End If
Next i
End With
'~~~> Delete the range
If Not rngBlanks Is Nothing Then rngBlanks.Delete
End Sub
Another way would be to use Autofilter to delete those ranges
I stepped through your code with a sheet having some non-blank cells in columns A:J down to row 15. Rows 16:18 were entirely blank and D19=1. You want to delete rows that have blanks in every cell from A:J.
On the first iteration of your For..Next loop rngBlanks was not Nothing because typing
?rngBlanks.address
returned $A$1,$A$5:$A$19. A2:A4 were not blank. When you execute
Set rngBlanks = Columns(i).SpecialCells(xlCellTypeBlanks)
it looks for any blanks in column A which is not what you wanted to test. You want to test each row, probably within your ActiveSheet.UsedRange to see if columns A:J are all blank. So you need to define a variable
Dim Rw as Range
and iterate through each Rw in UsedRange
For Each Rw in ActiveSheet.UsedRange
If WorksheetFunction.CountBlank(range(cells(Rw,1),cells(Rw,10))) =0 Then
Rw.EntireRow.Delete
I could post the entire code here but what I've given should put you on the right track.

Resources