Hiding multiple rows based on cell values - vba

I have an algorithm that works fine to hide all rows where, in a specified named range, a given row has the value 0. It's straightforward enough:
Public Sub MasquerLignesAZeroRapport(rap As Worksheet)
Dim cell As Range
rap.Rows.Hidden = False
For Each cell In rap.Range("Ra_LignesAZero")
If Round(cell.Value, 0) = 0 Then
cell.EntireRow.Hidden = True
End If
Next cell
End Sub
This, however, takes a bit of time even when calculation and screen updating are turned off and I have tried different other methods without success (using a filter and hiding all visible rows but removing the filter unhides the rows, the same goes for setting the row height to 0).
Is there a faster alternative ? I can live with that slow algorithm but it would be a welcome improvement as this macro may be run against 1-6 reports in a single run.

Here are a few optimizations:
Public Sub MasquerLignesAZeroRapport(rap As Worksheet)
'Optimization #1: as you pointed out, turning off calculations
' and screen updating makes a difference.
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
rap.Rows.Hidden = False
'Optimization #2: instead of loading each cell as a range,
' with all the associated properties, load JUST the values
' into a 2 dimensional array.
Dim values() As Variant
values = rap.Range("Ra_LignesAZero")
For r = 1 To UBound(values, 1)
For c = 1 To UBound(values, 2)
If Round(values(r,c), 0) = 0 Then
rap.Rows(r).Hidden = True
'Optimization #3: if we have already determined
' that the row should be hidden, no need to keep
' looking at cells in the row - might as well break out of the For:
Exit For
End If
Next
Next
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub

Related

Which Loop function would better suit to read the cells and get the total?

I have a table with a few cells in blank and the other ones filled in. The ones filled I read as value 12 which I'll later sum up for each row and add to the last column as Total.
At first, I thought on doing a function to read the cells filled in as value = 12 and add to a counter, then create a sub to put the total on the last column. This is the idea of function I had in mind.
Public Function TestValue(ByRef rRng as Range) As Long
Dim rCell as Range
Dim lCont as Long
For Each rCell in rRng.Cells
If isEmpty(rCell.Value) = False then
lCont = lCont + 12
Else
End If
Next rCell
TestValue = lCont
End Function
I came up with a Do While just so you can have an idea on what I'm trying to do.
Public Sub Test()
Do
ActiveSheet.Range("I2:I8").Value = TestValue
Loop While isEmpty(Range("I2:I8")) = False
End Sub
For Each is the less human-error prone loop. I find it's easy to accidentally write an infinite loop in the Do While format For example norie mention's in the comments that this example should get stuck in an infinite loop: IsEmpty(Range("I2:I8")) won't work here - it will always return False
Does it need to be VBA? You could do this with a simple formula.
=COUNTA(I2:I8)*12

How to automatically resize expanded rows?

So my problem is that I have a sheet in which a multitude of grouped rows exist. The rows are grouped in 2 levels. To put this in perspective, I have a group which covers the rows in A1:A55. Inside this first level group I have multiple second level groups covering smaller sections (e.g. rows in A2:A5, rows in A7:A10 and so on.). Because of Excel automatically adding groups together if they are adjacent to each other, I have added a blank row in between each 2nd level group of rows(A6, A11, etc.). I then proceeded to change the height of these blank rows to 0,00. This hid the + and - signs on the left hand bar for collapsing/expanding, which wasn't a problem as the collapsing and expanding is being handled via buttons on the sheet.
However, when all the grouped rows, or just the 2nd level grouped rows, are being expanded (either manually or via a macro), the row height of all the blank rows jumps back to a size at which Excel can display the + and - signs in the left hand bar again. This shows the blank rows which I want to prevent.
I know I probably can't prevent the resizing of the rows so it displays the + and - signs, however I was thinking about immediately resizing the blank rows to a height of 0.00. This is being built in the macro that is called via the buttons, but the concern is when a user expands the rows manually. There is no event for collapsing and or expanding for me to use in an event handler. Is there any way for me to have an automatic response on a manual expand action by the user?
I have provided a example of the code used below.
Sub Select1Year_Click()
Dim ws1 As Worksheet
Set ws1 = Worksheets("Overview")
Dim ws2 As Worksheet
Set ws2 = Worksheets("Selection Tab")
Dim ROffset As Integer
ROffset = ((ws2.Range("B33").Value - 1) * 4) 'User defined starting Year
'value
On Error Resume Next
With ws1
.Range("AJ2").Rows.ShowDetail = False '2018
.Range("AJ7").Rows.ShowDetail = False '2019
.Range("AJ12").Rows.ShowDetail = False '2020
.Range("AJ17").Rows.ShowDetail = False '2021
.Range("AJ22").Rows.ShowDetail = False '2022
.Range("AJ27").Rows.ShowDetail = False '2023
.Range("AJ32").Rows.ShowDetail = False '2024
.Range("AJ37").Rows.ShowDetail = False '2025
.Range("AJ42").Rows.ShowDetail = False '2026
.Range("AJ47").Rows.ShowDetail = False '2027
.Range("AJ52").Rows.ShowDetail = False '2028
End With
If ws2.Range("B31").Value = 1 Then 'User selected 1 year to be shown in
'expanded view
ws1.Range("AJ2").Offset(0, ROffset).Rows.ShowDetail = True
End If
End Sub
'------------------------------------------------------------------------
Sub Select10Year_Click()
Dim ws1 As Worksheet
Set ws1 = Worksheets("Overview")
Dim ws2 As Worksheet
Set ws2 = Worksheets("Selection Tab")
Dim i As Integer
Dim ROffset As Integer
ROffset = ((ws2.Range("B33").Value - 1) * 4) 'User defined starting Year
'value
If ws2.Range("B31").Value = 3 Then 'User selected all years to be expanded
On Error Resume Next
ws1.Shapes("Select10Year").ControlFormat.Value = True
With ws1
.Range("AJ2").Rows.ShowDetail = True '2018
.Range("AJ7").Rows.ShowDetail = True '2019
.Range("AJ12").Rows.ShowDetail = True '2020
.Range("AJ27").Rows.ShowDetail = True '2021
.Range("AJ22").Rows.ShowDetail = True '2022
.Range("AJ27").Rows.ShowDetail = True '2023
.Range("AJ32").Rows.ShowDetail = True '2024
.Range("AJ37").Rows.ShowDetail = True '2025
.Range("AJ42").Rows.ShowDetail = True '2026
.Range("AJ47").Rows.ShowDetail = True '2027
.Range("AJ52").Rows.ShowDetail = True '2028
End With
If ROffset > 0 Then 'User has selected a different starting year then
'2018, so collapse are years before selected
'starting year
For i = 0 To i = ROffset Step 1
ws1.Range("AJ2").Offset(0, ROffset).Rows.ShowDetail = False
Next i
End If
End If
End Sub
Any help would be greatly appreciated.
You can have your macro being launched as a result of a Worksheet_Change() event.

How to keep macro looping on a specified worksheet even when I switch worksheets in the same workbook

I tried to look for answers to this question but to no avail. I need this Macro to run on a specific worksheet called General on a specific workbook. The purpose, is to let the cell I24 be multiplied by 1.0003 every minute (which makes it a loop as far as I know). The below code only works when I have the General sheet opened. It stops looping when I switch to another worksheet.
Also, I want the macro to run automatically open opening the workbook, regardless of the General sheet being selected, so that I24 on the General sheet keeps getting multiplied without being redirected to the sheet. Just so you know, I have that cell referenced on various other sheets in the workbook, that is why I need the macro constantly running. Below is my code (It may not be at its optimum condition since I am very new to VBA):
Sub auto_open()
WshtNames = Array("General")
Application.ScreenUpdating = False
Dim num As Long
num = Sheets("General").Range("I24").Value
num = num * 1.0003
Range("I24").Value = num
Application.OnTime Now + TimeValue("00:01:00"), "auto_open"
Application.ScreenUpdating = True
End Sub
Thank you, I really appreciate your assistance.
Analyzing your code and making some suggestions to improve and remove unnecessary code
Switching Application.ScreenUpdating doesn't make much sense in this specific case, because there is only one update in Range("I24"). Therefore no gain if you turn it off.
There is only an advantage if you have many updates, so that they get performed all at once when switching Application.ScreenUpdating = True.
Use Option Explicit. This forces you to declare all your variables properly.
You set WshtNames but never use it, so this line can be removed.
Use Worksheets instead of Sheets unless you really need to use Sheets (Sheets also contains charts not only worksheets).
If num is Long then it can only contain integer/whole numbers. Therfore if you multiply num = num * 1.0003 it will automatically cast into Long which is the same result as num = num and that means it doesn't change anything. You will need to use at least Double or Decimal here.
You didn't specify a worksheet for the Range("I24").Value = num so Excel assumes that the range is in the active sheet. This is why your code fails when you select another sheet. Never let VBA guess the worksheet always specify the correct one Worksheets("General").Range("I24").Value = num.
So all together we can change your code from …
Sub auto_open()
WshtNames = Array("General") '(3) can be removed because WshtNames is never used
Application.ScreenUpdating = False '(1) dosn't make much sense
Dim num As Long '(5) wrong data type
num = Sheets("General").Range("I24").Value '(4) use worksheets
num = num * 1.0003 'see (5)
Range("I24").Value = num '(6) Always specify a worksheet
Application.OnTime Now + TimeValue("00:01:00"), "auto_open"
Application.ScreenUpdating = True
End Sub
Into this …
Option Explicit
Public Sub auto_open()
Dim num As Double
With Workheets("General") 'note we use a with statement to specify the sheet for the ranges (starting with a dot now!)
num = .Range("I24").Value
num = num * 1.0003
.Range("I24").Value = num
End With
Application.OnTime Now + TimeValue("00:01:00"), "auto_open"
End Sub
Or even shorter, because we don't need the num variable for that short calculation:
Option Explicit
Public Sub auto_open()
With Workheets("General") 'note we use a with statement to specify the sheet for the ranges (starting with a dot now!)
.Range("I24").Value = .Range("I24").Value * 1.0003
End With
Application.OnTime Now + TimeValue("00:01:00"), "auto_open"
End Sub
This part of the code has wrong logic:
Dim num As Long
num = Sheets("General").Range("I24").Value
num = num * 1.0003
Long is a whole number by specification. If you multiply it by 1.0003 it is the same as if it is multiplied by 1. Consider using Double instead.
Or Decimal, for better precision:
Dim num as Double
num = Sheets("General").Range("I24")
num = CDec(num * 1.0003)
You must set your cell as a variable.
Dim myCell as Range
Set myCell = ThisWorkbook.Worksheets("General").Range("I24")
and in the code:
myCell.Value = num
EDIT:
The whole code:
Sub auto_open()
WshtNames = Array("General")
Application.ScreenUpdating = False
Dim myCell As Range
Set myCell = ThisWorkbook.Worksheets("General").Range("I24")
myCell = myCell * 1.0003
Application.OnTime Now + TimeValue("00:01:00"), "auto_open"
Application.ScreenUpdating = True
End Sub

Can I use IsEmpty to refer to a different sheet and hide a column?

Is it possible to use IsEmpty to refer to a cell on a different sheet from where the macro is being fired from? Also, is it possible to hide the queried column if the result of that query is True?
Here's what I've built so far:
My first version looked like this:
If IsEmpty(L1) Then
Columns("L").EntireColumn.Hidden = True
Else
Columns("L").EntireColumn.Hidden = False
End If
Straightforward enough. But, that only works if it's fired from the worksheet where I want the query/hide to occur. When I launch the macro from the different sheet, it hides the column in that sheet (of course, duh).
So, after several iterations and errors, I got to this:
If IsEmpty(Sheets("Results").Cells(10, 1).Value) Then
Worksheets("Results").Columns(10).EntireColumn.Hidden = True
Else
Worksheets("Results").Columns(10).EntireColumn.Hidden = False
End If
Which at least doesn't throw any errors from the VBA. It also does a grand total of squat. :$ I'm starting to wonder if it's even possible to use IsEmpty on a different sheet? Or the EntireColumn.Hidden command? Also, given that I need to run this check on 9 columns, maybe there's a better way than 9 If/Then statements?
To get away from a loop through 9 columns' row 1, use SpecialCells(xlCellTypeBlanks).
dim blnks as range
with workSheets("Results")
with .range(.cells(1, "B"), .cells(1, "K"))
.entirecolumn.hidden = false
set blnks = .specialcells(xlCellTypeBlanks)
if not blnks is nothing then blnks.entirecolumn.hidden = true
end with
end with
Essentially this unhides all 9 columns then hides the columns with blank cells in the first row. Note that a zero-length string (e.g. "") returned by a formula is not the same thing as a truly blank cell.
I think you're very close, just you have the cells inputs the wrong way around:
If IsEmpty(Sheets("Results").Cells(1, 10).Value) Then
Worksheets("Results").Columns(10).EntireColumn.Hidden = True
Else
Worksheets("Results").Columns(10).EntireColumn.Hidden = False
End If
Additionally as mentioned in the comments you can create a loop to check many columns:
Dim i As Integer
Dim maxi As Integer
i = 1
maxi = 20
While i < maxi
If IsEmpty(ThisWorkbook.Worksheets("Results").Cells(1, i)) Then
Worksheets("Results").Columns(i).EntireColumn.Hidden = True
Else
Worksheets("Results").Columns(i).EntireColumn.Hidden = False
End If
i = i + 1
Wend

VBA code to hide a number of fixed discrete rows across a few worksheets

I'm for a solution to part of a macro I'm writing that will hide certain (fixed position) rows across a few different sheets. I currently have:
Sheets(Sheet1).Range("5:20").EntireRow.Hidden = True
To hide rows 5-20 in Sheet1. I also would like to hide (for arguements sake), row 6, row 21, and rows 35-38 in Sheet2 - I could do this by repeating the above line of code 3 more times; but am sure there's a better way of doing this, just as a learning exercise.
Any help much appreciated :)
Chris
Specify a Union of some ranges as follows
With Sheet1
Union(.Range("1:5"), .Rows(7), .Range("A10"), .Cells(12, 1)).EntireRow.Hidden = True
End With
Here is a try:
Sub hideMultiple()
Dim r As Range
Set r = Union(Range("A1"), Range("A3"))
r.EntireRow.Hidden = True
End Sub
But you cannot Union range from several worksheets, so you would have to loop over each worksheet argument.
This is a crude solution: no validation, no unhiding of existing hidden rows, no check that I have a sheet name as first parameter, etc. But it demonstrates a technique that I often find useful.
I load an array with a string of parameters relevant to my current problem and code a simple loop to implement them. Look up the sub and function declarations and read the section on ParamArrays for a variation on this approach.
Option Explicit
Sub HideColumns()
Dim InxPL As Integer
Dim ParamCrnt As String
Dim ParamList() As Variant
Dim SheetNameCrnt As String
ParamList = Array("Sheet1", 1, "5:6", "Sheet2", 9, "27:35")
SheetNameCrnt = ""
For InxPL = LBound(ParamList) To UBound(ParamList)
ParamCrnt = ParamList(InxPL)
If InStr(ParamCrnt, ":") <> 0 Then
' Row range
Sheets(SheetNameCrnt).Range(ParamCrnt).EntireRow.Hidden = True
ElseIf IsNumeric(ParamCrnt) Then
' Single Row
Sheets(SheetNameCrnt).Range(ParamCrnt & ":" & _
ParamCrnt).EntireRow.Hidden = True
Else
' Assume Sheet name
SheetNameCrnt = ParamCrnt
End If
Next
End Sub