0

My workbook is essentially for Statistical Process Control

I have multiple worksheets - WorksheetA and WorksheetB , C , D ....

WorksheetA is where users enter / update new data manually

WorksheetB, C and the rest each contains 2 pivot tables - PivotTable1 and PivotTable2. PivotTable1 filters the raw data based on the product group from WorksheetA and displays the in process data dynamically. Data from PivotTable1 is then copied into other columns (say, Column A to C) in the same worksheet and an excel formula evaluates whether is there any out of control situation. If there is, then the value of the data will be displayed, otherwise it will show N/A.

As I would like all out of control situation to be logged down, PivotTable2 captures data in Column A to C and only logs down the data with a value (it means that they are out of control) and ignore those with N/A.

Now, I would like excel to display a textbook that says "You have new out of control situation" whenever PivotTable2 has new value. In Cell D1, I have a total count of the no. of data in PivotTable2. This is where VBA comes in :-

1) When the count in Cell D1 is updated with a new value, the textbox will automatically pop out. Here's the code :-

Private Sub Worksheet_Calculate()
Static countvalue
If Range("D1").Value <> countvalue Then
    countvalue = Range("D1").Value
    MsgBox "You have new out of control situation"
End If
End Sub

2) However, in order for the first portion to work, PivotTable1 and PivotTable2 have to auto-refresh whenever new values are entered (in order for Cell D1 count to be updated). Therefore, in WorksheetA, I have another data count in Cell F1 which records the total number of data in one of the columns (let's call this Column Z). Whenever someone manually enters new data in to Column Z, the count in Cell F1 changes and triggers this auto-refreshing formula :-

Private Sub Worksheet_Calculate()
Static autorefresh
If Range("F1").Value <> autorefresh Then
    autorefresh = Range("F1").Value
    Sheets("WorksheetB").PivotTables("PivotTable1").RefreshTable
    Sheets("WorksheetB").PivotTables("PivotTable2").RefreshTable
End If
End Sub

These two groups of codes worked pretty well. However the only problem I face now is that whenever I save and reopen the workbook and enter new data in WorksheetA (but NOT in Column Z), the message box pops out even when the conditions for both worksheet_calculate are still not met. After this first msg box pop-out, everything returns normal again.

I'd appreciate it if anyone can shed some light to me on the codes that I'm using, and go through with me on what is wrong. I mostly referenced and combined the codes from various forums. I intend to replace the msg box with auto email sending capability whenever there is an out of control situation in the future. Therefore, if the worksheet_calculate function kept firing by itself whenever someone reopens the workbook, then false alarms will kept being sent to my email.

Thanks for your time reading through my questions. I appreciate it.

yswong
  • 15
  • 3
  • Change the Worksheet Event in `WorksheetA` to `Worksheet_Change` and test to make sure the change occurred in column Z before refreshing the PivotTables. – Scott Holtzman Dec 07 '15 at 18:47
  • Hi Scott, here's my code for WorksheetA now : Private Sub Worksheet_Change(ByVal Target As Range) If Not Intersect(Target, Range("Z:Z")) Is Nothing Then Sheets("WorksheetB").PivotTables("PivotTable1").RefreshTable Sheets("WorksheetB").PivotTables("PivotTable2").RefreshTable End If End Sub The msg box still auto-triggered whenever I entered a value in ColumnZ the first time when I reopen the workbook. ColumnZ refreshed whenever there is value added, but the msg box should only appear when the count in Cell D1, WorksheetB changes. – yswong Dec 08 '15 at 02:00
  • Now, I see why it's happening whenever someone reopens the workbook. Because your static variables `autorefresh` and `countvalue` reset back to `empty` everytime the workbook is open, so the logic will always result in `True` on the first calc after the workbook is open. You'd be better off by storing the last count value for `D1` in a cell somewhere and compare `D1` to that count (and update it each time the count is different), and `F1` as well, if you need it. Let me know if you need more explanation. – Scott Holtzman Dec 08 '15 at 13:14
  • Hi Scott, Thanks for your reply. If I understand you correctly, does it mean that in this line `If Range("D1").Value <> countvalue Then` Everytime when i reopen the workbook, `countvalue` is empty, and this means that `D1` does not equal `countvalue`, thus firing the next few lines? In this case, how am I able to achieve what you have proposed? I'm sorry but I'm quite new to VBA and I still have alot to catch up. Thanks alot for your patience Scott! – yswong Dec 08 '15 at 13:38
  • i'll put in an answer for you and that should be easier to follow then these comments. – Scott Holtzman Dec 08 '15 at 13:44
  • I'll certainly appreciate that Scott. Thanks alot – yswong Dec 08 '15 at 14:02

1 Answers1

0

The reason that code fires after the workbook re-opens each time is because the static variables autorefresh and countvalue reset back to empty every time the workbook is open, so the IF block logic will always result in True on the first calc after the workbook is open, firing the rest of the lines inside the block.

One way around this is to store the counts of cell D1 in Worksheets B, C ... etc. in a separate sheet.

So for example, if you make a sheet at the back of the workbook and call it reference, and make row 1 a header row, with WorksheetA, WorksheetB, WorksheetC, Worksheet D ... as headers, you can then use row 2 to store the counts of each sheet (cell F1 for WorksheetA, cell D1 for the rest). Furthermore, if you define a name for where each value is stored, based on the Worksheet name, you can make your code more functional and uniform (see code below.)

Then, in your code, you can compare the current value on each sheet against what was stored last. Also, in your code, when the value does end up being different, you store the new value in those same cells.

That said, your Worksheet_Calculate code for WorksheetA becomes:

Private Sub Worksheet_Calculate()

If Worksheets("reference").Range(Me.Name).Value2 <> Me.Range("F1").Value2 Then
    Worksheets("reference").Range(Me.Name).Value = Range("F1").Value2
    ThisWorkbook.RefreshAll 'will refresh all pivot tables in the workbook
    'Sheets("WorksheetB").PivotTables("PivotTable1").RefreshTable
    'Sheets("WorksheetB").PivotTables("PivotTable2").RefreshTable
End If

End Sub

Then, the Worksheet_Calculate code for all other sheets becomes:

Private Sub Worksheet_Calculate()

 If Worksheets("reference").Range(Me.Name).Value2 <> Me.Range("D1").Value2 Then
    Worksheets("reference").Range(Me.Name).Value = Me.Range("D1").Value2
    MsgBox "You have new out of control situation located in " & me.Name
End If

End Sub

One last thing: Make sure to enter values in the reference sheet before closing the workbook after making the changes. Otherwise, when you open the workbook the very first time the code will fire, because the values in the reference sheet will be Null and the If block will result in True again.

Scott Holtzman
  • 27,099
  • 5
  • 37
  • 72
  • Hi Scott, thanks for the answer. I'd still like to clarify the following :- I'm having a hard time understanding this particular line, especially on the `Range(Me.Name).Value2` part. What is the difference between `Value2` and `Value`? `If Worksheet("reference").Range(Me.Name).Value2 <> Me.Range("F1").Value2 Then Worksheet("reference").Range(Me.Name).Value = Range("F1").Value2` Also, say if I have Worksheet A,B,C,D in the `reference` worksheet, do I have to create a name for all 4 cells containing the count for the 4 worksheets? How do I link the name to the code above? Thanks! – yswong Dec 08 '15 at 14:48
  • **1.** - [Value vs. Value2 vs. Text](http://stackoverflow.com/questions/17359835/what-is-the-difference-between-text-value-and-value2) **2.** - Yes, create 4 separate named ranges for where each value will be stored `(A2 - WorksheetA, B2 - WorksheetB ...)` **3.** - The `Me.Name` in the `Worksheet Level` code will do that since `Me` refers to the Worksheet in this case :) – Scott Holtzman Dec 08 '15 at 14:57
  • Hi Scott, I tried the code but an error came out : "Compile error : Sub or function not defined" As for the Me.Name part, I still require some clarification :- Me refers to the worksheet, but how would the code knows what is the defined name to be used? I'm sorry if I've asked a very basic question.. – yswong Dec 08 '15 at 15:26
  • To answer your 2nd question, first, `Me.Name` evaluates to the WorksheetName the code is placed it, so for WorksheetA, `Me.Name = `WorksheetA`. Then, in the expression `Worksheet("reference").Range(Me.Name)` it refers to the **Named Range** `WorksheetA` in the Reference Worksheet. As for the error, did you name your worksheet `reference` or something else? – Scott Holtzman Dec 08 '15 at 15:42
  • In this case, should my named range be exactly the same as the name of the worksheet? i.e. if my worksheet name is Pivot-ProductA, then I can't use PivotProductA as my named range? I named my worksheet which contains the count for other worksheets as `reference` – yswong Dec 08 '15 at 15:54
  • Yes, the named range needs to be **exactly** as the Worksheet is written. Otherwise, it won't work as written, or you'll have to define the name of the range in each `Worksheet_Calculate` event code for each sheet, which is more cumbersome, obviously. – Scott Holtzman Dec 08 '15 at 15:56
  • I know my logic is a bit sophisticated, but I think you'll appreciate it and learn from it once you fully grasp how it's working. – Scott Holtzman Dec 08 '15 at 16:03