excel VBA: move overlapping shapes on worksheet - excel

I am creating a calendar where it pulls events from a user inputsheet and places a text box object on another worksheet (Dates across the top and different departments down the left). It currently seperates the events on the top row of each section (i.e. all HR events on top row of HR section). I then run a MACRO to check for overlapping objects and move them down to the next row.
The code I use to move objects is below:
Sub MoveShapes()
'This Macro moves overlapping shapes down to the next row
Dim wb As Workbook
Set wb = ActiveWorkbook
Dim sh As Worksheet
Set sh = wb.ActiveSheet
Dim s1 As Shape
Dim s2 As Shape
Dim CheckOverlap As Boolean
Worksheets("SRTC").Activate
For i = 1 To sh.Shapes.count
If i <= sh.Shapes.count Then
Set s1 = sh.Shapes(i)
Search:
CheckOverlap = False
For Each s2 In Worksheets("SRTC").Shapes
If s2.ID = s1.ID Then GoTo Suit
If s2.Left <= (s1.Left + s1.Width) And s2.Left >= s1.Left _
And s2.Top <= (s1.Top + s1.Height) And s2.Top >= s1.Top Then
s1.Top = s1.Top + 18 ' 32
CheckOverlap = True
Exit For
End If
Suit:
Next
If CheckOverlap = True Then GoTo Search
End If
Next
End Sub
(I found this code in a different forum) This code works but is extremely slow. It is comparing each textbox with all the text boxes on the worksheet. My worksheet has over 3000 shapes and the MACRO takes over 4 hours to run.
Is there a way to write this code to only move objects within certain ranges? (ie only HR section)
Thanks

The first thing would be to use application.screenupdating.
Also some variable declarations are not or badly done (i, Sh).
Don't use Goto. (i may use a do while or do until loop)
Why test on each loop If i <= sh.Shapes.count Thenwich obviously is the case?
You can avoid If s2.ID = s1.ID Then GoTo Suit by not using a for each (wich tests shapes already corrected also), but for j=i+ 1 to sh.shapes.count : set S2=sh.shapes(j) ....`
little reminder : on long IF tests , with several conditions, VBA will test all the conditions before continuing, so instead of testing 4 conditions, test only the 2 more important and then test the two others, for example.
Beware, comments, buttons, and many other stuff is also a shape, so you might need a test of the shape's type (on s1). Avoid unessaceray looping.
On a personal note, i'd use a dictionary and a class type, the whole thing would take max 5 secondes to loop, and no i won't write that code for you.
Your approach is more at your level and is good enough with a bit of code optimization using the hints i gave.

Related

Excel | Disable button at start

I am working on an excel file that will work as a calendar with specifications.
I want to have a button at each day. Since I want this to be reusable for other years, I will have buttons on columns with no days (for example, if January starts on a tewsday, Monday will have a button, but nothing on the day, since it is from December).
I know it is possible to set a button enable = False, but I don't know where to put that code. I don't want it to be disabled when another button is clicked but at the opening of the file.
I am new to vba, I'm sorry if this is something really simple.
My approach needs those cells with days from previous month to be empty or "", if theres any value inside it wont work (instead you change the logic to treat cells values like numbers instead of strings).
I noticed that days in your calendar are in string format or so (i.e: "01") that's why I use Len() to evaluate length of string.
This code will set buttons visibility based on TopLeftCell value. Visible = True to days with some value, and Visible = False to empty values.
There is a way to make a button "Enable" but that property is for buttons inside an UserForm.
Tell me if it works for your case, since Sheet.CurrentRegion may cause some issues if your cells are way to much separate from each other, plus it could also hide some other buttons you have. If any of those scenarios do happen let me know, I'll continue helping you anyways!
Sub Set_Buttons_Visibility()
Dim Sheet As Worksheet
Dim Calendar_DataBodyRange As Range
Dim Shape As Shape
'Set Calendar range
Set Sheet = ActiveSheet 'Set Sheet
Set Calendar_DataBodyRange = Sheet.Cells(1, 1).CurrentRegion 'Set current region
Calendar_DataBodyRange.Select '<- comment this after you tested everything[']
'Hide buttons from previous month
For Each Shape In Sheet.Shapes
'If Shape.Visible Then Shape.Select
'Get variables
'Get Button day, as string
strTemp = CStr(Shape.TopLeftCell)
'Get range occupied by button
Set rngTemp = Sheet.Range(Shape.TopLeftCell, Shape.BottomRightCell)
'rngTemp.Select
'Test conditions
'Test rngTemp is part of Calendar_DataBodyRange
bInRange = Not Intersect(Calendar_DataBodyRange, rngTemp) Is Nothing
'Test TopLeftCell has some string
bString = (Len(strTemp) > 0)
'Test bInRange and bShow (True and True)
bCondition = (bString = False) And bInRange
'Perform action
'Set shape visibility
Shape.Visible = Not (bCondition)
'Delete shape (only if you have another procedure to rebuild all buttons)
''''Shape.delete
Next
End Sub
Run code when workbooks opens
To start this function when workbook opens, go to VBA Project Explorer > ThisWorkbook then inside the module you can bind your code to Workbook_Open event. Later on (depending in where you've have stored your code) use the following Run function.
Important:
According to your case you might need to store your code 1) inside the sheet you are working on, in other cases you store your code 2) in a single sheet usually called PERSONAL.XLSB that is always open when Excel itself Opens (Know more about this) so your functions can be accesible for all sheets that you work on.
Pros and Cons:
On the first case is perfect for sharing your work with your boss or colleagues since your code is locally stored in the sheet (but is harder to update, and hard to back up) and the second case is optimal for your own use since all your functions are in the same workbook so you can call it like "[Workbook.Name]![FunctionName],[FunctionParameters]" (allows you to do better updating and an easier backup just by copy-pasting). In any case you can addapt to your necessities.
Private Sub Workbook_Open()
'Run sintax needs Workbook [extension] and string [!]
'Function is stored in current workbook (case 1)
Run ThisWorkbook.Name & "!Set_Buttons_Visibility"
'Function is stored in PERSONAL (case 2)
Run "PERSONAL.XLSB!Set_Buttons_Visibility"
End Sub

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

Calling/Referring to a named Range from another Sub

I managed to write a code from different threads and code examples from around the web. It is trial and error and lots of copy-pasting.
I have several ranges defined within my subs:
Define range names:
X.Sheets("Sheet1").Range("B4").Name = "Type1"
X.Sheets("Sheet1").Range("B9").Name = "SubTotal1"
X.Sheets("Sheet1").Range("A6:F8").Name = "Data1"
X.Sheets("Sheet1").Range("B11").Name = "Type2"
X.Sheets("Sheet1").Range("B16").Name = "SubTotal2"
X.Sheets("Sheet1").Range("A13:F15").Name = "Data2"
X.Sheets("Sheet1").Range("B18").Name = "Type3"
X.Sheets("Sheet1").Range("B23").Name = "SubTotal3"
X.Sheets("Sheet1").Range("A20:F22").Name = "Data3"
Y.Sheets("Sheet1").Range("A4:A6").Name = "Period"
Y.Sheets("Sheet1").Range("B4:B6").Name = "Name"
Y.Sheets("Sheet1").Range("D4:D6").Name = "Code"
Y.Sheets("Sheet1").Range("E4:E6").Name = "Type"
Y.Sheets("Sheet1").Range("F4:K4").Name = "Data"
This name range is used in every sub (I have around 15, with around 165 more needed) for copying and inserting information from Workbook X to Workbook Y.
Since it is redundant to reuse the code, I would like to put these Ranges in a separate Sub and call on it in each new Sub.
I would also like to do the same with the following code, which refers to the ranges defined above:
'Insert Type1 Data from X:
If X.Sheets("Sheet").Range("SubTotal1").Value > 0 Then
Range("Type1").Copy
Y.Sheets("Sheet1").Range("Type").Insert xlShiftDown
Range("Data1").Copy
Y.Sheets("Sheet1").Range("Data").Insert xlShiftDown
'Insert Period:
X.Sheets("Sheet1").Range("C3").Copy
Y.Sheets("Sheet1").Range("Period").Insert xlShiftDown
'Insert Name:
X.Sheets("Sheet1").Range("C12").Copy
Y.Sheets("Sheet1").Range("Name").Insert xlShiftDown
'Insert Code Type:
X.Sheets("Sheet1").Range("C10").Copy
Y.Sheets("Sheet1").Range("Code").Insert xlShiftDown
End If
This code, and 6 more like it (Type 1-6) are also redundant in other Subs, so ideally, I would put it in a separate sub and call on it when necessary too. I use this at the beginning of my subs to define X and Y sheets:
Dim X As Workbook
Dim Y As Workbook
'Define workbooks:
Set X = Workbooks.Open("C:\Users\user\Folder\File.xlsx")
Set Y = ThisWorkbook
EDIT: To give a better example of what I mean, I imagine Subs going something like this:
Sub Sub1
Call Sub "RangeNames"
Call Sub "Insert Type1 Data while referring to RangeNames"
Call Sub "Insert Type2 Data while referring to RangeNames"
End Sub
And/Or
Sub Sub2
Call Sub "RangeNames"
Call Sub "If RangeName 'SubTotal 3' > 0 then Insert Type3 Data while referring to RangeNames"
End Sub
EDIT 2:
For #SJR:
Sub Sub1
Dim X As Workbook
Dim Y As Workbook
Set X = Workbooks.Open("C:\Users\user\Folder\File.xlsx")
Set Y = ThisWorkbook
X.Sheets("Sheet1").Range("B4").Name = "Type1"
X.Sheets("Sheet1").Range("B9").Name = "SubTotal1"
Y.Sheets("Sheet1").Range("E4:E6").Name = "Type"
Sub2
End Sub
Sub 2 is:
Sub Sub2
If X.Sheets("Sheet").Range("SubTotal1").Value > 0 Then <- ERROR HAPPENS HERE
Range("Type1").Copy
Y.Sheets("Sheet1").Range("Type").Insert xlShiftDown
End If
End Sub
What you need are arguments (aka parameters).
e.g.
Sub CopyAndInsertStuff(sourceLocation as String, destinationLocation as String)
Set wbSrc = Workbooks(sourceLocation)
Set wbDst = Workbooks(destinationLocation)
'Do your copying and inserting logic here...
End Sub
Then call that function by:
Call CopyAndInsertStuff("C:\path\to\source\File.xlsx", "C:\path\to\destination\File.xlsx")
If you are looking at adding another 165 subs, may I suggest to have a look at loops and/or arrays?
It might take you over all about the same time to develop it (considering the learning curve), but the code will be about 150 times shorter (do everything in 1-2-3 subs), and much easier to maintain. This, and in conjunction with the suggested parameters to call similar functionality from other subs or functions, would be a lot more efficient.
Here are the first results from Google when it comes to loops and arrays, and after a quick look, they do cover the basic needs:
Loops: https://www.excel-easy.com/vba/loop.html
Arrays: https://www.excel-easy.com/vba/array.html
Final advice, keep in mind that the less you interact with the workbooks from VBA, the faster your macros will run. ie: load your full range in an array, perform the transforming you want, then put it back in the workbook - you are only accessing the workbook 2 times as needed. If on the other hand, you use vba to copy cell A to cell B, few tens/hundreds of thousands times... it will be slower.

Excel 2010 command button disappears

I'm developing an Excel 2010 workbook, in a manual formulas calculation mode.
(file -> options -> formulas -> Workbook calculation -> manual)
I have some command buttons in the sheet (ActiveX controls), and I set them to move and size with cells (right click on the button -> format control -> Properties -> move and size with text).
This is since I have some rows filtered out under some conditions, and I want the buttons placed in these rows to appear and disappear as well, according to the display mode of their hosting rows.
It all goes perfectly fine, till I save he worksheet when some of the rows (hence buttons) are filtered out (i.e. not displayed).
When I re-open the file again, and expand the filtered rows, the buttons don't show. When checking their properties I see that their visible property is True, but their height is 0, and this doesn't change when I un-filter their hosting rows.
I want to emphasize again that before saving the file - both filtering and un-filtering the buttons worked well.
Would much appreciate any help here.
OK so I get the same results either with ActiveX or Form Controls. For whatever reason, it seems the control's original height does not persist beyond the save & close.
Another option would be to simply clear the AutoFilter on the Workbook's Close and Save events. However, this probably is not what you want if you like to leave some filter(s) on when you save and re-open the file. It's probably possible to save the filter parameters in a hidden sheet or by direct manipulation of the VBE/VBA, but that seems like a LOT more trouble than it's worth. Then you could re-apply the filter(s) when you re-open the workbook.
Here is what code I suggest
NOTE: I relied on the worksheet's _Calculate event with a hidden CountA formula (setting, changing, or clearing the AutoFilter will trigger this event). I put the formula in E1 just so you can see what it looks like:
Since your application relies on Calculation = xlManual then this approach will not work exactly for you but in any case, the subroutine UpdateButtons could be re-used. You would need to tie it in to another event(s) or functions in your application, as needed.
Here is the code
Option Explicit
Private Sub UpdateButtons()
'## Assumes one button/shape in each row
' buttons are named/indexed correctly and
' the first button appears in A2
Dim rng As Range
Dim shp As Shape
Dim i As Long
Application.EnableEvents = False
'## use this to define the range of your filtered table
Set rng = Range("A1:A6")
'## Iterate the cells, I figure maybe do this backwards but not sure
' if that would really make a difference.
For i = rng.Rows.Count To 2 Step -1
Set shp = Nothing
On Error Resume Next
Set shp = Me.Shapes(i - 1)
On Error GoTo 0
If Not shp Is Nothing Then
DisplayButton Me.Shapes(i - 1), Range("A" & i)
End If
Next
Application.EnableEvents = True
End Sub
Private Sub DisplayButton(shp As Shape, r As Range)
'# This subroutine manipulates the shape's size & location
shp.Top = r.Top
shp.TopLeftCell = r.Address
shp.Height = r.Height
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
MsgBox "_Change"
End Sub
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
''## Assumes one button/shape in each row
'' buttons are named/indexed correctly and
'' the first button appears in A2
'Dim rng As Range
'Dim shp As Shape
'Dim i As Long
'
''## Uncomment this line if you want an annoying message every time
''MsgBox "Refreshing Command Buttons!"
'
'Application.EnableEvents = False
''## use this to define the range of your filtered table
'Set rng = Range("A1:A6")
'
''## Iterate the cells, I figure maybe do this backwards but not sure
'' if that would really make a difference.
'For i = rng.Rows.Count To 2 Step -1
' Set shp = Nothing
' On Error Resume Next
' Set shp = Me.Shapes(i - 1)
' On Error GoTo 0
'
' If Not shp Is Nothing Then
' DisplayButton Me.Shapes(i - 1), Range("A" & i)
' End If
'Next
'
'Application.EnableEvents = True
End Sub
For Another option See this article. You can re-purpose existing commands with RibbonXML customization. While this article is geared towards C# and Visual Studio it's possible to do it with the CustomUI Editor.
I had a similar problem with buttons disapearing (moving on upper left corner) when removing filters.
A solution I found was to add a row above the columns headers so that buttons were still appearing at the top of the columns but were not touching the row where filters were placed.
Adding / removing filters stop interfering with buttons' positions.
I had a similar problem where form buttons appear to work fine, but then disappear after saving and reopening the workbook. Specifically this happened when the form button where part of hidden rows (done using vba code).
Seems like a real bug, although I don't know where the link is.
By changing the form buttons to ActiveX buttons, the buttons stopped disappearing, but started moving/bunching to the top of the screen when the rows were hidden. I just added some vba to re-position the buttons (e.g. CommandButton1.Top = Range(A12:A12).Top --> moves the ActiveX command button to the 12th row).

Charts Do Not Automatically Update When Data Changes

Hopefully this is an easy one. I have a series of charts in MS Excel that point to data on the same worksheet. The data on the worksheet is calculated using a VBA function. When the data is updated by the VBA function the new numbers are not reflected in the charts that are pointing to them. I tried calling Application.Calculate, but that didn't do the trick. Any thoughts?
UDPATE:
I was able to duplicate this issue on a much smaller scale. Here's how:
Create a new workbook
Rename Sheet 1 to "Summary"
Rename Sheet 2 to "Data"
Open the Summary sheet in the VBA editor and paste the following code:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Parent.Range("worksheetDate") = Target Then
Application.CalculateFull
End If
End Sub
Create a new VBA module
Paste the following code into the new VBA module (I apologize - I can't get Stack Overflow to format this correctly for the life of me - this is the best I could get it to do):
.
Function getWeekValue (weekNumber As Integer, valuesRange As Range) As Integer
Dim aCell As Range
Dim currentDate As Date
Dim arrayIndex As Integer
Dim weekValues(1 To 6) As Integer
currentDate = ThisWorkbook.Names("worksheetDate").RefersToRange.Value
arrayIndex = 1
For Each aCell In valuesRange
If month(currentDate) = month(ThisWorkbook.Sheets("Data").Cells( _
aCell.Row - 1, aCell.Column)) Then
weekValues(arrayIndex) = aCell.Value
arrayIndex = arrayIndex + 1
End If
Next
getWeekValue = weekValues(weekNumber)
End Function
.
Modify the Data worksheet to match the following image:
Select Cell B1 and name the range "worksheetDate"
Duplicate rows 1 through 3 in the following image:
In row 4, under the "Week X" headers, enter the following formula
.
= getWeekValue(1, Data!$A$2:$M$2)
incrementing the first argument to the getWeekValue function by one for each week (e.g., pass 1 for Week 1, 2 for Week 2, 3, for Week 3, etc.
Create a bar graph using cells A3 through E4 as the data
Change the date in cell B2 to a date between 10/1/2010 and 12/31/2010, choosing a month other than the month that is currently in the cell. For example, if the date is 12/11/2010, change it to something like 11/11/2010 or 10/11/2010. Note that both the data and chart update correctly.
Modify the date in cell B2 gain. Note that the data updates, but the chart does not.
Oddly, after a period of time (several minutes) has elapsed, the chart finally updates. I'm not sure if this is because I have been performing other activities that triggered the update or because Excel is triggering an update after several minutes.
Just figured out the solution to this issue as I was suffering from the same.
I've just added "DoEvents()" prior to printing or exporting and the chart got refreshed.
example
Sub a()
Dim w As Worksheet
Dim a
Set w = Worksheets(1)
For Each a In w.Range("a1:a5")
a.Value = a.Value + 1
Next
DoEvents
End Sub
at the end of my changes I close the workbook and reopen it. that seems the easiest and most reliable way to update everything for me.
For example:
Sub a()
Dim w As Worksheet
Dim a
Set w = Worksheets(1)
For Each a In w.Range("a1:a5")
a.Value = a.Value + 1
Next
w.ChartObjects(1).Chart.Refresh
End Sub
This solution worked for me. For the offending worksheet add:
Private Sub Worksheet_Activate()
Dim rngSelection As Range
Dim objChartObject As ChartObject
Dim objChart As Chart
Dim objSeriesCollection As SeriesCollection
Dim objSeries As Series
Dim strFormula As String
Set rngSelection = Selection
For Each objChartObject In Me.ChartObjects
Set objChart = objChartObject.Chart
Set objSeriesCollection = objChart.SeriesCollection
For Each objSeries In objSeriesCollection
strFormula = objSeries.Formula
objSeries.Delete
Set objSeries = objSeriesCollection.NewSeries
objSeries.Formula = strFormula
Next objSeries
Next objChartObject
rngSelection.Select
End Sub
It's possible that the issue is the argument list of getWeekValue, which includes only the week number and the data stream.
If you add a third argument, worksheetDate, then Excel's recalculation engine will be hit on the side of the head with the fact that getWeekValue uses the value held in worksheetDate. In your current implementation, this fact is held only in the VBA code, where it is probably invisible to the recalculation engine.
I write this so hedgingly because I am not privy to the inner workings of the recalculation engine. (Maybe someone who knows about this better than I can comment on my speculation) But I did do a test, in which getWeekValue does have that third argument, and the chart does recalculate properly. Nice added benefit of this approach: you can remove all that other VBA event management. -HTH
I've found that calling this Sub works...
Sub DoAllEvents()
DoEvents
DoEvents
End Sub
BUT
Microsoft cautions about being caught with the next DoEvents executing before the first DoEvents completes, which can happen depending on how often it's called without a delay between calls. Thus DoEvents appears to be acting as a type of non maskable interrupt, and nesting non maskable interrupts can cause the machine to freeze for multiple reasons without any recovery other than reboot.
(Note: If one is not calling the routine above, often and quickly, nesting may not
be an issue.)
Using the following Sub below, which I modified from their suggestion, prevents this from happening.
Sub DoAllEvents()
On Error GoTo ErrorCheck
Dim i
For i = 1 To 4000 ' Start loop. Can be higher, MS sample shows 150000
'I've found twice is enough, but only increased it to four or 4000.
If i Mod 1000 = 0 Then ' If loop has repeated 1000 times.
DoEvents ' Yield to operating system.
End If
Next i
Exit Sub
ErrorCheck:
Debug.Print "Error: "; Error, Err
Resume Next
End Sub
I appears that the number of DoEvents needed is based on the number of background tasks running on your machine, and updating the graph appears to be a background task for the application. I only needed two DoEvents because I call the routine frequently; however, I may end up upping it later if needed.
I also keep the Mod at 1000 so to not change the lag between each DoEvents as Microsoft suggests, preventing nesting. One possible reason you might want to increase the number from 2000 to a higher number is if you system does not update the graph. Increasing this number allows the machine to handle larger numbers of background events that DoEvents might encounter through multiple calls as they are probably on a stack, and the DoEvents event is only allowed to run a specific number of cycles before marking its place in the stack to allow unhandled events and returning, leaving them to be handled on the next call. Thus the need for multiple calls. Changing this to their example of 150000 doesn't appear to slow the machine too much, to play it safe you might want to make it 150000.
Note: the first example Sub with two DoEvents is probably safe depending on how often you call the Sub, however, if called too often, your machine might freeze up. Your call. ;-)
PS: DoEvents will become one of your best calls if you create a lot of nested loops and the program doesn't behave as expected. Fortunately, this is available in all apps that use VBA!
Running Excel 2019.
Added the following to the macro code:
ActiveSheet.ChartObjects(1).Chart.Refresh
DoEvents
The chart now updates during macro execution
UDF getWeekValue has to be marked as volatile.
Function getWeekValue (weekNumber As Integer, valuesRange As Range) As Integer
Application.Volatile '!!
Dim aCell As Range
Dim currentDate As Date
'...
Just an idea: in your Worksheet_Change Sub, insert as the first line:
Application.EnableEvents = False
in order to avoid self-firing events....
Of course set it back to True at the end of the Sub.

Resources