I have code that will clear everything below the "----" on a spreadsheet full of data. The "----"comes from an export into excel and I don't want data located under it, so I clear it.
After the code clears everything underneath the "----", I want to sum each column in a range and then place each column total underneath its column of data. The column range is F thru T. I'd like to be able to change this range in the code for other projects.
The row may not be the same each time, so the code must sum the columns after the last row of data.
Can anyone help with this, thanks!
Sub Remove_everything_under()
Dim mtch As Long
mtch = 0
On Error Resume Next
mtch = Application.WorksheetFunction.Match("----", ActiveSheet.Range("A:A"), 0) + 0
On Error GoTo 0
If mtch > 0 Then
ActiveSheet.Range("A" & mtch, ActiveSheet.cells(Rows.Count, Columns.Count)).ClearContents
End If
End Sub
Sum-up Variable Sized Column Ranges
Description
Adjust the values in the constants section.
You can easily rewrite the procedure to use some of the constants as arguments for multi-purpose use.
The following will sum up the columns defined by cAddress and put the results (sums) to the cells below each non-empty column range. Executing it again will double the previous result each next time.
Some Challenges
Sum will fail if an error value so the cells of the column range have to be looped through. Solved.
Sum will sum up date values. Not solved, but not an issue when looping.
The loop will sum up TRUE as -1. Solved, but not an issue when Sum is used.
Option Explicit
Sub sumupVariableSizedColumnRanges()
Const cAddress As String = "F:T" ' Columns Address
Const FirstRow As Long = 2 ' First Row
Const hasDates_Slow As Boolean = True ' If 'True', then loop always.
Dim frOffset As Long: frOffset = FirstRow - 1 ' First Row Offset
Dim rg As Range ' Initial Range: from FirstRow to last worksheet row.
With ActiveSheet.Columns(cAddress)
Set rg = .Resize(.Rows.Count - frOffset).Offset(frOffset)
'Debug.Print "Initial Range address = " & rg.Address(0, 0)
End With
Dim crg As Range ' Column Range
Dim rrg As Range ' Result Range
Dim lCell As Range ' Last Cell (Range)
Dim cError As Long ' Current Error Number
Dim Result As Double ' Result
' If error, then loop.
Dim Data As Variant ' Data Array
Dim cValue As Variant ' Current Value
Dim r As Long ' Data Array Rows Counter
' Starting idea if same last row for all columns:
' Set lCell = rg.Find("*", , xlFormulas, , xlByRows, xlPrevious)
' If lCell Is Nothing Then Exit Sub
' Set rg = rg.Resize(lCell.Row - frOffset)
' Debug.Print "Processing Range address = " & rg.Address(0, 0)
For Each crg In rg.Columns
Set lCell = crg.Find("*", , xlFormulas, , , xlPrevious)
If Not lCell Is Nothing Then
Set rrg = crg.Resize(lCell.Row - frOffset)
'Debug.Print "Current Range address = " & rrg.Address(0, 0)
If hasDates_Slow Then
cError = -1 ' Dates are not summed up.
Else
On Error Resume Next ' Sum 'fails' if error values.
Result = Application.Sum(rrg) ' Dates are also summed up.
cError = Err.Number
On Error GoTo 0
End If
If cError <> 0 Then
'Debug.Print "Current Error Number = " & cError
cError = 0
Result = 0
If rrg.Rows.Count = 1 Then
ReDim Data(1 To 1, 1 To 1): Data = rrg.Value
Else
Data = rrg.Value
End If
For r = 1 To UBound(Data, 1)
cValue = Data(r, 1)
If IsNumeric(cValue) Then
If VarType(cValue) <> vbBoolean Then ' exclude TRUE = -1
Result = Result + cValue
End If
End If
Next r
Erase Data
End If
lCell.Offset(1).Value = Result
Set lCell = Nothing
End If
Next crg
End Sub
Related
Is there in VBA faster way to change row (or cell) color based on values without referring to cell
Referring to cell each time inside loop is very slow, that's why i am looking for faster method doing it in VBA.
Table:
Amount1
Amount2
100
50
20
200
...
...
If Amount1 is greater than Amount2, entire row(or cell) is red, vice versa entire row(or cell) is green.
Thank You!
It would have been helpful if you had clarified why you can't use CF as suggested, but if you really can't when looping it's best to refer to directly to cells as little as possible, especially changing values or formats. Try something like this:
Sub SampleValues()
Dim bGreater As Boolean
Dim rng As Range, rRow As Range
Set rng = ActiveSheet.Range("A1:B1000")
rng.Formula = "=RANDBETWEEN(1,1000)"
rng.Value = rng.Value
End Sub
Sub RedOrGreen()
Dim clr As Long, i as long
Dim rng As Range, rRow As Range
Dim arr As Variant
Const clrMore = vbGreen, clrLessEqual = vbRed
Dim t As Single
t = Timer
Set rng = Range("A1:B1000")
arr = rng.Value
For Each rRow In rng.Rows
i = i + 1
If arr(i, 2) > arr(i, 1) Then
clr = clrMore
Else
clr = clrLessEqual
End If
If rRow.Interior.Color <> clr Then
rRow.Interior.Color = clr
End If
Next
Debug.Print Timer - t
End Sub
Highlight Rows
Sub HighlightRows()
Dim t As Double: t = Timer
' Define constants (adjust).
Const PROC_TITLE As String = "Highlight Rows"
Const SMALL_COL As Long = 1
Const GREAT_COL As Long = 2
Dim RowColors(): RowColors = VBA.Array(vbGreen, vbRed)
' Reference the table range.
' Turn off screen updating.
Application.ScreenUpdating = False
Dim ws As Worksheet: Set ws = ActiveSheet ' improve!
If ws.AutoFilterMode Then ws.AutoFilterMode = False
Dim trg As Range: Set trg = ws.Range("A1").CurrentRegion
' Validate rows and columns.
' Validate rows.
Dim rCount As Long: rCount = trg.Rows.Count
If rCount < 2 Then
MsgBox "No data or just headers in the range '" _
& trg.Address(0, 0) & "'.", vbCritical, PROC_TITLE
Exit Sub ' screen updating will automatically get turned on
End If
' Validate columns.
Dim cCount As Long: cCount = trg.Columns.Count
Dim MaxCol As Long: MaxCol = Application.Max(SMALL_COL, GREAT_COL)
If cCount < GREAT_COL Then
MsgBox "Column " & MaxCol & " is greater than the number " _
& "of columns (" & cCount & ") in the range ('" _
& trg.Address(0, 0) & "').", vbCritical, PROC_TITLE
Exit Sub ' screen updating will automatically get turned on
End If
' Reference and populate the helper columns.
' Insert two helper columns adjacent to the right of the table range.
trg.Offset(, cCount).Resize(, 2).Insert xlShiftToRight
' Remove this line if there is no data to the right.
' Reference the expanded table range (including the helper columns)...
Dim erg As Range: Set erg = trg.Resize(, cCount + 2) ' has headers
' ... and reference its data to be used with 'SpecialCells'.
Dim edrg As Range: Set edrg = erg.Resize(rCount - 1).Offset(1) ' no headers
' Reference the helper columns.
Dim CompareCol As Long: CompareCol = cCount + 1 ' for the auto filter
Dim crg As Range: Set crg = erg.Columns(CompareCol)
Dim irg As Range: Set irg = erg.Columns(cCount + 2)
' Write an ascending integer sequence to the Integer column.
irg.Value = ws.Evaluate("ROW(1:" & rCount & ")")
' Write the values from the criteria columns to arrays.
Dim SmallData(): SmallData = erg.Columns(SMALL_COL).Value
Dim GreatData(): GreatData = erg.Columns(GREAT_COL).Value
' Define the Compare array.
Dim CompareData(): ReDim CompareData(1 To rCount, 1 To 1)
Dim SmallVal, GreatVal, r As Long
' Write the Compare results to the Compare array
' (1 for the 1st color and 2 for the 2nd), ...
For r = 2 To rCount ' skip headers
SmallVal = SmallData(r, 1)
GreatVal = GreatData(r, 1)
If IsNumeric(SmallVal) And IsNumeric(GreatVal) Then
Select Case SmallVal
Case Is < GreatVal: CompareData(r, 1) = 1
Case Is > GreatVal: CompareData(r, 1) = 2
End Select
End If
Next r
Erase SmallData
Erase GreatData
' ... write the results from the array to the Compare column...
crg.Value = CompareData
Erase CompareData
' ... and sort the range by it.
erg.Sort crg, xlAscending, , , , , , xlYes
' Highlight the rows.
edrg.Interior.Color = xlNone ' clear previous colors
Dim vedrg As Range
For r = 1 To 2
erg.AutoFilter CompareCol, CStr(r)
On Error Resume Next ' prevent error when no filtered rows
Set vedrg = edrg.SpecialCells(xlCellTypeVisible)
On Error GoTo 0
ws.AutoFilterMode = False ' turn off the auto filter!!!
If Not vedrg Is Nothing Then
Debug.Print vedrg.Address ' only two areas are being highlighted
vedrg.Interior.Color = RowColors(r - 1) ' 'RowColors' is zero-based
Set vedrg = Nothing ' reset for the next iteration
End If
Next r
' Clean up.
' Sort the range by the Integer column restoring initial order.
erg.Sort irg, xlAscending, , , , , , xlYes
' Delete the helper columns.
crg.Resize(, 2).Delete xlShiftToLeft
' If you have removed the Insert-line, replace this line with:
'crg.Resize(, 2).Clear
' Turn on screen updating to immediately see the changes
' (if the worksheet is active) before OK-ing the message box.
Application.ScreenUpdating = True
Debug.Print Format(Timer - t, "00.000000")
' Inform.
MsgBox "Rows highlighted.", vbInformation, PROC_TITLE
End Sub
This question already has answers here:
Find last used cell in Excel VBA
(14 answers)
Closed 1 year ago.
I need to find the first blank cell in a column. The solution for this is easy assuming there are 2 or more filled cells in the column.
Range("A1").End(xlDown).Offset(1, 0).Select
This stops working if the only populated cell is A1 or if A1 is blank.
In these cases it will select the last cell in the workbook.
Is there any work around that will always select the first blank cell in the column even if that cell happens to be A1 or A2?
Here is a solution that tests if the cell we find is empty and if A1 is empty:
Dim Rng As Range
Set Rng = Range("A1").End(xlDown)
If Rng.Value = "" Then
If Range("A1").Value = "" Then
Range("A1").Select
Else
Range("A2").Select
End If
Else
Rng.Offset(1, 0).Select
End If
In the comment you write that you don't like the order of the code, here is another example:
If Range("A1").Value = "" Then
Range("A1").Select
ElseIf Range("A2").Value = "" Then
Range("A2").Select
Else
Range("A1").End(xlDown).Offset(1, 0).Select
End If
And here is another example that avoids the use of End() and Offset():
Dim Cnt As Long
Cnt = ActiveSheet.UsedRange.Rows.Count
If Cnt = 1 And Range("A1").Value = "" Then Cnt = 0
Range("A" & Cnt + 1).Select
If you add a header row, then this example works:
Range("A" & ActiveSheet.UsedRange.Rows.Count + 1).Select
I always include a header row in all sheets with tabular data, to limit special cases - it's also more user friendly.
Find First Empty Cell by Looping
Empty
Except looping through cells, there are various more or less reliable ways to do it.
If there are hidden rows or columns, many of them will not work.
Even worse, if the worksheet is filtered, probably most of them will not work.
The Basic Loop
If you loop through the cells and test each one of them, you will surely get the correct result.
Function RefFirstEmptyCellInColumnBasic( _
ByVal FirstCell As Range) _
As Range
' Validate the given range ('FirstCell').
If FirstCell Is Nothing Then Exit Function
' Create a reference to the Column Range ('crg').
With FirstCell.Cells(1)
Dim crg As Range: Set crg = .Resize(.Worksheet.Rows.Count - .Row + 1)
End With
' Loop.
Dim cCell As Range
' Loop through the cells of the Column Range...
For Each cCell In crg.Cells
' ... until an empty cell is found.
If IsEmpty(cCell) Then
' Create a reference to the current cell.
Set RefFirstEmptyCellInColumnBasic = cCell
Exit Function
End If
Next cCell
End Function
The issue is that it may take a long time. It will 'behave' for a few thousand rows but e.g. if the first empty cell is the last cell in the column, the previous code takes 'forever' (5s) on my machine.
Loop in Memory (Array)
To remedy this, you can introduce an array into the previous code which will reduce the execution time ten times (0.5s). (Note that it will roughly take 0.05s each time for just writing the values to the array.)
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Creates a reference to the top-most empty cell
' in the one-column range from the first cell of a range
' ('FirstCell') through the last cell in its column.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function RefFirstEmptyCellInColumn( _
ByVal FirstCell As Range) _
As Range
' Validate the given range ('FirstCell').
If FirstCell Is Nothing Then Exit Function
' Create a reference to the Column Range ('crg').
With FirstCell.Cells(1)
Dim crg As Range: Set crg = .Resize(.Worksheet.Rows.Count - .Row + 1)
End With
' Write the values from the Column Range to the Column Data Array ('cData').
Dim cData As Variant
If crg.Rows.Count = 1 Then ' only one cell
ReDim cData(1 To 1, 1 To 1): cData(1, 1) = crg.Value
Else
cData = crg.Value
End If
' Loop.
Dim r As Long
' Loop through the elements of the Column Data Array...
For r = 1 To UBound(cData, 1)
' ... until an empty value is found.
If IsEmpty(cData(r, 1)) Then
' Create a reference to the r-th cell of the Column Range.
Set RefFirstEmptyCellInColumn = crg.Cells(r)
Exit Function
End If
Next r
End Function
The Test
To test the previous you can do the following.
Sub RefFirstEmptyCellInColumnTEST()
Dim ws As Worksheet: Set ws = ActiveSheet
Dim fCell As Range: Set fCell = ws.Range("A3")
' Empty
Dim feCell As Range: Set feCell = RefFirstEmptyCellInColumn(fCell)
If Not feCell Is Nothing Then
Debug.Print feCell.Address(0, 0)
End If
End Sub
Blank
You can do the same for blank cells i.e. empty cells or cells containing a single quote (') or cells containing formulas evaluating to "". Note that cells containing spaces are neither blank nor empty.
Function RefFirstBlankCellInColumnBasic( _
ByVal FirstCell As Range) _
As Range ' (Empty, ="" and ')
' Validate the given range ('FirstCell').
If FirstCell Is Nothing Then Exit Function
' Create a reference to the Column Range ('crg').
With FirstCell.Cells(1)
Dim crg As Range: Set crg = .Resize(.Worksheet.Rows.Count - .Row + 1)
End With
' Loop.
Dim cCell As Range
' Loop through the cells of the Column Range...
For Each cCell In crg.Cells
' (exclude cell containing error value)
If Not IsError(cCell) Then
' ... until a blank cell is found.
If Len(cCell.Value) = 0 Then
' Create a reference to the current cell.
Set RefFirstBlankCellInColumnBasic = cCell
Exit Function
End If
End If
Next cCell
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Creates a reference to the top-most blank cell
' in the one-column range from the first cell of a range
' ('FirstCell') through the last cell in its column.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function RefFirstBlankCellInColumn( _
ByVal FirstCell As Range) _
As Range ' (Empty, ="" and ')
' Validate the given range ('FirstCell').
If FirstCell Is Nothing Then Exit Function
' Create a reference to the Column Range ('crg').
With FirstCell.Cells(1)
Dim crg As Range: Set crg = .Resize(.Worksheet.Rows.Count - .Row + 1)
End With
' Write the values from the Column Range to the Column Data Array ('cData').
Dim cData As Variant
If crg.Rows.Count = 1 Then ' only one cell
ReDim cData(1 To 1, 1 To 1): cData(1, 1) = crg.Value
Else
cData = crg.Value
End If
' Loop.
Dim r As Long
' Loop through the elements of the Column Data Array...
For r = 1 To UBound(cData, 1)
' (exclude error values)
If Not IsError(cData(r, 1)) Then
' ... until a blank is found.
If Len(cData(r, 1)) = 0 Then
' Create a reference to the r-th cell of the Column Range.
Set RefFirstBlankCellInColumn = crg.Cells(r)
Exit Function
End If
End If
Next r
End Function
Sub RefFirstBlankCellInColumnTEST()
Dim ws As Worksheet: Set ws = ActiveSheet
Dim fCell As Range: Set fCell = ws.Range("A3")
' Blank
Dim fbCell As Range: Set fbCell = RefFirstBlankCellInColumn(fCell)
If Not fbCell Is Nothing Then
Debug.Print fbCell.Address(0, 0)
End If
End Sub
I regularly download an excel file that has 1000+ columns, many of these are unwanted and manually deleting them is quite tedious. I found a VBA that will delete the unwanted columns but this method is not suited for a large list.
So, I have a workbook where Sheet1 is the data and columns run from A to BQM. I took all the header names and transposed them into column A in Sheet2 (A2:A1517). I think I'm looking for a way to have the vba look through the table in Sheet2 and delete any matching header titles on Sheet1. Any suggestions? I'm new at this so go slow.
Sub DeleteColumnByHeader()
Set P = Range("A2:BQM2")
For Each cell In P
If cell.Value = "MAP Price" Then cell.EntireColumn.Delete
If cell.Value = "Retail Price" Then cell.EntireColumn.Delete
If cell.Value = "Cost" Then cell.EntireColumn.Delete
If cell.Value = "Additional Specifications" Then cell.EntireColumn.Delete
Next
End Sub
EDIT2: actually works now...
EDIT: added re-positioning of matched columns
Using Match():
Sub DeleteAndSortColumnsByHeader()
Dim wsData As Worksheet, wsHeaders As Worksheet, mHdr, n As Long
Dim wb As Workbook, arr, rngTable As Range, addr
Dim nMoved As Long, nDeleted As Long, nMissing As Long
Set wb = ThisWorkbook 'for example
Set wsData = wb.Sheets("Products")
Set wsHeaders = wb.Sheets("Headers")
'get array of required headers
arr = wsHeaders.Range("A1:A" & _
wsHeaders.Cells(Rows.Count, "A").End(xlUp).Row).Value
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
'shift the data over so we can move columns into the required order
Set rngTable = wsData.Range("a1").CurrentRegion 'original data
addr = rngTable.Address 'remember the position
rngTable.EntireColumn.Insert
Set rngTable = wsData.Range(addr) 'restore to position before insert
'loop over the headers array
For n = 1 To UBound(arr, 1)
mHdr = Application.Match(arr(n, 1), wsData.Rows(1), 0) 'current position of this header
If IsError(mHdr) Then
'required header does not exist - do nothing, or add a column with that header?
wsData.Cells(1, n).Value = arr(n, 1)
nMissing = nMissing + 1
Else
wsData.Columns(mHdr).Cut wsData.Cells(1, n) 'found: move
nMoved = nMoved + 1
End If
Next n
'delete everything not found and moved
With rngTable.Offset(0, rngTable.Columns.Count)
nDeleted = Application.CountA(.Rows(1)) 'count remaining headers
Debug.Print "Clearing: " & .Address
.EntireColumn.Delete
End With
Application.Calculation = xlCalculationAutomatic
Debug.Print "moved", nMoved
Debug.Print "missing", nMissing
Debug.Print "deleted", nDeleted
End Sub
In Sheet2 please clear the cells that display names of columns to delete.
And run the below code.
Sub DeleteColumnByHeader()
For Col = 1517 To 2 Step -1
If Range("Sheet2!A" & Col).Value == "" Then
Columns(Col).EntireColumn.Delete
End If
Next
End Sub
Delete Columns by Headers
The DeleteColumnsByHeaders procedure will do the job.
Adjust the values in the constants section.
The remaining two procedures are here for easy testing.
Testing
To test the procedure, add a new workbook and make sure it contains the worksheets Sheet1 and Sheet2.
Add a module and copy the complete code to it.
Run the PopulateSourceRowRange and the PopulateDestinationColumnRange procedures. Look at the worksheets to see the example setup.
Now run the DeleteColumnsByHeaders procedure. Look at the Destination Worksheet (Sheet1) and see what has happened: all the unwanted columns have been deleted leaving only the 'hundreds'.
Option Explicit
Sub DeleteColumnsByHeaders()
Const sName As String = "Sheet2"
Const sFirst As String = "A2"
Const dName As String = "Sheet1"
Const dhRow As String = "A2:BQM2"
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Create a reference to the Source Column Range (unwanted headers).
Dim srg As Range
Dim srCount As Long
With wb.Worksheets(sName).Range(sFirst)
Dim slCell As Range
Set slCell = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If slCell Is Nothing Then Exit Sub
srCount = slCell.Row - .Row + 1
Set srg = .Resize(srCount)
End With
' Write the values from the Source Range to the Source Data Array.
Dim sData As Variant
If srCount = 1 Then
ReDim sData(1 To 1, 1 To 1): sData(1, 1) = srg.Value
Else
sData = srg.Value
End If
' Create a reference to the Destination Row Range.
Dim drg As Range: Set drg = wb.Worksheets(dName).Range(dhRow)
' Combine all cells containing unwanted headers into the Union Range.
Dim urg As Range
Dim dCell As Range
For Each dCell In drg.Cells
If IsNumeric(Application.Match(dCell, sData, 0)) Then
If urg Is Nothing Then
Set urg = dCell
Else
Set urg = Union(urg, dCell)
End If
End If
Next dCell
Application.ScreenUpdating = False
' Delete the entire columns of the Union Range.
If Not urg Is Nothing Then
urg.EntireColumn.Delete
End If
Application.ScreenUpdating = True
End Sub
' Source Worksheet ('Sheet1'):
' Writes the numbers from 1 to 1807 into the cells of the row range
' and to five rows below.
Sub PopulateSourceRowRange()
With ThisWorkbook.Worksheets("Sheet1").Range("A2:BQM2").Resize(6)
.Formula = "=COLUMN()"
.Value = .Value
End With
End Sub
' Destination Worksheet ('Sheet2'):
' Writes the numbers from 1 to 1807 except the hundreds (100, 200,... 1800)
' to the range 'A2:A1790'. The hundreds are the columns you want to keep.
Sub PopulateDestinationColumnRange()
Dim n As Long, r As Long
r = 1
With ThisWorkbook.Worksheets("Sheet2")
For n = 1 To 1807
If n Mod 100 > 0 Then
r = r + 1
.Cells(r, "A").Value = n
End If
Next n
End With
End Sub
How do I merge cells with the same value and color in a row?
and the result should be :
I think you could try this:
Option Explicit
Sub test()
Dim LastRow As Long, i As Long, Value As Long
Dim Color As Double
With ThisWorkbook.Worksheets("Sheet1")
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
For i = LastRow To 2 Step -1
Value = .Range("A" & i).Value
Color = .Range("A" & i).Interior.Color
If .Range("A" & i - 1).Value = Value And .Range("A" & i - 1).Interior.Color = Color Then
.Rows(i).EntireRow.Delete
End If
Next i
End With
End Sub
Copy Consecutive to One
Adjust the values in the constants section to fit your needs.
The image looks like you want all this to happen in the same column
of the same worksheet, which is adjusted in the constants section.
Before writing to Target Column (cTgtCol), the code will clear its
contents. Be careful not to lose data.
Colors are applied using a loop, which will slow down the fast array approach of copying the data.
The Code
Sub CopyConsecutiveToOne()
' Source
Const cSource As Variant = "Sheet1" ' Worksheet Name/Index
Const cSrcCol As Variant = "A" ' Column Letter/Number
Const cSrcFR As Long = 1 ' Column First Row Number
' Target
Const cTarget As Variant = "Sheet1" ' Worksheet Name/Index
Const cTgtCol As Variant = "A" ' Column Letter/Number
Const cTgtFR As Long = 1 ' Column First Row Number
Dim rng As Range ' Source Column Last Used Cell Range,
' Source Column Range, Target Column Range
Dim vntS As Variant ' Source Array
Dim vntT As Variant ' Target Array
Dim vntC As Variant ' Color Array
Dim i As Long ' Source Range/Array Row/Element Counter
Dim k As Long ' Target/Color Array Element Counter
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
'On Error GoTo ProcedureExit
' In Source Worksheet
With ThisWorkbook.Worksheets(cSource).Columns(cSrcCol)
' Calculate Source Column Last Used Cell Range.
Set rng = .Find("*", , xlFormulas, xlWhole, xlByColumns, xlPrevious)
' Check if data in Source Column.
If Not rng Is Nothing Then ' Data found.
' Calculate Source Range.
Set rng = .Range(.Cells(cSrcFR), .Cells(rng.Row))
' Copy values from Source Range to Source Array.
vntS = rng
Else ' Data Not Found.
With .Cells(1)
MsgBox "No Data in column '" & .Split(.Address, "$")(1) & "'."
GoTo ProcedureExit
End With
End If
End With
' In Arrays
' Count the number of elements in Target/Color Array.
k = 1 ' The first element will be included before the loop.
' Loop through elements of Source Array.
For i = 2 To UBound(vntS)
' Check if current value is different then the previous one.
If vntS(i, 1) <> vntS(i - 1, 1) Then
' Count element of Target/Color Array.
k = k + 1
End If
Next
' Write to Target/Color Arrays
' Resize Target/Color Arrays.
ReDim vntT(1 To k, 1 To 1)
ReDim vntC(1 To k, 1 To 1)
' Reset Counter
k = 1 ' The first element will be included before the loop.
' Write first value from Source Array to Target Array.
vntT(1, 1) = vntS(1, 1)
' Write first color value to Target Color Array.
vntC(1, 1) = rng.Cells(1, 1).Interior.Color
' Loop through elements of Source Array.
For i = 2 To UBound(vntS)
' Check if current value is different then the previous one.
If vntS(i, 1) <> vntS(i - 1, 1) Then
' Count element of Target/Color Array.
k = k + 1
' Write from Source Array to Target Array.
vntT(k, 1) = vntS(i, 1)
' Write color values from Source Range to Color Array.
vntC(k, 1) = rng.Cells(i, 1).Interior.Color
End If
Next
' All necessary data is in Target/Color Arrays.
Erase vntS
Set rng = Nothing
' In Target Worksheet
With ThisWorkbook.Worksheets(cTarget).Cells(cTgtFR, cTgtCol)
' Clear contents of range from Target First Cell to Target Bottom Cell.
.Resize(Rows.Count - .Row + 1).ClearContents
' Calculate Target Column Range.
Set rng = .Resize(k)
' Copy Target Array to Target Range.
rng = vntT
' Apply colors to Target Range.
With rng
' Loop through cells of Target Column Range.
For i = 1 To k
' Apply color to current cell of Target Range using the values
' from Color Array.
.Cells(i, 1).Interior.Color = vntC(i, 1)
Next
End With
End With
ProcedureExit:
With Application
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
End With
End Sub
Create a custom function in Visual Basic Editor that will return to the color index of the cell:
Function COLOR(Target As Range)
COLOR = Target.Interior.ColorIndex
End Function
Then in the right column use a formula similar to this:
=IF(OR(A2<>A3,COLOR(A2)<>COLOR(A3)),1,0)
Then filter to show only 1's.
I am searching a column for cell that contains text and does not contain the word "cat" in the first 6 characters (needs to be case insensitive). This will then cut that entire row to another sheet. Cannot get the code to run without compile errors. the below code is before i try to change it. I do not know how to code it to look at the first 6 characters.
tried instr & iserror but i think my existing code just needs a small alteration which escapes me.
Sub CATDEFECTS()
UsdRws = Range("C" & Rows.Count).End(xlUp).Row
For i = UsdRws To 2 Step -1
If Range("C" & i).Value Like "<>""" And Range("c" & i).Value Like "CAT" Then
Rows(i).Cut Sheets("AWP DEFECTS").Range("A" & rows.Count).End(xlUp).Offset(1)
Rows(i).Delete
End If
Next i
End Sub
Regardless of how you decide to implement the macro, your test to see if a cell is blank is entirely redundant. You can just test if the cell meets your CAT criteria. If it does, it is definitely not blank so no need to test it.
Method 1
You can look at the first 6 characters with LEFT(Range, 6)
If Left(Range("C" & i), 6) Like "*CAT*" Then
This needs Option Compare to work (Thanks #Comintern)
Method 2
I would prefer this method. Its explicit and does not delete or shift anything inside the loop so your action statements are greatly minimized.
Sub Cat()
Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets("Sheet1") '<--UPDATE
Dim ps As Worksheet: Set ps = ThisWorkbook.Sheets("AWP DEFECTS")
Dim LR As Long, DeleteMe As Range, i As Long
LR = ws.Range("C" & ws.Rows.Count).End(xlUp).Row
For i = 2 To LR
If InStr(Left(ws.Range("C" & i), 6), "CAT") Then
If Not DeleteMe Is Nothing Then
Set DeleteMe = Union(DeleteMe, ws.Range("C" & i))
Else
Set DeleteMe = ws.Range("C" & i)
End If
End If
Next i
Application.ScreenUpdating = False
If Not DeleteMe Is Nothing Then
LR = ps.Range("A" & ps.Rows.Count).End(xlUp).Row
DeleteMe.EntireRow.Copy ps.Range("A" & LR)
DeleteMe.EntireRow.Delete
End If
Application.ScreenUpdating = True
End Sub
If cat is within the first 6 characters then InStr will report its position being less than 5.
Sub CATDEFECTS()
dim UsdRws as long, pos as long
UsdRws = Range("C" & Rows.Count).End(xlUp).Row
For i = UsdRws To 2 Step -1
pos =instr(1, cells(i, "C").value2, "cat", vbtextcompare)
If pos > 0 and pos < 5 Then
Rows(i).Cut Sheets("AWP DEFECTS").Range("A" & rows.Count).End(xlUp).Offset(1)
Rows(i).Delete
End If
Next i
End Sub
Criteria Backup (Hide/Delete)
To enable the deletion of the rows in the Source Worksheet you have to set cDEL to True in the constants section. Adjust the other constants to fit you needs.
The Code
Option Explicit
'Option Compare Text
Sub CATDEFECTS()
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
On Error GoTo ProcedureExit
' Source Constants
Const cSource As Variant = "Sheet1" ' Worksheet Name/Index
Const cCol As Variant = "C" ' Search Column Letter/Number
Const cFirstR As Long = 2 ' First Row Number
Const cChars As Long = 6 ' Number of Chars
Const cSearch As String = "CAT" ' Search String
' Target Constants
Const cTarget As Variant = "AWP DEFECTS" ' Worksheet Name/Index
Const cColTgt As Variant = "A" ' Column Letter/Number
Const cFirstRTgt As Long = 2 ' First Row Number
Const cDEL As Boolean = False ' Enable Delete (True)
' Variables
Dim rngH As Range ' Help Range
Dim rngU As Range ' Union Range
Dim vntS As Variant ' Source Array
Dim i As Long ' Source Range Row Counter
' The Criteria
' When the first "cChars" characters do not contain the case-INsensitive
' string "cSearch", the criteria is met.
' Source Worksheet
With ThisWorkbook.Worksheets(cSource)
' Calculate Last Cell in Search Column using the Find method and
' assign it to Help (Cell) Range.
Set rngH = .Columns(cCol).Find("*", , xlFormulas, _
xlWhole, xlByColumns, xlPrevious)
' Calculate Source Column Range from Help (Cell) Range.
If Not rngH Is Nothing Then ' Last Cell was found.
' Calculate Source Column Range and assign it to
' Help (Column) Range using the Resize method.
Set rngH = .Cells(cFirstR, cCol).Resize(rngH.Row - cFirstR + 1)
' Copy Help (Column) Range into 2D 1-based 1-column Source Array.
vntS = rngH
' Show hidden rows to prevent the resulting rows (the rows to be
' hidden or deleted) to appear hidden in Target Worksheet.
rngH.EntireRow.Hidden = False
Else ' Last Cell was NOT found (unlikely).
MsgBox "Empty Column '" & cCol & "'."
GoTo ProcedureExit
End If
' Loop through rows of Source Array.
For i = 1 To UBound(vntS)
' Check if current Source Array value doesn't meet Criteria.
If InStr(1, Left(vntS(i, 1), cChars), cSearch, vbTextCompare) = 0 _
Then ' "vbUseCompareOption" if "Option Compare Text"
' Note: To use the Like operator instead of the InStr function
' you have to use (uncomment) "Option Compare Text" at the beginning
' of the module for a case-INsensitive search and then outcomment
' the previous and uncomment the following line.
' If Not Left(vntS(i, 1), cChars) Like "*" & cSearch & "*" Then
Set rngH = .Cells(i + cFirstR - 1, cCol)
If Not rngU Is Nothing Then
' Union Range contains at least one range.
Set rngU = Union(rngU, rngH)
Else
' Union Range does NOT contain a range (only first time).
Set rngU = rngH
End If
End If
Next
End With
' Target Worksheet
If Not rngU Is Nothing Then ' Union Range contains at least one range.
With ThisWorkbook.Worksheets(cTarget)
' Calculate Last Cell in Search Column using the Find method and
' assign it to Help Range.
Set rngH = .Columns(cColTgt).Find("*", , xlFormulas, _
xlWhole, xlByColumns, xlPrevious)
' Calculate Last Cell from Help Range, but in column 1 ("A").
If Not rngH Is Nothing Then ' Last Cell was found.
Set rngH = .Cells(rngH.Row + 1, 1)
Else ' Last Cell was NOT found.
Set rngH = .Cells(cFirstRTgt - 1, 1)
End If
' Copy the entire Union Range to Target Worksheet starting from
' Help Range Row + 1 i.e. the first empty row (in one go).
' Note that you cannot Cut/Paste on multiple selections.
rngU.EntireRow.Copy rngH
End With
' Hide or delete the transferred rows (in one go).
If cDEL Then ' Set the constant cDEL to True to enable Delete.
rngU.EntireRow.Delete
Else ' While testing the code it is better to use Hidden.
rngU.EntireRow.Hidden = True
End If
End If
ProcedureExit:
With Application
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
End With
End Sub
Remarks
The use of the array did not speed up considerably.
The InStr function was a few milliseconds faster than the Like operator in my data set.
Calculating the Real Used Range and copying it into a Source Array
and then writing the data that meets the criteria from Source Array
to a Target Array and copying the Target Array to the Target
Worksheet, might be faster, and/but would additionally copy the data without formulas or formatting.