Select first 800 visible cells only form a column, even if there are more then 800 visible filtered cells - excel

I need a VBA code, that will allow me to select and copy custom number of visible rows only.
For example: I filtered a column data, and the count of all the visible cells is 1000. However, I want to copy only the first 800 visible cells only out of the 1000 visible cells.

One idea is to get all visible cells using SpecialCells(xlCellTypeVisible) and then loop through and collect them one by one using Application.Union to limit them to your desired amount.
Option Explicit
Public Sub Example()
Dim Top800Cells As Range
Set Top800Cells = GetTopVisibleRows(OfRange:=Range("A:A"), TopAmount:=800)
Top800Cells.Select
End Sub
Public Function GetTopVisibleRows(ByVal OfRange As Range, ByVal TopAmount As Long) As Range
Dim VisibleCells As Range
Set VisibleCells = OfRange.SpecialCells(xlCellTypeVisible)
If VisibleCells Is Nothing Then
Exit Function
End If
Dim TopCells As Range
Dim Count As Long
Dim Row As Range
For Each Row In VisibleCells.Rows
If TopCells Is Nothing Then
Set TopCells = Row
Else
Set TopCells = Application.Union(TopCells, Row)
End If
Count = Count + 1
If Count = TopAmount Then Exit For
Next Row
Set GetTopVisibleRows = TopCells
End Function
If you want to use it as a UDF (user defined function) in a formula SpecialCells(xlCellTypeVisible) is known to fail there (see SpecialCells(xlCellTypeVisible) not working in UDF). And you need to check visibility yourselft:
Public Function GetTopVisibleRows(ByVal OfRange As Range, ByVal TopAmount As Long) As Range
Dim TopCells As Range
Dim Count As Long
Dim Row As Range
For Each Row In OfRange.Rows
If Not Row.EntireRow.Hidden Then
If TopCells Is Nothing Then
Set TopCells = Row
Else
Set TopCells = Application.Union(TopCells, Row)
End If
Count = Count + 1
If Count = TopAmount Then Exit For
End If
Next Row
Set GetTopVisibleRows = TopCells
End Function

Copy First n Rows of SpecialCells(xlCellTypeVisible)
This is usually done to more columns as illustrated in the code.
To apply it just to column A, replace Set rg = ws.Range("A1").CurrentRegion with
Set rg = ws.Range("A1").CurrentRegion.Columns(1)
assuming that the header is in the first worksheet row.
In a nutshell, it loops through the rows (rrg) of each area (arg) of the range (MultiRange, dvrg) counting each row (r) and when it hits the 'mark' (DataRowsCount), it uses this row (Set SetMultiRangeRow = rrg, lrrg) and the first row (frrg) as arguments in the range property to set the required range and reapply the same type of SpecialCells to finally reference the required amount of rows.
Sub ReferenceFirstMultiRangeRows()
' Define constants
Const CriteriaColumn As Long = 1
Const CriteriaString As String = "Yes"
Const DataRowsCount As Long = 800
' Reference the worksheet ('ws').
Dim ws As Worksheet: Set ws = ActiveSheet ' improve!
If ws.AutoFilterMode Then ws.AutoFilterMode = False
' Reference the ranges.
Dim rg As Range ' the range (has headers)
Set rg = ws.Range("A1").CurrentRegion ' you may need to use another way!
Dim drg As Range ' the data range (no headers)
Set drg = rg.Resize(rg.Rows.Count - 1).Offset(1)
' Apply the auto filter to the range.
rg.AutoFilter CriteriaColumn, CriteriaString
' Attempt to reference the visible data range ('vdrg').
Dim vdrg As Range
On Error Resume Next
Set vdrg = drg.SpecialCells(xlCellTypeVisible)
On Error GoTo 0
' Reference the required amount of visible rows ('vdrg').
' Reference the partial range ('vdrg') from the first row
' to the DataRowsCount-th row of the visible range
' and reapply special cells to this range.
If Not vdrg Is Nothing Then ' filtered rows found
Dim lrrg As Range: Set lrrg = SetMultiRangeRow(vdrg, DataRowsCount)
If Not lrrg Is Nothing Then ' there are more rows than 'DataRowsCount'
Dim frrg As Range: Set frrg = vdrg.Rows(1)
Set vdrg = ws.Range(frrg, lrrg).SpecialCells(xlCellTypeVisible)
'Else ' the visible data range is already set; do nothing
End If
'Else ' no filtered rows found; do nothing
End If
ws.AutoFilterMode = False ' remove the auto filter
If vdrg Is Nothing Then
MsgBox "No filtered rows.", vbExclamation
Exit Sub
End If
' Continue using vdrg e.g.:
Debug.Print vdrg.Address ' only the first <=257 characters of the address
'vdrg.Select
'vdrg.Copy Sheet2.Range("A2")
End Sub
Function SetMultiRangeRow( _
ByVal MultiRange As Range, _
ByVal MaxRowNumber As Long) _
As Range
Dim rCount As Long
rCount = MultiRange.Cells.CountLarge / MultiRange.Columns.Count
If rCount < MaxRowNumber Then Exit Function
Dim arg As Range
Dim rrg As Range
Dim r As Long
Dim lrrg As Range
For Each arg In MultiRange.Areas
For Each rrg In arg.Rows
r = r + 1
If r = MaxRowNumber Then
Set SetMultiRangeRow = rrg
Exit For
End If
Next rrg
Next arg
End Function

Related

Function to check for specific value in a range of cells and output 'TRUE' in a helper column

I'm trying to check a range of cells for the value "X" and when the column name where the "X" was found is among an array I have previously specified, I want to have a helper column that would say TRUE otherwise say FALSE.
To illustrate, here's a sample table:
In my sample, I have this array that contains 3 values ( Math, English and History). If there is an X in any of the rows whose header name is in the array, I want the helper column to say TRUE otherwise FALSE. It doesn't have to be all of the values in the array, it can be at least only one.
Here is my code (my original file has more columns than my sample, so my code is liek this)
Sub add_helper()
' Adding helper column
Dim checking As Variant
checking = check_issue() -- this is another function, basically checking will contain the values I want to check in this case Math, English and History, i have confirmed this gets it successfully
Dim wks As Worksheet
Set wks = ActiveSheet
Dim rowRange As Range
Dim colRange As Range
Dim LastCol As Long
Dim LastRow As Long
LastRow = wks.Cells(wks.Rows.Count, "I").End(xlUp).row
Set rowRange = wks.Range("I2:AD" & LastRow)
Set colRange = wks.Range("I1:AD1")
'Loop through each row
For Each rrow In rowRange
Do
For Each cell In colRange
'Do something to each cell
If InStr(checking, cell.value) > 0 Then
If Cells(rrow.row, rrow.Column).value <> "" Then
wks.Range("AI" & rrow.row).value = "TRUE"
Exit For
Else
wks.Range("AI" & rrow.row).value = "FALSE"
End If
End If
Next cell
Loop Until wks.Range("AI" & rrow.row).value <> "TRUE"
Next rrow
End Sub
My code results to just having an input of true whenever there is an X without actually checking if the header column is in my array.
Did you try normal formulas in Excel? You could create a table (a ListObject) with the courses as your array values and the combine SUMPRODUCT with COUNTIF to output True/False in your helper column. Easy to update and adapt:
Notice the table at most right named T_COURSES. The formula in helper column is:
=SUMPRODUCT(--(COUNTIF(T_COURSES,$B$1:$E$1)>0)*--(B2:E2="x"))>0
It works perfectly and it autoupdates changing values:
Match Headers of Matches Against Values in Array
Option Explicit
Sub AddHelper()
Dim checking As Variant: checking = check_issue()
Dim ws As Worksheet: Set ws = ActiveSheet ' improve!
Dim hrg As Range: Set hrg = ws.Range("I1:AD1") ' Header Range
Dim drg As Range ' Data Range
Set drg = ws.Range("I2:AD" & ws.Cells(ws.Rows.Count, "I").End(xlUp).Row)
Dim crg As Range: Set crg = drg.EntireRow.Columns("AI") ' (Helper) Column Range
crg.Value = False
Dim rrg As Range, rCell As Range, r As Long, c As Long, IsFound As Boolean
For Each rrg In drg.Rows
r = r + 1 ' for the (helper) column range
c = 0 ' for the header range
For Each rCell In rrg.Cells
c = c + 1
If StrComp(CStr(rCell.Value), "x", vbTextCompare) = 0 Then
If IsNumeric(Application.Match(CStr(hrg.Cells(c)), checking, 0)) _
Then IsFound = True: Exit For
End If
Next rCell
If IsFound Then crg.Cells(r).Value = True: IsFound = False
Next rrg
End Sub

How to bypass code if criteria don't match?

The code works when the criteria exists. I get an error when the criteria doesn't exist.
' Define constants.
Const srcName As String = "wfm_rawdata"
Const srcFirst As String = "D2" ' Location for Group
Const dstName As String = "bond_insurance"
Const dstFirst As String = "A2" ' do not change the 'A' (entire row).
'This function will transfer rows from one worksheet to another worksheet
' if the value = specified critiera
' Define workbook.
Dim wb As Workbook: Set wb = ActiveWorkbook ' Workbook containing this code.
' Define Source Range
Dim LastRow As Long
Dim srg As Range
' Define worksheet and column am working on and
' getting the range of last used cell using(LastRow)
With wb.Worksheets(srcName).Range(srcFirst)
LastRow = .Offset(.Worksheet.Rows.Count - .Row).End(xlUp).Row
Set srg = .Resize(LastRow - .Row + 1, 10)
End With
'Combine' critical cells into a range.
Dim brg As Range ' Built Range --> Range in the new sheet
Dim cel As Range ' Current Cell Range --> Range in the current sheet(rawdata)
'for every cell in group within wfm_rawdata sheet if the value = GO
For Each cel In srg.Cells
If cel.Value = "BOND INSURANCE" Then
' If the range in the new sheet have nothing then
' add specific criteria from the group in wfm_rawdata
If brg Is Nothing Then
Set brg = cel
' if there is range in there combine the new and
' old range together using -> Union function
Else
Set brg = Union(brg, cel)
End If
End If
Next cel
Application.ScreenUpdating = False
' Copy and delete critical rows of Source Range.
With wb.Worksheets(dstName).Range(dstFirst)
.Resize(.Worksheet.Rows.Count - .Row + 1, _
.Worksheet.Columns.Count).clear
Set brg = brg.EntireRow ' 'Convert' cells into rows.
brg.Copy .Offset ' Copy. 'Offset' because range is in 'With'.
brg.Delete ' Delete.
End With
How can I use a Boolean or other function to bypass the above code if the criteria doesn't exist?
For example if criteria "dog" exists then run the code and if it doesn't exist bypass the code.
I use this code to run three modules with code similar to the top code.
Sub master()
Call report1
Call report2
Call report3
End Sub
One you've assigned srg you can use Match() to check whether it contains any instances of the term you're interested in:
'...
'...
' Define worksheet and column am working on and getting the range of last used cell using(LastRow)
With wb.Worksheets(srcName).Range(srcFirst)
LastRow = .Offset(.Worksheet.Rows.Count - .Row).End(xlUp).Row
Set srg = .Resize(LastRow - .Row + 1, 10)
End With
'Exit if "BOND INSURANCE" is not found in `srg`
If IsError(Application.Match("BOND INSURANCE", srg, 0)) Then Exit Sub
'...
'...

Excel VBA Hiding Rows

In the picture below, I'm trying to hide the rows that are empty in a certain cells (e.g. Row 39 to Row 48). Is is possible to do it in one click? I'm planning of doing it with VBA.
This is the formula that I'm currently using but the thing is the cells that I want to hide may not start at row 39 or ends at row 48, it depends on the data.
Sub HideRows()
Dim ws As Worksheet
For Each ws In Worksheets(Array("NAMES", "AUGUST"))
'ws.Rows("39:48").Hidden = True
Next
End Sub
Hide 'Empty' Rows
This is a slightly different approach:
uses Option Explicit
uses constants and variables
uses For Each...Next loops for both, worksheets and cells
qualifies all objects (e.g. ws.Cells or rg.Cells, not just Cells)
combines empty cells into a range
unhides all rows in one go, then hides the 'empty' rows in another (go)
Option Explicit
Sub HideRows()
Const StartRow As Long = 9
Const EndRow As Long = 89
Const ColNum As Long = 3
Dim WorksheetNames As Variant
WorksheetNames = Array("NAMES", "AUGUST") ' add more
Dim ws As Worksheet ' Current Worksheet
Dim rg As Range ' Current Range
Dim hrg As Range ' Current Hide Range
Dim cCell As Range ' Current Cell in Range
' Loop through the worksheets in the workbook containing this code.
For Each ws In ThisWorkbook.Worksheets(WorksheetNames)
' Create a reference to the range of the current worksheet.
Set rg = ws.Range(ws.Cells(StartRow, ColNum), ws.Cells(EndRow, ColNum))
' or using resize:
'Set rg = ws.Cells(StartRow, ColNum).Resize(EndRow - StartRow + 1)
' Loop through the cells of the current range.
For Each cCell In rg.Cells
If IsEmpty(cCell) Then ' cell is empty
' Combine ('add') the current cell into the hide range.
If Not hrg Is Nothing Then ' for all except the first
Set hrg = Union(hrg, cCell)
Else ' for the first
Set hrg = cCell
End If
'Else ' cell is not empty - do nothing
End If
Next cCell
' Unhide all rows of the current range of the current worksheet.
rg.EntireRow.Hidden = False
If Not hrg Is Nothing Then ' there are combined cells
' Hide the rows of the hide range.
hrg.EntireRow.Hidden = True
' Reset the hide range variable for the next worksheet.
' Also, note that 'Union' works only with ranges from one worksheet.
Set hrg = Nothing
'Else ' there are no combined cells - do nothing
End If
Next ws
End Sub
I already made it. Below is the script that I used.
Sub HideRows()
Dim ws As Worksheet
For Each ws In Worksheets(Array("NAMES", "AUGUST"))
StartRow = 9
EndRow = 89
ColNum = 3
For i = StartRow To EndRow
If Not IsEmpty(Cells(i, ColNum).Value) Then
ws.Cells(i, ColNum).EntireRow.Hidden = False
Else
ws.Cells(i, ColNum).EntireRow.Hidden = True
End If
Next i
Next
End Sub

Finding blank cells and moving row

I am trying to find people who are missing their street address and moving their row to a separate tab in my sheet.
Sub NEW_NoAddress()
Const Title As String = "Move Data Rows"
Const scCol As Long = 6
Const dCol As Long = 1
Const Criteria As String = "ISEmpty()"
' Remove any previous filters.
If Sheet1.AutoFilterMode Then
Sheet1.AutoFilterMode = False
End If
' Filter.
Dim srg As Range ' Source Range (Headers and Data)
Set srg = Sheet1.Range("A1").CurrentRegion
srg.AutoFilter scCol, Criteria
' Count the number of matches.
Dim sdrg As Range ' Source Data Range (Without Headers)
Set sdrg = srg.Resize(srg.Rows.Count - 1).Offset(1)
Dim sdccrg As Range ' Source Data Criteria Column Range
Set sdccrg = sdrg.Columns(scCol)
Dim drCount As Long ' Destination Rows Count (Matches Count)
drCount = Application.Subtotal(103, sdccrg)
' Move if there are matches.
If drCount > 0 Then ' matches found
Dim sdfrrg As Range ' Source Data Filtered Rows Range
Set sdfrrg = sdrg.SpecialCells(xlCellTypeVisible)
Dim dCell As Range ' Destination Cell
Set dCell = Sheet10.Cells(Sheet10.Rows.Count, dCol).End(xlUp).Offset(1, 0)
With sdfrrg
.Copy dCell
' Either delete the entire worksheet rows...
'.EntireColumn.Delete
' ... or remove filter to prevent...
Sheet1.AutoFilterMode = False
' ... deleting the entire worksheet rows leaving possible data
' to the right (after the empty column) intact.
.Delete xlShiftUp
End With
Else ' no matches
Sheet1.AutoFilterMode = False
End If
End Sub
I tried "<>", "<> **", " "" ", I think I tried one that had vbStringISNull, (), and other things I came across in Google. I considered going the other direction and keeping the <> to move those who have an address, but I'd rather move the incorrect entries to my exceptions tab.
Move Matching Rows
I'm glad you like my code. Unfortunately, it has a big mistake:
drCount = Application.Subtotal(103, sdccrg)
which is similar to Excel's ACOUNT which results in 0 when selecting blanks.
I've seen this in a couple of codes and adopted it as valid. Was I in for a surprise.
When you plan on using such a code so intensely, you want to move the changing variables to the arguments section to easily use it many times (see the long procedure below).
You can use the new procedure...
... for your first question like this:
Sub MoveMatchRows()
MoveMatchingRows Sheet1, 4, "FD.Matching Gifts FY22", Sheet2, 1, False
End Sub
... for yesterday's question like this:
Sub NEW_Move_Stock_InKind_DAF()
MoveMatchingRows Sheet1, 44, "<>*/*", Sheet8, 1, False
End Sub
... and for today's question like this:
Sub NewNoAddress()
MoveMatchingRows Sheet1, 6, "=", Sheet10, 1, False
End Sub
I have declared SourceCriteria as variant and added xlFilterValues to be able to use multiple criteria, e.g. Array("1", "2").
The Procedure
Sub MoveMatchingRows( _
ByVal SourceWorksheet As Worksheet, _
ByVal SourceColumn As Long, _
ByVal SourceCriteria As Variant, _
ByVal DestinationWorksheet As Worksheet, _
Optional ByVal DestinationColumn As Long = 1, _
Optional ByVal DoClearPreviousDestinationData As Boolean = False)
Const ProcTitle As String = "Move Matching Rows"
' Remove any previous filters.
If SourceWorksheet.AutoFilterMode Then
SourceWorksheet.AutoFilterMode = False
End If
' Filter.
Dim srg As Range ' Source Range (Headers and Data)
Set srg = SourceWorksheet.Range("A1").CurrentRegion
srg.AutoFilter SourceColumn, SourceCriteria, xlFilterValues
' Create a reference to the Source Data Range (no headers).
Dim sdrg As Range
Set sdrg = srg.Resize(srg.Rows.Count - 1).Offset(1)
' Clear Destination worksheet.
If DoClearPreviousDestinationData Then ' new data, copies headers
DestinationWorksheet.Cells.Clear
End If
' Attempt to create a reference to the Source Data Filtered Rows Range.
Dim sdfrrg As Range
On Error Resume Next
Set sdfrrg = sdrg.SpecialCells(xlCellTypeVisible)
On Error GoTo 0
If Not sdfrrg Is Nothing Then
' Create a reference to the Destination Cell (also, add headers).
Dim dCell As Range ' Destination Cell
Set dCell = DestinationWorksheet.Cells(1, DestinationColumn)
If IsEmpty(dCell) Then
srg.Rows(1).Copy dCell
Set dCell = dCell.Offset(1)
Else
Set dCell = DestinationWorksheet.Cells( _
DestinationWorksheet.Rows.Count, DestinationColumn) _
.End(xlUp).Offset(1, 0)
End If
With sdfrrg
.Copy dCell
' Either delete the entire worksheet rows...
'.EntireColumn.Delete
' ... or remove filter to prevent...
SourceWorksheet.AutoFilterMode = False
' ... deleting the entire worksheet rows leaving possible data
' to the right (after the empty column) intact.
.Delete xlShiftUp
End With
Else ' no matches
SourceWorksheet.AutoFilterMode = False
End If
End Sub

Delete Columns With Blank Headers VBA

I am looking for assistance in deleting two columns within my range of data that have blank headers. These blank headers will appear in the first row of my used range. What would be the best way to go about this? Should I use .Find to search for blank cells in the first row and then get the column address of the two blank cells in order to delete them?
Currently, I am just deleting the columns that I know they'll appear in, but this has the potential to change. Current code:
rngUsed.Columns("F").Delete
rngUsed.Columns("H").Delete
Because the data can change, what would be the better way of handling this?
Thanks!
You can use SpecialCells to find the blanks in the first row and remove the corresponding columns:
Dim rng As Range
Set rng = Range("B3").CurrentRegion 'for example...
On Error Resume Next 'ignore error if no blanks
rng.Rows(1).SpecialCells(xlCellTypeBlanks).EntireColumn.Delete
On Error GoTo 0 'stop ignoring errors
Delete Columns With Blank Headers
The current setup is in Test Mode i.e. it will select the columns to be deleted. If the result is satisfactory, switch to Const TestMode As Boolean = False when the columns will be deleted.
Adjust the values in the constant sections.
The Code
Option Explicit
Sub TESTdeleteBlankHeadered()
Const wsName As String = "Sheet1"
Const ColumnsCount As Long = 2 ' -1 - all columns containing blank headers.
Dim wb As Workbook: Set wb = ThisWorkbook
Dim rg As Range: Set rg = wb.Worksheets(wsName).UsedRange
deleteBlankHeadered rg, ColumnsCount ' first found columns
'deleteBlankHeadered rg, ColumnsCount, True ' last found columns
'deleteBlankHeadered rg ' all found columns
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: In a worksheet, deletes a specified number of its columns,
' defined by blank cells in the first (header) row of
' a given range.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Sub deleteBlankHeadered( _
rg As Range, _
Optional ByVal ColumnsCount As Long = -1, _
Optional ByVal LastOccurringColumns As Boolean = False)
' When 'True', tests with select.
' When 'False', deletes.
Const TestMode As Boolean = True
' Validate inputs.
If rg Is Nothing Then Exit Sub
If ColumnsCount < -1 Or ColumnsCount = 0 Then Exit Sub
' Define Source Row Range.
Dim srg As Range: Set srg = rg.Areas(1).Rows(1)
' Write values from Source Row Range to Data Array.
Dim cCount As Long: cCount = srg.Columns.Count
Dim Data As Variant
If cCount > 1 Then
Data = srg.Value
Else
ReDim Data(1 To 1, 1 To 1): Data(1, 1) = srg.Value
End If
' Define 'elements' of the 'For...Next' Loop.
Dim cFirst As Long, cLast As Long, cStep As Long
If LastOccurringColumns Then
cFirst = cCount: cLast = 1: cStep = -1
Else
cFirst = 1: cLast = cCount: cStep = 1
End If
' Declare additional variables.
Dim drg As Range ' Delete Range
Dim oCount As Long ' Occurrences Count
Dim j As Long ' Data Array (Source Row Range) Columns Counter
' Loop through columns of Data Array and use found blank values
' to combine blank cells with Delete Range.
For j = cFirst To cLast Step cStep
If Not IsError(Data(1, j)) Then
If Len(Data(1, j)) = 0 Then
oCount = oCount + 1
Select Case oCount
Case 1
Set drg = srg.Cells(j)
If ColumnsCount = 1 Then
Exit For
End If
Case ColumnsCount
Set drg = Union(drg, srg.Cells(j))
Exit For
Case Else
Set drg = Union(drg, srg.Cells(j))
End Select
End If
End If
Next
' Declare additional variables.
Dim ActionTaken As Boolean
' Delete Column Ranges (containing blank headers).
If Not drg Is Nothing Then
Application.ScreenUpdating = False
If TestMode Then
drg.Worksheet.Activate
drg.EntireColumn.Select
Else
drg.EntireColumn.Delete
End If
Application.ScreenUpdating = True
ActionTaken = True
End If
' Inform user.
If ActionTaken Then
MsgBox "Columns deleted: " & oCount, vbInformation, "Success"
Else
MsgBox "No columns deleted.", vbExclamation, "No Action Taken"
End If
End Sub

Resources