Select a range, avoiding hidden cells, using ActiveCell.Offset() - vba

I am running a macro, that asks for a sheet name and a reference cell, and then selects a range of cells, surrounding the cell of our choice.
After applying a filter to my data, some of the rows become hidden, as they are not needed.
The problem is, that the macro does not take that into consideration and counts the hidden rows too.
Here is the code, that I use in the original version of the macro:
.....after applying some InputBox and a search for the user's value, the following row is executed:
Range(ActiveCell.Offset(90, 0), ActiveCell.Offset(-252, 2)).Select
Selection.Copy
In this way hidden rows are included in the selection.
I tried the following modification
Range(ActiveCell.Offset(90, 0), ActiveCell.Offset(-252, 2)).SpecialCells(xlCellTypeVisible).Select
Selection.Copy
However without success.
I was wondering, can anyone suggest a way to use ActiveCell.Offset in combination with SpecialCells(xlCellTypeVisible) resulting in the above described functionality of the macro -namely to select a range of cells avoiding the hidden rows after filtering?

To select just the visible cells from a range of selected cells, you can use the following line of code:
Selection.SpecialCells(xlCellTypeVisible).Select
Like in this Example:
Range(ActiveCell.Offset(90, 0), ActiveCell.Offset(-252, 2)).Select
Selection.SpecialCells(xlCellTypeVisible).Select
Selection.Copy
The Code Below is a example on how you could count the Rows.
So but there is a Problem where you have to think about.
If You Paste the Selection to your Original Sheet, there is a Chance that there are Hidden Rows in the area where you Copied the Selection.
If this is so, The Text which you copied there will be Hidden too.
So you have to Copied the Data to a new Sheet to avoid that Problem or you have to Copied the Data at the bottom of the Sheet 1.
Option Explicit
'Define a Constant for the Amount of Rows you Need
Private Const ConstAmountRows As Integer = 40
Sub Test()
Dim intCountedRows As Integer
Dim idx As Integer
Dim intDifference As Integer
idx = 0
Do
If Not (intCountedRows = ConstAmountRows) Then
intCountedRows = ConstAmountRows
idx = idx + 1
End If
Sheets("Sheet1").Select
'Select the Range with the Amount of Rows you need ideally
Range("A1:A" & intCountedRows + idx).Select
'Select only the Visible Cells
Selection.SpecialCells(xlCellTypeVisible).Select
Selection.Copy
Sheets("Sheet2").Select 'Select another Sheet
'***-> Her you can select the Place you want to Paste the Text<-***
Range("B1").Select
ActiveSheet.Paste
'*** Count the Rows that you Paste
intCountedRows = Selection.Rows.Count
'if the Counted Rows are not equal to the Amount. Repeat
Loop While Not (intCountedRows >= ConstAmountRows)
End Sub

Related

Copy, PasteSpecial Formatting select cells based on criteria in another cell

Not a VBA expert at all and would really appreciate any help you experts could offer. I have a spread sheet with 10 columns (A-J) and a "control" column (M). Rows of data will be populated beginning with Row 9, with Row 8 being the header row. Row 7 contains specific formatting to be applied to rows if criteria in column M is met. For several reasons, I need to use VBA rather than conditional formatting. The code I have almost works. It worked on the initial row that met the criteria but none of the subsequent rows. Tried to fix the issue and now none of it works.
The specifics of my current code are:
rngSheet - the area of my spreadsheet with data that needs to be formatted.
rngColorTrigger - Column M containing the criteria. Criteria trigger is if the column value is 0.
rngColor - the cells containing the format to be copied and pasted (Row 7).
My current very-bad-doesn't-work code:
Private Sub Worksheet_SelectionChange2(ByVal Target As Range)
Dim rngColor As Range
Dim rngSheet As Range
Dim rngColorTrigger As Range
Set rngColor = Sheet1.Range("rngColor")
Set rngSheet = Sheet1.Range("rngSheet")
Set rngColorTrigger = Sheet1.Range("rngColorTrigger")
' Limit the copy area to the range for which the trigger is entered
If Not Intersect(Target, Sheet1.Range("rngColorTrigger")) Is Nothing Then
' Only trigger if the value entered is valid (Cell in Column M = 0)
For Each cell In Range("rngColorTrigger")
If cell.Value = 0 Then
Sheet1.Range("rngColor").Select
Range(Selection, Selection.End(xlDown)).Select
Selection.Copy
Sheet1.Range("rngSheet").Select
Selection.PasteSpecial Paste:=xlPasteFormats
End If
Next cell
' Reset EnableEvents
Application.EnableEvents = True
End If
End Sub
I have very similar code that copy, cuts, and pastes content between pages based on a valid date being entered in a control column and it works fine...

Excel to CountIF in filtered data

I am trying to count the number of occurrences of a specific string in filtered data. I can do it using a formula in a cell but when I combine that with the other macros in my workbook the whole thing freezes.
So I would like to move the calculation to VBA so that it only calculates when the macro is run. Here is the formula that works in the cell:
=SUMPRODUCT(SUBTOTAL(3,OFFSET('2015 Master'!H:H,ROW('2015 Master'!H:H)-MIN(ROW('2015 Master'!H:H)),,1)),ISNUMBER(SEARCH("*Temp*",'2015 Master'!H:H))+0)
Basically I want to count the number of times "Temp" occurs in column H but only in the filtered data.
Thank you for your help!
ADDITION:
Here is the code I've written for the macro so far. It filters the data on a different sheet then updates the pivot table with the date range. I would like to add the count calculations to the end of this code and return the count to a cell on the 'Reporting' sheet.
Sub Button1_Click()
'Refresh the pivot table and all calculations in the active sheet
ActiveWorkbook.RefreshAll
'Gather the start and end times from the active sheet
dStart = Cells(2, 5).Value
dEnd = Cells(3, 5).Value
'Change the active sheet to the alarms database, clear all filters and then filter for the defined date range and filter for only GMP alarms
Sheets("2015 Master").Select
If ActiveWorkbook.ActiveSheet.FilterMode Or ActiveWorkbook.ActiveSheet.AutoFilterMode Then
ActiveWorkbook.ActiveSheet.ShowAllData
End If
ActiveSheet.ListObjects("Table44").Range.AutoFilter Field _
:=3, Criteria1:=">=" & dStart, Operator:=xlAnd, Criteria2:= _
"<=" & dEnd
Range("Table44[[#Headers],[GMP or non-GMP]]").Select
ActiveSheet.ListObjects("Table44").Range.AutoFilter Field:=2, Criteria1:= _
"GMP"
'Change the active sheet to the Reporting sheet
Sheets("Reporting").Select
'Within the alarms pivot table clear the label filters then filter for the date range and GMP alarms
ActiveSheet.PivotTables("PivotTable1").PivotFields("Active Time"). _
ClearLabelFilters
ActiveSheet.PivotTables("PivotTable1").PivotFields("Active Time").PivotFilters. _
Add Type:=xlDateBetween, Value1:=dStart, Value2:=dEnd
ActiveSheet.PivotTables("PivotTable1").PivotFields("GMP or non-GMP"). _
CurrentPage = "GMP"
End Sub
Pertinent to clarified question topic (i.e. " Basically I want to count the number of times "Temp" occurs in column H..."), the VBA solution can be as shown in the following code snippet. Assuming sample data entered in Column "H":
H
Temp Directory on C: Drive
Temp Directory
Project Directory
Output Temp Directory
Start Directory
Temp obj
apply the VBA Macro:
Sub CountTempDemo()
Dim i As Integer
Dim count As Integer
Dim startRow As Integer
Dim lastRow As Integer
Dim s As String
startRow = 2 'or use your "filtered range"
lastRow = Cells(Rows.count, "H").End(xlUp).Row 'or use your "filtered range"
count = 0
For i = 2 To lastRow
If InStr(Cells(i, 8).Value, "Temp") > 0 Then
count = count + 1
End If
Next
End Sub
where count value of 4 is a number of "Temp" occurrences in specified "H" range.
Hope this may help. Best regards,
To iterate over a column and find only visible (unfiltered) cells, one way is this:
Set h = ... Columns ("H");
Set r = h.SpecialCells(xlCellTypeVisible)
' now r is a composite range of potentially discontiguous cells
' -- it is composed of zero or more areas
'but only the visible cells; all hidden cells are skipped
Set ar = r.Areas
for ac = 1 to ar.Count
Set rSub = ar(ac)
'rSub is a contiguous range
'you can use a standard formula, e.g. Application.WorksheetFunction.CountIf(...)
'or loop over individual elements
'and count what you like
next
caveats: if any rows (or the column) are hidden manually (not from filtering) the count using this method will consider them as filtered (i.e. hidden/not visible).
Update: answer to comment
A Range is really a very general purpose notion of an aggregation of cells into a grouping or collecting object (the Range). Even though we usually think of a Range as being a box or rectangle of cells (i.e. contiguous cells), a Range can actually assemble discontiguous cells.
One example is when the user selects several discontiguous cells, rows, and/or columns. Then, for example, ActiveSheet.Selection will be a single Range reflecting these discontiguous cells. The same can happen with the return value from SpecialCells.
So, the Excel object model says that in general, a Range can be composed of Areas, where each Area itself is also represented by a Range, but this time, it is understood to be a contiguous Range. The only way you can tell if the Range is contiguous or not is if you created it as a box/rectangle, or, if Areas.Count = 1.
One way to investigate a bit more might be to select some discontiguous cells, then enter a macro and use the debugger to observe Selection.

How do I add “+1” to all cells in a user selected range?

I need to have the user select a range of cells with their mouse and then run a macro to add +1 to that selected range. The range will often be different every time, so it cannot be defined.
Here's what I have so far, it just works on a single active cell, I cannot get it to work on a range selection, because I do not know what to define or function to utilize.
My code Follows:
Sub Add()
ActiveCell.Value = ActiveCell.Value + 1
End Sub
The below code uses the built-in Paste Special Add feature to add 1 to every cell in the selected range.
Sub AddOneToSelection()
Dim rBlank As Range
If TypeName(Selection) = "Range" Then
'Put a '1' in an unused cell and copy it
Set rBlank = ActiveSheet.Cells.SpecialCells(xlLastCell).Offset(1, 0)
rBlank.Value = 1
rBlank.Copy
'Paste Special Add over the selection
Selection.PasteSpecial Paste:=xlPasteValues, _
Operation:=xlAdd
'Get rid of the copied cell
rBlank.ClearContents
End If
End Sub
The benefit of this over looping is that Paste Special Add treats formulas and values differently and you don't have to code that part yourself.
The downside is you increase your UsedRange by one row, if that matters to you.
This should work for you:
Dim r, c As Range
Set r = Selection
For Each c In r
c.Value = "+1"
Next
This assumes that your cell formats will display "+1" instead of "1". You can set the format of your cells to "Text".

VBA code in Excel to add a row to multiple sheets and then copy formula from adjacent row

I'm really hoping someone can help me with this one. I have recorded a macro to use within a sheet that needs to create a row at the same position on 2 worksheets and then, on one of them, copy the formula's in the cells from the row below it. The code I have looks like this -
Sub Macro1()
Sheets(Array("SCHEDULE", "ANNUAL SUMMARY")).Select
Sheets("SCHEDULE").Activate
ActiveCell.Offset(1, 0).Rows("1:1").EntireRow.Select
Selection.Insert Shift:=xlDown, CopyOrigin:=xlFormatFromLeftOrAbove
Sheets("ANNUAL SUMMARY").Select
ActiveCell.Offset(1, 0).Rows("1:1").EntireRow.Select
Selection.AutoFill Destination:=ActiveCell.Offset(-1, 0).Rows("1:2").EntireRow _
, Type:=xlFillDefault
ActiveCell.Offset(-1, 0).Rows("1:2").EntireRow.Select
Sheets("SCHEDULE").Select
ActiveCell.Select
My problem is, when I run it manually and then record the macro, it does exactly what I want it to, but when I run this from a button on the "SCHEDULE" sheet it does not copy the formula's from the row below the one on the "ANNUAL SUMMARY" sheet.
Can anyone help to get this working with me?
Thanks all in advance
Mark
The problem with the macro recorder is that although it can give you a good indication of what code you need, it also generates very inefficient code and includes all of the select and activate statements that you need to try and avoid using.
Any reference in the code to ActiveCell is referring to the cell that is currently selected and ActiveSheet is the sheet that is currently selected. This can give you undesired results if you run the macro from a different sheet that the macro was recorded from...
If you wanted to copy row 1 from SCHEDULE sheet then you can use
Sheets("SCHEDULE").Rows(1).Copy Sheets("ANNUAL SUMMARY").Rows(1)
If you want to auto fill a range, then this can be accomplished with a single line of code
This will auto fill the contents of row1 (column A - E) down to row 100 in your ANNUAL SUMMARY sheet
Sheets("ANNUAL SUMMARY").Range("A1:E100").FillDown
So if we put it all together and include some declarations for our source and destination sheet to make the sub more readable..
Sub CopyAndFillDownExample()
Dim rowNumber As Long, offset As Long
Dim sourceSht As Worksheet, destinationSht As Worksheet
'set the source and destinationsheets
Set sourceSht = Sheets("SCHEDULE")
Set destinationSht = Sheets("ANNUAL SUMMARY")
'number of rows to copy down
offset = 100
'get currently selected row
rowNumber = ActiveCell.Row
'copy the selected row from the source sheet to the destination sheet
sourceSht.Rows(rowNumber).Copy destinationSht.Rows(rowNumber)
'fill down the formulas
destinationSht.Rows(rowNumber & ":" & rowNumber + offset).FillDown
End Sub

Looking to select an undetermined number of rows in excel as part of larger VBA macro

I'm working with an excel book containing a large number of sheets; the first sheet is linked to an external program and pulls in data via an external function, and the number of lines imported varies significantly.
This block data is the disseminated over a number of subsequent sheets. The first step has been to populate column A (row name) with the number of rows in sheet 1. From here the data is split over a number of columns (currently B->L). The top row uses an IF() function to populate the first row, and I'm looking to write a clean macro to copy this formula to row x (which varies with each data import refresh) and then paste values for a manageable file size.
Here's what I've got so far; it works, but it's fairly (read: VERY!) clumsy:
Sub Refresh_Data()
Sheets("Sheet2").Select
ActiveWindow.ScrollWorkbookTabs Sheets:=13
Sheets(Array("Sheet2" ... "Sheet25")).Select
Sheets("Sheet2").Activate
Sheets("Sheet25").Select Replace:=False
Range("B1:L1").Select
Selection.Copy
Range("__B2:B1000__").Select
ActiveSheet.Paste
Application.Calculate
ActiveWindow.ScrollWorkbookTabs Position:=xlFirst
Sheets(Array("Sheet2" ... "Sheet25")).Select
Sheets("Sheet2").Activate
Sheets("Sheet25").Select Replace:=False
Sheets("Sheet2").Select
Range("B3").Select
Sheets(Array("Sheet2" ... "Sheet25")).Select
Sheets("Sheet2").Activate
Sheets("Sheet25").Select Replace:=False
Range("B3:L4").Select
Range("__B2:L1000__").Select
Application.CutCopyMode = False
Selection.Copy
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
Sheets("Check_sheet").Select
MsgBox "Update complete"
End Sub`
The main thing I'm looking to achieve is to replace the code B2:L1000 with something that can assess the number of rows in column A and select a range in rows B to L accordingly.
Since column L is the last populated column, I don't see why this can't also be done horizontally rather than defining "B:L" incase future columns need to be added.
Although the earlier answer has merits:
1) I would not use COUNTA because if there are empty cells in the row or column, the cells at the bottom or the right will be ignored.
2) I would never rely on the user picking the correct sheet to be used before running a macro; particularly one with so many sheets.
My reaction to the question is that you have set Macro Record, wandered around your workbook and then stopped the record. You select one thing, then another. You scroll through the sheets. To me most of the statements are not clumsy they are pointless.
The following does include an answer to your question about finding the last row of column A but it is more a tutorial about finding the dimensions of a range, getting data out of the range and then putting it somewhere else. This seems to be most of what you are trying to do with the most minimal understanding of VBA. I am sorry if this criticism is unfair but that is the impression your question gives to me.
Sub Test()
Dim RowS01Max As Integer
Dim Sheet1Data() As Variant
' With Sheets("Sheet1") allows you to access data within worksheet Sheet1
' without selecting it.
' Range("A1:C11") refers to a range within the active sheet
' .Range("A1:C11") refers to a range within the sheet identified in the
' With statement.
' ^ Note the dot
With Sheets("Sheet1")
' Rows.Count is the number of rows for the version of Excel you are using.
' .Cells(Rows.Count, "A") address the bottom row of column A of worksheet
' Sheet1.
' .Cells(Rows.Count, 1) refer to column A by number.
' End(xlUp) is the VBA equivalent of Ctrl+Up.
' If you positioned the cursor at the bottom of column A and pressed
' Ctrl+Up, the cursor would jump to the last row in column A with a value.
' The following statement gets that row number without actually moving
' the cursor.
RowS01Max = .Cells(Rows.Count, "A").End(xlUp)
' The following statement loads the contents of range A1:C11 of
' Sheets("Sheet1") into array Sheet1Data.
Sheet1Data = .Range("A1:C11").Value
' This is the same statement but the range is specified in a different way.
' .Cells(Row,Column) identifies a single cell within the sheet specified in
' the With statement. .Cells(1,1) identifies row 1, column 1 which is A1.
'. Cells(11, "C") identifies row 11, column C which is C11.
Sheet1Data = .Range(.Cells(1, 1), .Cells(11, "C")).Value
' This statement uses RowS01Max to specify the last row
Sheet1Data = .Range(.Cells(1, 1), .Cells(RowS01Max, 1)).Value
' In all three examples above, the contents of the specified range will
' be loaded to array Sheet1Data. Whichever range you pick, Sheet1Data
' will always be a two dimensional array with the first dimension being
' the row and the second dimension being the column.
' In the first two examples Sheet1Data(5,3) contains the contents
' of cell C5. In the third example, I have only loaded column A but the
' array will still has two dimensions but the only permitted value for the
' second dimension is 1.
' The following statement writes the contents of Sheet1Data to column "E"
.Range(.Cells(1, 5), .Cells(RowS01Max, 5)).Value = Sheet1Data
End With
With Sheets("Sheet2")
' The following statement writes the contents of Sheet1Data to column "E"
' of worksheet Sheet2.
.Range(.Cells(1, 5), .Cells(RowS01Max, 5)).Value = Sheet1Data
End With
End Sub
Don't despair! Most of us started with the macro recorder and still use it to discover the syntax for an unfamiliar command. Look through other questions. Some ask about exotic functionality but many are about moving data around in, to the experienced programmer, simple ways. Set up some workbooks with the questioner's problem. Copy and paste the solution into a module. Step through it using F8 (see the debugger), switch between Excel and Editor, watch what is happening to the worksheet and move the cursor over a variable to see its current value. Spend half a day playing. You will be amazed at how quickly it starts to make sense. Good luck and good programming.
The following should do the trick:
Sub Refresh_Data()
Dim lastRow As Integer
Dim lastCol As Integer
Dim entireRange As Range
Dim targetRange As Range
lastRow = Excel.Evaluate("COUNTA(A:A)") ''// count the rows in column A
lastCol = Excel.Evaluate("COUNTA(1:1)") ''// count the columns in row 1
Set entireRange = Range(Cells(1, 2), Cells(lastRow, lastCol))
Set targetRange = Range(Cells(2, 2), Cells(lastRow, lastCol))
entireRange.FillDown
Application.Calculate
targetRange.Copy
targetRange.PasteSpecial Paste:=xlPasteValues
End Sub
Notes:
Excel.Evaluate(...) allows you to use the result of worksheet functions in your VBA macros.
COUNTA(range) is a worksheet function that counts the number of non-blank cells in a given range. In this case, it can be used to determine the total number of rows in your data set, as well as the number of columns in row 1 that have a formula in them.