Avoiding Select in Excel VBA still doesn't speed up my code - vba

This is the first time I couldn't find an answer to my problem on StackExchange. You guys are quite thorough. Anyway, we recently updated to Office 365/Excel 2016 from 2007, and now my VBA scripts won't run, except overnight. I researched and learned that I was a horrible person for using Select/Activate. I have seen the error of my ways, but now even simple code still doesn't want to run on a large sheet. My code, which clears the formatting from the current worksheet to make populating it with data faster:
Sub ClearFormattingAndValidation()
Dim referenceCell As Range
Dim rngToFormat As Range
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
.EnableEvents = False
End With
Set referenceCell = Cells.Find("Some specific text", after:=ActiveCell, LookAt:=xlWhole)
Set rngToFormat = Range(referenceCell.Offset(2, 0), ActiveCell.SpecialCells(xlLastCell))
' rngToFormat.Select
With rngToFormat
.Validation.Delete 'near-instantaneous
.FormatConditions.Delete 'took 7-15 minutes on timed runs
End With
With Application
.ScreenUpdating = True
.Calculation = xlCalculationAutomatic
.EnableEvents = True
End With
End Sub
When I uncomment rngToFormat.Select, I get a total of 76,302 cells, to give you an idea of the size of the spreadsheet. There is a LOT of validation and conditional formatting. In 2007, even using Select/Selection, it ran in seconds. Now, I have no idea how long it takes. I gave up after 5 minutes. It does run successfully on a smaller version of the worksheet.
I would like to avoid removing the validation and conditional formatting if at all possible, but it IS a (time-consuming and costly) possibility for about half of it if that is the only way to speed it up.
Is there anything else that I can do to make the code run faster, or is there something that I'm doing wrong?
Edit: Code changed to reflect comments/suggestions as of 2/21. Similar results.

Your code runs instantly with Excel 2013.
Though the code can be written as below...
Sub ClearFormattingAndValidation()
Dim referenceCell As Range
Dim rngToFormat As Range
With Application
.Calculation = xlCalculationManual
.EnableEvents = False
.ScreenUpdating = False
End With
Set referenceCell = Cells.Find("Some specific text", after:=ActiveCell, LookAt:=xlWhole)
If Not referenceCell Is Nothing Then
Set rngToFormat = Range(referenceCell.Offset(2, 0), ActiveCell.SpecialCells(xlLastCell))
With rngToFormat
.FormatConditions.Delete
.Validation.Delete
End With
End If
With Application
.Calculation = xlCalculationAutomatic
.EnableEvents = True
.ScreenUpdating = True
End With
End Sub

With ActiveSheet
.EnableFormatConditionsCalculation = False
End With
This disables the conditional formatting that is causing the slowdown. It should be reenabled after the code is run.
thanks again, #sktneer. Your help pushed me in the right direction.

Related

When I use a button to run a macro the excel can't complete it because of few memory

My macro set the values of a block of cells to 1 later it sets some of these cells to 0 based on the daily conditions (5232 cells total). I would like to put this macro behind a button, if I run it through the button I got the error message immediately.
Excel cannot complete this task with available resources.
Choose less data or close other applications.
Private Sub CommandButton1_Click()
Dim atado As String
Dim LastRow As Long
Dim i As Long
Dim j As Long
Dim elsoora As Long
Dim utolsoora As Long
Sheets("Maszk").Select
Range("C4", Range("HL4").End(xlDown)).Value = 1
(...)
End Sub
The code is trying to set values of 228 million cells (probably). This is quite a lot, see yourself. It is a good idea always to refer to the correct worksheet in VBA, otherwise you can get various errors.
Sub TesteMe()
With Worksheets("SomeName")
MsgBox .Range("C4", .Range("HL4").End(xlDown)).Cells.Count
End With
End Sub
However, you can upgrade it a bit, by turing the Application.ScreenUpdating off. Like this: Application.ScreenUpdating = False at the beginning of the code and Application.ScreenUpdating = True at the end of the code.
Are there any formulas pointing to that range? If yes, the re-calculation probably causes the memory issue. Set calculation to manual and stop screen updating.
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
'run your code here
With Worksheets("Maszk") 'fully qualify your range
.Range("C4", .Range("HL4").End(xlDown)).Value = 1
End With
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
Note that you always need to qualify your range to be in a specific worksheet, otherwise Excel might take the wrong worksheet. Therefor use a With statement and start your ranges with a dot. Or qualify each range like Worksheets("YourSheetName").Range(…)
There are several things you can "switch off" to speed up code processing - ScreenUpdating, EnableEvents, Calculation. I (re)use this particular routine:
Sub xlQuiet(Optional ByVal bQuiet As Boolean, Optional ByVal sStatusMessage As String)
On Error GoTo Terminate
With Application
.ScreenUpdating = Not bQuiet
.EnableEvents = Not bQuiet
.DisplayAlerts = Not bQuiet
.StatusBar = bQuiet
If bQuiet Then
.Calculation = xlCalculationManual
If Not sStatusMessage = "" Then .StatusBar = sStatusMessage
Else
.Calculate
.Calculation = xlCalculationAutomatic
DoEvents
End If
End With
Terminate:
If Err Then
Debug.Print "Error", Err.Number, Err.Description
Err.Clear
End if
End Sub
Then I call at the start / end of other routines, like this:
Sub foo()
xlQuiet True
With Sheets("Maszk")
.Range("C4", .Range("HL4").End(xlDown)).Value = 1
End With
xlQuiet False
End Sub
Edit: note the way that the range objects are qualified to the stated sheet - so the active / selected sheet becomes irrelevant.
You could write the 1s one row at a time:
Application.ScreenUpdating = False
For Each rw In Range("C4", Range("HL4").End(xlDown)).Rows
rw.Value = 1
Next
Application.ScreenUpdating = True

Sub produces 'flashing' effect with ScreenUpdating turned off

I'm using the following code (reduced to relevant parts for simplicity):
Sub deleteCategory()
Application.ScreenUpdating = False
Sheets("Recurring Expenses").Activate
'A bunch of stuff is checked and done on the now activated sheet
Sheets("Input").Activate 'This is the sheet the sub is called from via a button
Application.ScreenUpdating = True
End Sub
Although Application.ScreenUpdating is turned off, every time you click the button and the macro runs (otherwise bug-free) you can clearly see the Sheet "Recurring Expenses" briefly flash for a moment.
Any idea what causes this or how it could be fixed?
Try using With statement:
Sub deleteCategory()
With Excel.Application
.ScreenUpdating = False
.EnableEvents = False
End With
With Workbooks("your_workbook_name").Sheets("Recurring Expenses")
' do stuff like
' .copy
' .cells.select
End With
'A bunch of stuff is checked and done on the now activated sheet
With Workbooks("your_workbook_name").Sheets("Input") 'This is the sheet the sub is called from via a button
' Do stuff
End With
With Excel.Application
.ScreenUpdating = True
.EnableEvents = True
End With
End Sub
Hope this help you.

VBA Remove format from empty cells in Excel

I need a quick code to clean the format of all the cells that are empty.
I have written this code, but it is too slow. Is there a way to make it quicker?
Sub removeFormatEmpty()
'Declaration of variables
Dim sheet As Worksheet
Dim rcell As Range
For Each sheet In Worksheets
sheet.Activate
'Cells.UnMerge
For Each rcell In sheet.UsedRange.Cells
If rcell.MergeCells = True Then
rcell.UnMerge
End If
If rcell.Value = "" Then
rcell.ClearFormats
End If
Next rcell
Next sheet
End Sub
This code works, however it is slow as it needs to go cell by cell. Is there a way to select the whole range except the cells with content?
Update:
Thank you to the comments of bobajob and jordan I've been able to update the code and make it much more faster and optimized. It is the new code:
Sub removeFormatEmptyImproved()
Dim sheet As Worksheet
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
For Each sheet In Worksheets
'sheet.Activate
sheet.UsedRange.SpecialCells(xlCellTypeBlanks).ClearFormats
Next sheet
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
So now it is solved.
Firstly, you don't have to check whether a cell is merged before unmerging it. So to unmerge all cells in sheet...
sheet.UsedRange.UnMerge
You don't need to activate a sheet before altering it
As mentioned in the comments, you can alter all cells at once by using
sheet.UsedRange.SpecialCells(xlCellTypeBlanks).ClearFormats
Turning Calculation to manual and ScreenUpdating to false is an easy go-to method to speed most VBA code up!
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
' <other code>
' Include error handling so that these are always set back!
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
So your resulting Sub would be
Sub removeFormatEmpty()
Dim sheet As Worksheet
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
For Each sheet In ThisWorkbook.Worksheets
sheet.UsedRange.UnMerge
sheet.UsedRange.SpecialCells(xlCellTypeBlanks).ClearFormats
Next sheet
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
A final step to speed things up would be to dig into your UsedRange a little more. It can be notorious for remembering long-unused cells and being far bigger than necessary. If you know your sheet layout, there may be a way to restrict the range you are working with.
See some methods for doing this here:
Getting the actual usedrange

Changing value of a cell to trigger a macro

As the title suggests, I want a specific macro (PopulateCalculatorWithWeekChosen) to run whenever a user changes the value in the 'Summary' worksheet, cell H10.
I found the below question and answer and tried it but it doesn't seem to trigger it. Did I understand correctly that I put the code below...
Trigger event when select from dropdown
Sub PopulateCalculatorWithWeekChosen()
If Target.Address = "$H$10" Then
With Application
.EnableEvents = False
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
[the rest of the code in the PopulateCalculatorWithWeekChosen macro]
With Application
.EnableEvents = True
.ScreenUpdating = True
.Calculation = xlCalculationAutomatic
End With
End If
End Sub
...in the right place?
It doesn't seem to do anything. Thanks in advance for any help.
The problem is you've defined a macro and the question/answer you reference is using the Worksheet_Change event. In your VBA code, you should have Worksheet object which has a Change event. An event is just a function that is triggered when a specific thing happens.
In this case, the function Worksheet_Change triggers whenever the worksheet is modified. You should have something like this:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$H$10" Then
With Application
.EnableEvents = False
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
With Application
.EnableEvents = True
.ScreenUpdating = True
.Calculation = xlCalculationAutomatic
End With
End If
End Sub
When you modify the worksheet, Excel will automatically call this function and tell you which cells were modified by providing that information in the Target parameter.

Why is a small Excel VBA Macro is running extremely slow

I am writing a short macro to hide all customers that have no current sales for the current year. The YTD sales are in the K column (specifically K10-250). Those cells use a vlookup to pull data from another tab where we dump data. My question is why on earth would this macro take 10-15minutes to run? I have a similar macro on another spreadsheet that takes only 2-3 minutes for over 1,500 rows. I have already turned off screen updating. I can't think of anything else that would speed it up.
Sub HideNoSlackers()
'
' HideNoSlackers Macro
'
'
Application.ScreenUpdating = False
'
Sheets("CONSOLIDATED DATA").Select
Dim cell As Range
For Each cell In Range("K10:K250")
If cell.Value = 0 Then
cell.EntireRow.Hidden = True
Else
cell.EntireRow.Hidden = False
End If
Next
End Sub
You might want the calculation to be set Manual before hiding the rows? Also you can get rid of If statements in your case. Try this:
Sub HideNoSlackers()
Dim cell As Range, lCalcState As Long
Application.ScreenUpdating = False
' Record the original Calculation state and set it to Manual
lCalcState = Application.Calculation
Application.Calculation = xlCalculationManual
For Each cell In ThisWorkbook.Worksheets("CONSOLIDATED DATA").Range("K10:K250")
cell.EntireRow.Hidden = (cell.Value = 0)
Next
' Restore the original Calculation state
Application.Calculation = lCalcState
Application.ScreenUpdating = True ' Don't forget set ScreenUpdating back to True!
End Sub
Sub HideNoSlackers()
Dim cell As Range, rng As Range, rngHide As Range
Set rng = Sheets("CONSOLIDATED DATA").Range("K10:K250")
rng.EntireRow.Hidden = False
For Each cell In rng.Cells
If cell.Value = 0 Then
If Not rngHide Is Nothing Then
Set rngHide = Application.Union(rngHide, cell)
Else
Set rngHide = cell
End If
End If
Next
If Not rngHide Is Nothing Then rngHide.EntireRow.Hidden = True
End Sub
Why are you doing this with a macro?
If you create a table over the data, you can set up a filter on the sales column that will show only those where sales<> 0.
Macros are useful in excel but the majority of actions that people turn to macros for can be done natively in excel.
there must be something else that's wrong. Try without .Selecting the sheet but that's not a huge improvement
Note rows are visible by default so the Else statement should be optional really.
Sub HideNoSlackers()
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
Sheets("CONSOLIDATED DATA").Cells.EntireRow.Hidden = False
Dim cell As Range
For Each cell In Sheets("CONSOLIDATED DATA").Range("K10:K250")
If cell.Value = 0 Then cell.EntireRow.Hidden = True
Next
Application.EnableEvents = True
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
the shortest code to achieve the same Goal in a very different way:
Sub column_K_not_NULL
Sheets("CONSOLIDATED DATA").Select
If ActiveSheet.FilterMode Then Selection.AutoFilter 'if an autofilter already exists this is removed
ActiveSheet.Range("$K$10:$K$250").AutoFilter Field:=1, Criteria1:="<>0"
End Sub
of course you could put in the standard minimums like
application.calculation = Manual
Application.ScreenUpdating = False
and other way round at the end.
Max
Try disabling page breaks. I had a similar problem that would happen after someone printed from the sheet. This turned on page breaks, and subsequent runs of the script would take forever.
ActiveSheet.DisplayPageBreaks = False
We found out, that the program Syncplicity in the Version 4.1.0.1533 slows down macros up to 15times slower because events trigger syncplicity.
with
Application.EnableEvents = False
;do your job here
Application.EnableEvents = True
the speed is back.