Excel Automatic Calculation at the sheet level only - excel

I have an excel sheet that became very slow.
For some reason, wherever I am in the document, if I set Calculation to manual and refresh the current sheet after any change, it is fast enough and serves my purpose.
This is not very comfortable however.
I would like the current sheet (and not the whole document) to be refreshed whenever a cell is changed. This should be done whatever sheet I'm on. How can I do that?
EDIT: let it be clear that I'm not asking for clues on how to make my workbook faster, it was just contextual info. I'm interested in autorefresh only.

Charles Williams has extensive information, techniques and code on calculation on his website http://decisionmodels.com. Quoting from this page:
Another method uses the worksheet.enablecalculation property. When
this property is changed all the formulae on the worksheet are flagged
as uncalculated, so toggling the property to false and then back to
true for all sheets in all open workbooks will cause the next
calculation to be a full calculation.
> Dim oSht as worksheet Application.Calculation=xlCalculationManual
>
> for each oSht in Worksheets oSht.enablecalculation=false
> osht.enablecalculation=true next osht
>
> Application.calculate
You can also use this method for a single worksheet to do a full sheet
calculate.
You can easily work this technique into the Worsheet_Change event, like this:
Private Sub Worksheet_Change(ByVal Target As Range)
Application.Calculation = xlCalculationManual
Me.EnableCalculation = False
Me.EnableCalculation = True
Me.Calculate
End Sub
This will do a full calculation of the current sheet only and leave all other sheets uncalculated.

I believe the Calculation event can happen at the Application, Worksheet or Range level (but not the Workbook level.)
If you have calculation mode set to manual but want the current worksheet (only) updated when you enter something, you could add a worksheet change event (putting the code in the relevant sheet.)
Private Sub Worksheet_Change(ByVal ChangedRng As Range)
ChangedRng.Worksheet.Calculate
End Sub
This will recalculate just the worksheet and not the rest of the Workbook or any other workbooks.

As the provided answers don't fully satisfy me here is my own, borrowing from their appreciated help. It's possible that I've missed some nuance from teylyn's answer. I'll gladly edit if it's relevant.
There doesn't seem to be a way to handle change events at the APPLICATION level.
So all changes must be handled at the SHEET level.
This can be done by copying and pasting this bit of code in EVERY Sheet object of the workbook (and remember to copy and paste it it whenever you add a new sheet):
Private Sub worksheet_change(ByVal Target As Range)
Me.Calculate
End Sub
However because of the tedious copy pastes we had to do, we may want to turn this feature off easily without going back to each sheet object.
We may also want to avoid entering a loop of hell if we do mass change on one sheet (say through a macro, a drag & drop, or a ctrl+H).
To do this we add a new sheet called WS_refresh where we set 3 values :
in A2: Yes/No to enable/disable our new feature
in A4: time of latest refresh
in A6: min interval to allow refresh, in sec (I've set it to 1)
Now when we change values in several cells in a short amount on time, autorefresh will only work on the first change, avoiding previously mentioned loops of hell. If you like to live dangerously set A6 to 0.
This is what should be copied and pasted in every sheet object:
Private Sub worksheet_change(ByVal Target As Range)
auto_refresh = ThisWorkbook.Worksheets("WS_refresh").Range("A2")
If auto_refresh = "Yes" Then
last_refresh = ThisWorkbook.Worksheets("WS_refresh").Range("A4")
refresh_interval_sec = ThisWorkbook.Worksheets("WS_refresh").Range("A6")
refresh_interval_tv = TimeValue("0:00:" & refresh_interval_sec)
If Application.Calculation = xlCalculationManual And Now() > last_refresh + refresh_interval_tv Then
ThisWorkbook.Worksheets("WS_refresh").Range("A4") = Now()
Me.Calculate
End If
End If
End Sub

Related

Is there a way to freeze, snapshot, and chart streaming RTD data?

I have real-time data streaming from another application into Excel and making =RTD() calls successfully in a complex workbook.
Trying to (a) compare historical data as of a moment in time to current/live data, and, (b) run stats on snapshots taken. Say I have RTD data in 4 rows and 8 columns A1:H4.
The "freeze": Is it possible to push a button (to run a macro) and render, say, row 2 (A2:H2) 'inert', so it's no longer RTD? Basically, copy then-current values and paste them into the same cells (A2:H2) as values, removing the RTD formulas so that the data stops changing at that moment.
The "snapshot": Make a copy of the 3rd row, A3:H3, and paste it into A5:H5 periodically by timer, and have the copy become 'inert', i.e., no longer RTD or linked to RTD in row 5 while row 3 remains RTD. And place a timestamp for this action in cell I5 next to the copy.
This should also theoretically enable me to chart the snapshot data. I can't get RTD data to chart - expected that chart would update as streaming data changed, but charts won't display at all. So that's a secondary but related issue. It would appear that if I can snapshot the data and make it inert, I should be able to chart.
I've tried all kinds of manual processes from paste special/values, go to / special / formulas, highlight, etc. to using a third party tool. I've looked for VBA macros and found one which I'm pasting below, but I can't get it to work (the macro runs on cells to which it's directed, does not error out, but nothing happens). And even if it did work, it would only solve part of the problem as per above.
I do not speak VBA but write enough code in other languages that I should be able to figure out what it does and mod it, if someone is able to offer the base functionality. Any help would be greatly appreciated.
'This code should go in a regular module sheet, and won't work properly if installed anywhere else. _
The next (Dim) statement must occur before any subs or functions.
Dim NextTime As Double
Sub RecordData()
Dim Interval As Double
Dim cel As Range, Capture As Range
Interval = 5 'Number of seconds between each recording of data
Set Capture = Worksheets("Sheet1").Range("A1:A5") 'Capture this column of data
With Worksheets("Sheet2") 'Record the data on this worksheet
Set cel = .Range("A2") 'First timestamp goes here
Set cel = .Cells(.Rows.Count, cel.Column).End(xlUp).Offset(1, 0)
cel.Value = Now
cel.Offset(0, 1).Resize(1, Capture.Cells.Count).Value = Application.Transpose(Capture.Value)
End With
NextTime = Now + Interval / 86400
Application.OnTime NextTime, "RecordData"
End Sub
Sub StopRecordingData()
On Error Resume Next
Application.OnTime NextTime, "RecordData", , False
On Error GoTo 0
The above code is initiated when the workbook is opened, and stopped when the workbook is closed by code in ThisWorkbook code pane.
'These subs must go in ThisWorkbook code pane. They won't work at all if installed anywhere else!
Private Sub Workbook_BeforeClose(Cancel As Boolean)
StopRecordingData
End Sub
Private Sub Workbook_Open()
RecordData
End Sub
End Sub

Excel VBA: How to dynamically enlarge/expand ActiveCell?

I ran across a question in a facebook group which asked how to expand/enlarge/zoom the active cell in an Excel worksheet.
Normally, I don't like to mess with the Excel UI visually but I guess that the person must have some form of visual impairment or something which requires a clear and bigger view of the contents of the active cell.
I searched in stackoverflow and googled and also in the similar questions box which doesn't show the exact same answer I was searching.
I believe that there are multiple possible approaches to this question.
1.to change the rowHeight and columnWidth of the activecell.
Application.ActiveCell.RowHeight=50
Application.ActiveCell.ColumnWidth=50
2.to change the autofit of the column containing activecell.
Application.ActiveCell.EntireColumn.AutoFit
3.to change the zoom level of activewindow.
ActiveWindow.Zoom 50
4.to assign the activecell contents into a textbox.text property on a modeless userform.
I think methods 1 & 4 are most likely to work and personally, I prefer method4 because it seems less likely to visually disturb the user.
Workbook_SheetSelectionChange "event will be used
Allow me to answer to my own question with the following VBA code.
Please feel free to improve upon this.
'copy paste into ThisWorkbook module
'explicit error checking was not performed - use at users' own risk
Option Explicit
Const increasedColumnWidth = 50'change how large as per requirement
Const increasedRowHeight = 50
'saved properties to restore later
Private saved_ActiveCell_ColumnWidth As Integer
Private saved_ActiveCell_RowHeight As Integer
Private saved_ActiveCell As Range
'can also be placed into individual worksheet modules but used ThisWorkbook to cover newly inserted sheets
Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
'very important that sh must be a worksheet<-not checked here
' If Sh.Name = "Sheet1" Then 'set sheet name here to limit to Sheet1 only or any particular sheet
If Target.Cells.CountLarge = 1 Then 'if .Count, there may be overflow
Application.ScreenUpdating = False 'to reduce flashing
If Target.Value <> "" Then 'or isempty(target.value)
'restoring previous activecell if there's one
If Not saved_ActiveCell Is Nothing Then
saved_ActiveCell.EntireColumn.ColumnWidth = saved_ActiveCell_ColumnWidth
saved_ActiveCell.EntireRow.RowHeight = saved_ActiveCell_RowHeight
End If
'backup
Set saved_ActiveCell = Target 'Application.ActiveCell
saved_ActiveCell_ColumnWidth = Target.ColumnWidth
saved_ActiveCell_RowHeight = Target.RowHeight
'expanding
Target.EntireRow.RowHeight = increasedRowHeight
Target.EntireColumn.ColumnWidth = increasedColumnWidth
Else'if activecell doesn't contain any value, restore previous activecell to previous size
If Not saved_ActiveCell Is Nothing Then
saved_ActiveCell.EntireColumn.ColumnWidth = saved_ActiveCell_ColumnWidth
saved_ActiveCell.EntireRow.RowHeight = saved_ActiveCell_RowHeight
End If
End If
Application.ScreenUpdating = True
End If
' End If
End Sub
Sharing this code doesn't mean that I support messing around with Excel UI manipulation especially those that would disturb the user.
The provided code is just for changing activecell's rowHeight and columnWidth to give the user the visual effect that the activecell is expanded/enlarged.
I've seen other code that would create an image of activecell on-the-fly and gives the user the impression of a zoomed cell here.
The code shared herewith is only for manipulating the rowHeight and columnWidth of the ActiveCell.
The method dealing with a textbox on a modeless userform is still getting improvements and will eventually be available(probably) on my GitHub as a .xlsm file.
Also uploaded a .gif below to clearly display the requirements and the results.
This video .gif contains features not available in the code shared here.

Excel VBA Code won't run "behind" multiple workbook sheets: Better approach?

I am a complete novice, this is my first VBA code (necessity is mother of . . . inept coding by novice).
Problem: Why is my code not updating in real-time? Or in any time at all? Can it be fixed? Do I need to somehow put all 16 sheets worth of VBA code into a "module" or do some other trick to fix it?
Background:
I have VBA code "behind" multiple "client" spreadsheets in a workbook. The code allows cell colors to transfer to a master "all clients" spreadsheet. The reason I needed the VBA code was that there was a function (and INDEX function) already in the color-filled cells.
The code was not working properly, so I figured out that the references were wrong and edited one of the sheets' VBA code to ensure I had the references right. They were correct. But even getting those edited references in that one sheet's code to work correctly took a bunch of clicking around and saving and reopening the document.
I then needed to fix the code in all the other sheets, starting with one of them. I can't for the life of me get anything to happen even though I made the correct edit. I should have seen colors change, but nothing happened.
Google search led me to the news that just putting code "behind" spreadsheets often doesn't work. One reference said I should place it in a module. But I have no idea how to do that across all of my 16 client sheets.
I'm also working over Remote Desktop which is probably not helping. I could probably send myself the workbook if needed.
Below is my code (one sheet's worth). The references are different across sheets so that the various client's data (in vertical columns) populates on the correct horizontal rows of the master sheet. Along with that data are the colors that this VBA code is supposed to help render onto the master sheet.
This is the "Glen" spreadsheet's VBA code, Glen's data that needs to be color coded identically on the "WeeklyRatingsAllClients" sheet (ending up in the BD6:CH6 range and BD7:CH7 range) is in the Q4:Q38 range and the U4:U38 range. The other sheets are the exact same except that in the next person's sheet the BD6:CH6 range and BD7:CH7 ranges will update to become BD8:CH8 range and BD9:CH9 and so on sequentially (next client is 10, 11; next is 12, 13 etc.).
If it matters to anyone, I got the original code here and modified it for my needs: https://www.extendoffice.com/documents/excel/4071-excel-link-cell-color-to-another-cell.html
Also, I make a long comment on above page under "Sara" dated 3 months ago that describes more about the code/purpose and shows how I modified the example code for my purpose and it worked--it's just not working now (probably not useful if you already know this stuff well, like I don't).
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim xRg As Range
Dim xCRg As Range
Dim xStrAddress As String
xStrAddress = "WeeklyRatingsAllClients!$BD$6:$CH$6"
Set xRg = Application.Range(xStrAddress)
Set xCRg = Me.Range("$Q$4:$Q$38")
On Error Resume Next
For xFNum = 1 To xRg.Count
xRg.Item(xFNum).Interior.Color = xCRg.Item(xFNum).Interior.Color
Next
xStrAddress = "WeeklyRatingsAllClients!$BD$7:$CH$7"
Set xRg = Application.Range(xStrAddress)
Set xCRg = Me.Range("$U$4:$U$38")
On Error Resume Next
For xFNum = 1 To xRg.Count
xRg.Item(xFNum).Interior.Color = xCRg.Item(xFNum).Interior.Color
Next
End Sub
Perhaps use the Workbook.SheetSelectionChange event, something like the following. Note that this can definitely be refactored.
Make sure to add this code in the ThisWorkbook module.
Change "Bob", "Fred", "Joe" to the sheet names in question (in order), and add more Cases as needed, always increasing the offsetNum by 2 from the previous Case.
There's a mismatch in the number of cells on the main sheet vs the client sheet. U4:U38 would be 35 cells, but BD6:CH6 is only 31... more an FYI.
Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
Dim offsetNum As Long
Select Case Sh.Name
Case "Glen"
offsetNum = 0
Case "Bob"
offsetNum = 2
Case "Fred"
offsetNum = 4
Case "Joe"
offsetNum = 6
Case Else
Exit Sub
End Select
Dim allClientsSheet As Worksheet
Set allClientsSheet = Me.Worksheets("WeeklyRatingsAllClients")
Dim mainColorRange As Range
Set mainColorRange = allClientsSheet.Range("BD6:CH6").offset(offsetNum)
Dim sourceColorRange As Range
Set sourceColorRange = Sh.Range("Q4:Q38")
Dim i As Long
For i = 1 To mainColorRange.Rows(1).Cells.Count
mainColorRange.Rows(1).Cells(i).Interior.Color = sourceColorRange.Cells(i).Interior.Color
Next
Set sourceColorRange = Sh.Range("U4:U38")
For i = 1 To mainColorRange.Rows(2).Cells.Count
mainColorRange.Rows(2).Cells(i).Interior.Color = sourceColorRange.Cells(i).Interior.Color
Next
End Sub

Handling worksheet_change event before dependent formulas are calculated

I have a use case where there's a cell for user input, and a bunch of other cells with formulas dependent on this cell. My intention is to sanitize the data before the formulas are calculated. These formulas can be quite expensive, so I'd like to prevent having them calculated on garbage data if possible. So I wanted to have some vba code check the input and clean it up before the formula calculation is fired. I set up a Worksheet_Change event to handle the user input, however, unfortunately, it would seem that dependent cells are calculated even before Worksheet_Change() is entered. Anyway I can achieve what I want?
You could try this:
First, switch off auto-calculate by choosing Manual from the Calculation Options on the Formulas menu.
Then add this line at the end of your cleaning-up code, to cause the workbook to calculate:
Application.Calculate
This will update all those formulas with the new cleaned value, before Excel does the calculation (since auto-calc is switched off).
If you are worried about the user switching auto-calc back on, hence slowing down the calculations, you could add this further line to switch it off again as soon as possible:
Application.Calculation = xlCalculationManual
Note: maybe this is anti-social because I think the auto-calc option in Excel is instance-wide, so would affect the users' other spreadsheets. You could perhaps get round this with some code in the Workbook_Open event, to remember the state of the Application.Calculation property, then switch it off (as above), and then some code in the Workbook_Close event to restore it again. It may be advisable for the users to make sure they use your workbook alone in an Excel instance, to be sure of no inconvenience to them!
Edit: just on the last note, better to use Workbook_Activate and Workbook_Deactivate instead of Workbook_Open and Workbook_Close.
With that, here is the code, to go into your ThisWorkbook object.
Public previousCalculationState As XlCalculation
Private Sub Workbook_Activate()
previousCalculationState = Application.Calculation
Application.Calculation = xlCalculationManual
End Sub
Private Sub Workbook_Deactivate()
Application.Calculation = previousCalculationState
End Sub
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Debug.Print "before clean: G6 = " & Range("G6").Value ' this shows that G6 has the old value, before calc
' your cleaning code to go here, which can use Sh and Target to find out which cell changed
Application.Calculate
Application.Calculation = xlCalculationManual
Debug.Print "after clean: G6 = " & Range("G6").Value ' this shows that G6 has the new value, after calc
Debug.Print
End Sub
This code includes some Debug.Prints, which assume you have a formula in cell G6 on each sheet, and which prove that your cleaning code is happening before the formulas are calculated.

VBA Macro To Select Same Cell on all Worksheets

I'm somewhat newer to VBA, and this particular action seems like it may be out of my current scope of knowledge.
Is there a way to code VBA to have it actively select the same cell on all worksheets as the current cell selected? I have a model I've put together to allow my team to enter data simultaneously regarding product SKUs in Column A on Sheet1, but due to the large amount of information that we enter per item, I used multiple sheets
For example, if I have cell H4 selected on Sheet1, is it possible to have all other sheets active cell H4 upon switching to the other worksheets?
This is what I've come up with so far on a test workbook, but it does not seem to work:
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
Select Case LCase(Sh.Name)
Case Is = "sheet1", "sheet2", "sheet3"
If CurRow > 0 Then
With Application
.EnableEvents = False
.Goto Sh.Cells(CurRow, CurCol), Scroll:=True
Sh.Range(ActCellAddr).Select
.EnableEvents = True
End With
End If
End Select
End Sub
Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
Select Case LCase(Sh.Name)
Case Is = "sheet1", "sheet2", "sheet3"
CurRow = ActiveWindow.ScrollRow
CurCol = ActiveWindow.ScrollColumn
ActCellAddr = ActiveCell.Address
End Select
End Sub
I've located this code below:
Excel VBA code to allow the user to choose the same cell on every sheet
But this requires the user actually enter the cell they'd like to have selected. I am looking for it to be automatic.
Any tips or suggestions? Any help is greatly appreciated.
You can post the following to every sheet in your workbook.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Set CurrWS = ActiveSheet
For Each WS In ThisWorkbook.Worksheets
WS.Activate
WS.Range(Target.Address).Select
Next
CurrWS.Activate
End Sub
Every time you select a cell, it will cycle through all the worksheets and select the same cell there. The downside to this is obvious: if you have too many sheets, it's going to be tedious. The other problem is that it's going to cycle through everything. So it might mess up some other sheets if you're going to use this for data entry.
Otherwise, if it's just selecting the cell, then this is harmless though the flicker can be noticeable at times, based on how many sheets you have.
Not as elegant as one would want, but it works. Good luck and let us know if this helps.
Worth noting there is a workbook-level event handler which handles the same event, so you only need to add the code once to the ThisWorkbook code module:
Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, _
ByVal Target As Range)
Sh represents the ActiveSheet.
Probably also worth disabling events while you're selecting the ranges on the other sheets, or that will re-trigger your event handler (don't forget to turn event handling back on before exiting your code!)
This approach will test for hidden sheets. It selects all non-hidden sheets, selects the target cell then returns to the original sheet. It works pretty fast even if you have many many tabs.
targetcell = ActiveCell.Address
OriginSheet = ActiveSheet.Name
Dim ws As Worksheet
For Each ws In Sheets
If ws.Visible = True Then ws.Select (False)
Next ws
range(targetcell).Select
Sheets(OriginSheet).Select

Resources