Macro that Hides Rows Doesn't Update When Named Range Changes Sizes - excel

I compiled some code that hides rows based on some certain criteria. At the beginning of the code, I unhide all the rows so that when the sheet recalculates the correct rows hide. Here is the code:
Private Sub Worksheet_Calculate()
Dim ws As Worksheet
Dim primaryarray As Range
Dim rw As Range
Set ws = ThisWorkbook.Sheets("Experience Rating Sheet")
Set primaryarray = ws.Range("B9:M322")
Application.ScreenUpdating = False
' unhide all rows before we begin
primaryarray.EntireRow.Hidden = False
For Each rw In primaryarray.Rows
rw.EntireRow.Hidden = BlankOrZero(rw.Cells(4)) And BlankOrZero(rw.Cells(8))
Next rw
Application.ScreenUpdating = True
End Sub
Function BlankOrZero(c As Range)
BlankOrZero = Len(c.Value) = 0 Or c.Value = 0
End Function
The range that it is checking is a named range that shows
=IF(Expected_Actual_Losses="","",Expected_Actual_Losses)
in order to get rid of random zeros.
I have to manually press the the calculate worksheet each time to fix it because the calculation is on manual.
I have tried putting Worksheet.Calculate in the Module to compensate for this but it has not worked.
By compensate, I mean automate this calculation in order to change how many rows are hidden.
Note: The source for the named range is also 2 different sections that have dynamic formulas that change sizes based on criteria I set. Using index/match for one and filter/sortby formulas for the other.

PotterFan,
I beg to differ, the following code has no problem hiding/unhiding rows on a non-active sheet.
Option Explicit
Sub HideFilter(lFilter As Long)
Dim wksHide As Worksheet
Dim lCntr As Long
Dim lFirstRow As Long
Dim lLastRow As Long
Sheets("Sheet1").Select 'Just to make sure not running from Sheet2!
Set wksHide = Sheets("Sheet2")
lFirstRow = Range("HideRange").Row 'Return 1st row
lLastRow = (Range("HideRange").Count - 1) + lFirstRow
wksHide.Range("HideRange").EntireRow.Hidden = False
For lCntr = lLastRow To lFirstRow Step -1
If wksHide.Cells(lCntr, 1).Value = lFilter Then wksHide.Cells(lCntr, 1).EntireRow.Hidden = True
Next lCntr
End Sub
Before:
After:

Turns out when I tried to calculate the worksheet on my other macro it was in-between an Application.EnableEvents command so the Worksheet_Calculate wouldn't trigger when it was supposed to. Here is the other Macro:
Sub UpdateReport()
Application.ScreenUpdating = False
Application.EnableEvents = False
'Puts in the effective date and member name
Dim ws As Worksheet
Dim Report As Variant
Dim xprating As Variant
Set Report = ThisWorkbook.Sheets(Array("Yearly Breakdown", "Loss Template", "Codes", "Yearly Breakdown", "Cover Sheet", "Ag Loss Sensitivity", "Experience Rating Sheet", "Loss Ratio Analysis", "Mod Analysis&Strategy Proposal", "Mod Snapshot", "Mod & Potential Savings"))
Set xprating = ThisWorkbook.Sheets("Experience Rating Sheet")
For Each ws In Report
ws.Calculate
Next ws
For Each ws In Report
ws.Calculate
ws.PageSetup.RightFooter = Sheet17.Range("B3").Text & Chr(10) & "Mod Effective Date: " & Sheet17.Range("B4")
Next ws
Application.EnableEvents = True
xprating.Calculate
Application.ScreenUpdating = True
I just stuck xprating.Calculate after Application.EnableEvents = True and it started updating the rows automatically.

Related

Copy Row from every sheet with cell containing word

I am building out a workbook where every sheet is for a different stage of a software installation. I am trying to aggregate the steps that fail by copying my fail rows into a summary sheet. I finally got them to pull, but they are pulling into the new sheet on the same row # as they are located in the original sheet.
Here is what I am using now:
Option Explicit
Sub Test()
Dim Cell As Range
With Sheets(7)
' loop column H untill last cell with value (not entire column)
For Each Cell In .Range("D1:D" & .Cells(.Rows.Count, "D").End(xlUp).Row)
If Cell.Value = "Fail" Then
' Copy>>Paste in 1-line (no need to use Select)
.Rows(Cell.Row).Copy Destination:=Sheets(2).Cells(Rows.Count, "A").End(xlUp).Offset(1, 0)
End If
Next Cell
End With
End Sub
I need to:
Pull row that has cell containing "Fail"
Copy row into master starting at Row 4 and consecutively down without overwriting
Run across all sheets at once-
*(they are named per step of install - do i need to rename to "sheet1, sheet2, etc"????)
When macro is run clear previous results (to avoid duplicity)
Another user offered me an autofilter macro but it is failing on a 1004 at this line ".AutoFilter 4, "Fail""
Sub Filterfail()
Dim ws As Worksheet, sh As Worksheet
Set sh = Sheets("Master")
Application.ScreenUpdating = False
'sh.UsedRange.Offset(1).Clear 'If required, this line will clear the Master sheet with each transfer of data.
For Each ws In Worksheets
If ws.Name <> "Master" Then
With ws.[A1].CurrentRegion
.AutoFilter 4, "Fail"
.Offset(1).EntireRow.Copy sh.Range("A" & Rows.Count).End(3)(2)
.AutoFilter
End With
End If
Next ws
Application.ScreenUpdating = True
End Sub
Try this:
The text “Completed” in this xRStr = "Completed" script indicates the specific condition that you want to copy rows based on;
C:C in this Set xRg = xWs.Range("C:C") script indicates the specific column where the condition locates.
Public Sub CopyRows()
Dim xWs As Worksheet
Dim xCWs As Worksheet
Dim xRg As Range
Dim xStrName As String
Dim xRStr As String
Dim xRRg As Range
Dim xC As Integer
On Error Resume Next
Application.DisplayAlerts = False
xStr = "New Sheet"
xRStr = "Completed"
Set xCWs = ActiveWorkbook.Worksheets.Item(xStr)
If Not xCWs Is Nothing Then
xCWs.Delete
End If
Set xCWs = ActiveWorkbook.Worksheets.Add
xCWs.Name = xStr
xC = 1
For Each xWs In ActiveWorkbook.Worksheets
If xWs.Name <> xStr Then
Set xRg = xWs.Range("C:C")
Set xRg = Intersect(xRg, xWs.UsedRange)
For Each xRRg In xRg
If xRRg.Value = xRStr Then
xRRg.EntireRow.Copy
xCWs.Cells(xC, 1).PasteSpecial xlPasteValuesAndNumberFormats
xC = xC + 1
End If
Next xRRg
End If
Next xWs
Application.DisplayAlerts = True
End Sub
Here's another way - You'll have to assign your own Sheets - I used 1 & 2 not 2 & 7
Sub Test()
Dim xRow As Range, xCel As Range, dPtr As Long
Dim sSht As Worksheet, dSht As Worksheet
' Assign Source & Destination Sheets - Change to suit yourself
Set sSht = Sheets(2)
Set dSht = Sheets(1)
' Done
dPtr = Sheets(1).Rows.Count
dPtr = Sheets(1).Range("D" & dPtr).End(xlUp).Row
For Each xRow In sSht.UsedRange.Rows
Set xCel = xRow.Cells(1, 1) ' xCel is First Column in Used Range (May not be D)
Set xCel = xCel.Offset(0, 4 - xCel.Column) ' Ensures xCel is in Column D
If xCel.Value = "Fail" Then
dPtr = dPtr + 1
sSht.Rows(xCel.Row).Copy Destination:=dSht.Rows(dPtr)
End If
Next xRow
End Sub
I think one of the problems in your own code relates to this line
.Rows(Cell.Row).Copy Destination:=Sheets(2).Cells(Rows.Count, "A").End(xlUp).Offset(1, 0)
The section Rows.Count, "A" should be referring to the destination sheet(2) but isn't because of the line
With Sheets(7)
further up

How to use VBA to change if an Excel file is visible (minimized) based on changes to the grand totals in a pivot table?

I have a live report in Excel that shows if cranes are currently delayed. What I would like to do, by using VBA, is to make it so that when a crane either starts a delay, or ends one, the file will go from being minimized to being expanded so that a user can be signalled for when a crane has gone on delay. I would also like this to only occur for the cranes that are present within the table after being filtered.
I have attached an image of the data along with the relevant filter for cranes.
Crane Delay Data with Crane Filter
My thinking was to scan the Grand Total column, and if the value has changed from being empty to having a figure greater than 0 (and vice versa) to trigger a Macro that will make the file visible.
If possible, what code would I need to use and how would I go about doing this?
Many thanks.
For those interested, I have come up with a solution. Please leave a comment if you want me to talk through it. Code is below:
Option Explicit
Private Sub Worksheet_PivotTableAfterValueChange(ByVal TargetPivotTable As PivotTable, ByVal TargetRange As Range)
End Sub
Public Sub Worksheet_PivotTableUpdate(ByVal Target As PivotTable)
Dim pt As PivotTable
Dim wsBackup As Worksheet
Dim c As Range
Dim rngPivot As Range
Dim lastCol As Long
Dim strCrane As String
Dim sValues As Variant
sValues = ArrayListOfSelectedAndVisibleSlicerItems("Slicer_QC1")
'Change to suit
Set pt = Me.PivotTables("PivotTable1")
'Where has a copy of table been saved?
Set wsBackup = ThisWorkbook.Worksheets("Pivot Copy")
Set rngPivot = pt.DataBodyRange
'How many columns?
lastCol = rngPivot.Columns.Count
Application.ScreenUpdating = False
'Check each cell in last column/grand total
For Each c In rngPivot.Columns(lastCol).Cells
'What item is this?
strCrane = c.Offset(0, -lastCol).Value
'Escape clause
If strCrane = "Grand Total" Then Exit For
If c.Value = 0 Then
'Use a function that won't throw an error
'Note we add 1 to account for row labels
If WorksheetFunction.SumIfs(wsBackup.Columns(1 + lastCol), _
wsBackup.Range("A:A"), strCrane) <> 0 Then
If IsInArray(strCrane, sValues) = True Then
ActiveWindow.WindowState = xlMaximized
MsgBox strCrane & " has ended a delay" & vbCrLf & vbCrLf & "(Minimise Excel after using file)"
End If
End If
ElseIf c.Value > 0 Then
If WorksheetFunction.SumIfs(wsBackup.Columns(1 + lastCol), _
wsBackup.Range("A:A"), strCrane) = 0 Then
If IsInArray(strCrane, sValues) = True Then
ActiveWindow.WindowState = xlMaximized
MsgBox strCrane & " has started a delay" & vbCrLf & vbCrLf & "(Minimise Excel after using file)"
End If
End If
End If
Next c
'Save our new backup
wsBackup.Cells.Clear
pt.TableRange2.Copy
wsBackup.Range("A1").PasteSpecial xlPasteValues
'Clean up
Application.CutCopyMode = False
Application.ScreenUpdating = True
End Sub
Function ArrayListOfSelectedAndVisibleSlicerItems(MySlicerName As String) As Variant
'This function returns an array of the limited set of items in Slicer A
'Limitation is due to both:
'(1) direct selection of items by user in slicer A
'(2) selection of items in slicer B which in consequence limits the number of items in slicer A
Dim ShortList() As Variant
Dim i As Integer: i = 0 'for iterate
Dim sC As SlicerCache
Dim sI As SlicerItem 'for iterate
Set sC = ThisWorkbook.SlicerCaches(MySlicerName)
For Each sI In sC.SlicerItems
If sI.Selected = True Then
' Debug.Print sI.Name
ReDim Preserve ShortList(i)
ShortList(i) = sI.Value
i = i + 1
End If
Next sI
ArrayListOfSelectedAndVisibleSlicerItems = ShortList
End Function
Private Function IsInArray(stringToBeFound As String, arr As Variant) As Boolean
IsInArray = UBound(Filter(arr, stringToBeFound)) > -1
End Function

Hide/unhide rows in an array based on if an entire is blank or zero in the array

I think I found what I need but I do not know what I am missing. I'm getting Runtime code 91 and cant find the object that needs to be defined.
Sub Hide_UnhideBlanks()
Dim ws As Worksheet
Dim primaryarray As Range
Dim crit1 As Range
Dim cell As Range
Set ws = ThisWorkbook.Sheets("Experience Rating Sheet")
Set primaryarray = ws.Range("B10:M137")
Set crit1 = ws.Range("B10:B137,M10:M137")
Application.ScreenUpdating = False
' unhide all rows before we begin
primaryarray.EntireRow.Hidden = False
For Each cell In primaryarray
If crit1 Is Nothing Or 0 Then cell.EntireRow.Hidden = True
Next cell
Application.ScreenUpdating = True
End Sub
The purpose of this is if crit1 has either 0 or "" to hide all of the rows within primaryarray that have met the criteria described for crit1
Essentially my goal is for the macro to automatically hide the entire row if the array's row is completely empty using crit1 as determining if the row is empty.
Not sure I'm clear what you want but this might be a starting point:
Sub Hide_UnhideBlanks()
Dim ws As Worksheet
Dim primaryarray As Range
Dim rw As Range
Set ws = ThisWorkbook.Sheets("Experience Rating Sheet")
Set primaryarray = ws.Range("B10:M137")
Application.ScreenUpdating = False
' unhide all rows before we begin
primaryarray.EntireRow.Hidden = False
For Each rw In primaryarray.Rows
'not sure if you want Or/And here?
rw.EntireRow.Hidden = ( BlankOrZero(rw.Cells(1)) Or _
BlankOrZero(rw.Cells(12)) )
Next rw
Application.ScreenUpdating = True
End Sub
'is cell empty or zero?
Function BlankOrZero(c As Range)
BlankOrZero = len(c.value)=0 or c.value=0
End function

How can I speed up this For Each loop in VBA?

I have an Worksheet_Change macro that hides/unhides rows depending on the choice a user makes in a cell with a data validation list.
The code takes a minute to run. It's looping over c.2000 rows. I'd like it to take closer to a few seconds so it becomes a useful user tool.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
'Exit the routine early if there is an error
On Error GoTo EExit
'Manage Events
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Application.EnableEvents = False
'Declare Variables
Dim rng_DropDown As Range
Dim rng_HideFormula As Range
Dim rng_Item As Range
'The reference the row hide macro will look for to know to hide the row
Const str_HideRef As String = "Hide"
'Define Variables
'The range that contains the week selector drop down
Set rng_DropDown = Range("rng_WeekSelector")
'The column that contains the formula which indicates if a row should
'be hidden c.2000 rows
Set rng_HideFormula = Range("rng_HideFormula")
'Working Code
'Exit sub early if the Month Selector was not changed
If Not Target.Address = rng_DropDown.Address Then GoTo EExit
'Otherwise unprotect the worksheet
wks_DailyPlanning.Unprotect (str_Password)
'For each cell in the hide formula column
For Each rng_Item In rng_HideFormula
With rng_Item
'If the cell says "hide"
If .Value2 = str_HideRef Then
'Hide the row
.EntireRow.Hidden = True
Else
'Otherwise show the row
.EntireRow.Hidden = False
End If
End With
'Cycle through each cell
Next rng_Item
EExit:
'Reprotect the sheet if the sheet is unprotected
If wks_DailyPlanning.ProtectContents = False Then wks_DailyPlanning.Protect (str_Password)
'Clear Events
Application.ScreenUpdating = True
Application.DisplayAlerts = True
Application.EnableEvents = True
End Sub
I have looked at some links provided by other users on this website and I think the trouble lies in the fact I'm having to iterate through each row individually.
Is it possible to create something like an array of .visible settings I can apply to the entire range at once?
I'd suggest copying your data range to a memory-based array and checking that, then using that data to adjust the visibility of each row. It minimizes the number of interactions you have with the worksheet Range object, which takes up lots of time and is a big performance hit for large ranges.
Sub HideHiddenRows()
Dim dataRange As Range
Dim data As Variant
Set dataRange = Sheet1.Range("A13:A2019")
data = dataRange.Value
Dim rowOffset As Long
rowOffset = IIf(LBound(data, 1) = 0, 1, 0)
ApplicationPerformance Flag:=False
Dim i As Long
For i = LBound(data, 1) To UBound(data, 1)
If data(i, 1) = "Hide" Then
dataRange.Rows(i + rowOffset).EntireRow.Hidden = True
Else
dataRange.Rows(i + rowOffset).EntireRow.Hidden = False
End If
Next i
ApplicationPerformance Flag:=True
End Sub
Public Sub ApplicationPerformance(ByVal Flag As Boolean)
Application.ScreenUpdating = Flag
Application.DisplayAlerts = Flag
Application.EnableEvents = Flag
End Sub
Another possibility:
Dim mergedRng As Range
'.......
rng_HideFormula.EntireRow.Hidden = False
For Each rng_Item In rng_HideFormula
If rng_Item.Value2 = str_HideRef Then
If Not mergedRng Is Nothing Then
Set mergedRng = Application.Union(mergedRng, rng_Item)
Else
Set mergedRng = rng_Item
End If
End If
Next rng_Item
If Not mergedRng Is Nothing Then mergedRng.EntireRow.Hidden = True
Set mergedRng = Nothing
'........
to increase perfomance you can populate dictionary with range addresses, and hide or unhide at once, instead of hide/unhide each particular row (but this is just in theory, you should test it by yourself), just an example:
Sub HideHiddenRows()
Dim cl As Range, x As Long
Dim dic As Object: Set dic = CreateObject("Scripting.Dictionary")
x = Cells(Rows.Count, "A").End(xlUp).Row
For Each cl In Range("A1", Cells(x, "A"))
If cl.Value = 0 Then dic.Add cl.Address(0, 0), Nothing
Next cl
Range(Join(dic.keys, ",")).EntireRow.Hidden = False
End Sub
demo:

Defining a range from values in another range

I have an excel file of tasks which have either been completed or not, indicated by a Yes or No in a column. Ultimately I am interested in data in a different column but I want to set up the code so it ignores those rows where the task has been completed. So far I have defined the column range containing the yes/no's but I don't know which command to run on this range. I imagine I want to define a new range based on the value in column C.
Option Explicit
Sub Notify()
Dim Chk As Range
Dim ChkLRow As Long
Dim WS1 As Worksheet
On Error GoTo WhatWentWrong
Application.ScreenUpdating = False
'--> If the text in column C is Yes then Ignore (CountIF ?)
'--> Find last cell in the column, set column C range as "Chk"
Set WS1 = Sheets("2011")
With WS1
ChkLRow = .Range("C" & Rows.Count).End(xlUp).Row
Set Chk = .Range("C1:C" & ChkLRow)
End With
'--> Else Check date in column H
'--> Count days from that date until today
'--> Display list in Message Box
Reenter:
Application.ScreenUpdating = True
Application.DisplayAlerts = True
Exit Sub
WhatWentWrong:
MsgBox Err.Description
Resume Reenter
Application.ScreenUpdating = True
End Sub
Would it perhaps be easier to simply define one range based on the values in column C rather than first defining column C as the range and then redefining it?
Thanks
Yes Column H has the date the task 'arrived' and I want to display a count from then to the current date. The tasks are identified by a 4 digit code in Column A. I envisage the message box saying Task '1234' outstanding for xx days. – Alistair Weir 1 min ago
Is this what you are trying? Added Col I for visualization purpose. It holds no significance otherwise.
Option Explicit
Sub Notify()
Dim WS1 As Worksheet
Dim Chk As Range, FltrdRange As Range, aCell As Range
Dim ChkLRow As Long
Dim msg As String
On Error GoTo WhatWentWrong
Application.ScreenUpdating = False
Set WS1 = Sheets("2011")
With WS1
ChkLRow = .Range("C" & Rows.Count).End(xlUp).Row
'~~> Set your relevant range here
Set Chk = .Range("A1:H" & ChkLRow)
'~~> Remove any filters
ActiveSheet.AutoFilterMode = False
With Chk
'~~> Filter,
.AutoFilter Field:=3, Criteria1:="NO"
'~~> Offset(to exclude headers)
Set FltrdRange = .Offset(1, 0).SpecialCells(xlCellTypeVisible)
'~~> Remove any filters
ActiveSheet.AutoFilterMode = False
For Each aCell In FltrdRange
If aCell.Column = 8 And _
Len(Trim(.Range("A" & aCell.Row).Value)) <> 0 And _
Len(Trim(aCell.Value)) <> 0 Then
msg = msg & vbNewLine & _
"Task " & .Range("A" & aCell.Row).Value & _
" outstanding for " & _
DateDiff("d", aCell.Value, Date) & "days."
End If
Next
End With
End With
'~~> Show message
MsgBox msg
Reenter:
Application.ScreenUpdating = True
Exit Sub
WhatWentWrong:
MsgBox Err.Description
Resume Reenter
End Sub
SNAPSHOT
Why not brute force it.
Dim r_table as Range, i as Integer, N as Integer
' Start from the top
Set r_table = Sheets("2011").Range("C1")
' Find the last entry on column C and count the # of cells
N = Sheets("2011").Range(r_table, r_table.End(xlDown)).Rows.Count
Dim table_values() as Variant
' This will transfer all the values from the spreadsheet into an VBA array
' and it works super fast. Access values with A(row,col) notation.
table_values = r_table.Resize(N, 5).Value2 ' No. of columns is 5 ?
For i=1 to N
If table_values(i,1)="Yes" Then 'Check Column C
Else
... table_values(i,5) ' Column H
End if
Next i
MsgBox ....
This will be super fast, with no flicker on the screen.

Resources