I have a spreadsheet that receives a large amount of data from an ERP export. A macro splits the data into separate tabs (worksheets) based on values in a certain column (SalesPerson).
Within each worksheet, I want a macro that adds functionality to that sheet, i.e. the ability to double click on a row and delete it. My code works.....
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
Dim resp As VbMsgBoxResult
If Not Intersect(Target, Columns("A")) Is Nothing Then
Cancel = True
resp = MsgBox(Prompt:="Delete data from row " & Target.Row & "?", Buttons:=vbYesNoCancel)
If resp = vbYes Then Range(Replace("I#:M#", "#", Target.Row)).ClearContents
End If
End Sub
....the question I have:
this macro has to reside in the worksheet level, not the module level. Is there a way to automatically paste this script into each worksheet that is created?
You should be able to use the Workbook event instead. It gives you a sheet parameter you can use to identify which sheet the event was triggered from.
Private Sub Workbook_SheetBeforeDoubleClick(ByVal Sh As Object, ByVal Target As Range, Cancel As Boolean)
End Sub
Naturally, this doesn't reside on the Sheet object, it can be found on the ThisWorkbook object in your VBA editor.
Related
Very novice at VBA so please bear with me.
Im trying to delete a table if a cell (B2) in another sheet changes.
Currently I have:
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Worksheets("sheet2").Range("B2")) Is Nothing Then
Range("B21:D30").ClearContents
End If
End Sub
I've tried many variations, indirect, and different syntax but none work.
Update:
I should also mention that B2 on sheet2 will be changing based on the user selecting a group of radio buttons which are linked to sheet2!B2. I.e. I am not directly changing the value of B2 from sheet2. In fact sheet2 will be eventually hidden.
To trap the events in the Sheet2 of the hidden workbook (Let's call it Book2), you need to create a class to manage the _SheetChange event capture.
Let's say you want to capture the events in Book2.Sheet2 from Book1. Do this
1. Insert a class module (Let's call it Class1) and paste this code there
Code
Private WithEvents hiddenWb As Workbook
Public Property Set Workbook(wb As Workbook)
Set hiddenWb = wb
End Property
Public Property Get Workbook() As Workbook
Set Workbook = hiddenWb
End Property
Private Sub hiddenWb_SheetChange(ByVal Sh As Object, ByVal Target As Range)
If Sh.Name = "Sheet2" Then
If Not Intersect(Target, Sh.Range("B2")) Is Nothing Then
MsgBox "Range B2 was chnaged"
End If
End If
End Sub
2. In a module paste this code
Code
Option Explicit
Dim cWb As New Class1
Sub Sample()
'~~> Set a reference to the hidden workbook
Set cWb.Workbook = Workbooks("Book2")
'~~> Change the value of the cell B2
cWb.Workbook.Sheets("Sheet2").Cells(2, 2).Value = "Blah Blah"
End Sub
Screenshots
Testing
Run the procedure Sample() from Book1
Intresting Read
Events And Event Procedures In VBA by Charles H. Pearson
This has to be written in the Sheet2 module:
Private Sub Worksheet_Change(ByVal Target As Range)
With Me
If Not Intersect(Target, .Range("B2")) Is Nothing Then
.Parent.Worksheets("Sheet1").Range("B21:D30").ClearContents
End If
End with
End Sub
Change "Sheet1" to fit your worksheet name.
I created a worksheet_calculate event macro to return a message box (CHANGE DETECTED!) whenever the value in the cells W4656:W4657 change. These values are referenced from another sheet in the same workbook.
My problem is the worksheet_calculate event is fired whenever data is entered anywhere in the workbook.
Could this be modified such that the worksheet_calculate event is fired only when data in a specific cell (a cell in a different sheet) is changed.
Private Sub Worksheet_Calculate()
Dim Xrg As Range
Set Xrg = Range("W4656:W4657")
If Not Intersect(Xrg, Range("W4656:W4657")) Is Nothing Then
MsgBox ("CHANGE DETECTED!!")
ActiveWorkbook.Save
End If
End Sub
Well, if we examine these lines of your code
Dim Xrg As Range
Set Xrg = Range("W4656:W4657")
If Not Intersect(Xrg, Range("W4656:W4657")) Is Nothing Then
Since we set Xrg, then immediately use it, we can rewrite that as
If Not Intersect(Range("W4656:W4657"), Range("W4656:W4657")) Is Nothing Then
which will always be true. So, every time the worksheet Calculates, it will say "CHANGE DETECTED!"
Ideally, you want to store the values in those Cells somewhere, and then just run a comparison between the cells and the stored values. Using Worksheet Variables, you could get the following: (You could also store the values in hidden worksheet as an alternative)
Option Explicit 'This line should almost ALWAYS be at the start of your code modules
Private StoredW4656 As Variant 'Worksheet Variable 1
Private StoredW4657 As Variant 'Worksheet Variable 2
Private Sub Worksheet_Calculate()
On Error GoTo SaveVars 'In case the Variables are "dropped"
'If the values haven't changed, do nothing
If (Me.Range("W4656").Value = StoredW4656) And _
(Me.Range("W4657").Value = StoredW4657) Then Exit Sub
MsgBox "CHANGE DETECTED!", vbInformation
SaveVars:
StoredW4656 = Me.Range("W4656").Value
StoredW4657 = Me.Range("W4657").Value
End Sub
So I've managed to find a solution (work around?) to my problem.
I ended up using a macro to check if the the number in Sheet 38, Cell W4656 which was referenced from Sheet 5, Cell J2, has changed. If yes, fire a macro. If not, do nothing.
I've realized that with the code below, worksheet_calculate event is fired only when there is change in Sheet 5, Cell J2 or Sheet 38, Cell W4656 which is what I want.
Private Sub Worksheet_Calculate()
Static OldVal As Variant
If Range("w6").Value <> 24 Then
MsgBox ("XX")
'Call Macro
End If
End Sub
I've updated my code and made it cleaner, and shamelessly stole some of
Chronocidal's approach (my original code required the workbook to be closed and opened to work). So here is what Sheet5 looks like in my example:
And here is Sheet38. In my example I simply setup formulas in Sheet38!W4656:W4657 to equal Sheet5!$J$2 ... so when Sheet5!$J$2 changes so does Sheet38!W4656:W4657 which will trigger the code.
And copy this code into ThisWorkbook ...
Option Explicit
Dim vCheck1 As Variant
Dim vCheck2 As Variant
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
If vCheck1 <> Sheet38.Range("W4656") Or vCheck2 <> Sheet38.Range("W4657") Then
MsgBox ("CHANGE DETECTED!!")
Application.DisplayAlerts = False
ActiveWorkbook.Save
Application.DisplayAlerts = True
vCheck1 = Sheet38.Range("W4656")
vCheck2 = Sheet38.Range("W4657")
End If
End Sub
Like this ...
Sheet 1 of my workbook contains (besides other data) a list of the other worksheets in column A. I wish to be able to click on any cell in column A5:A50 and go to the appropriate worksheet listed in that cell. My Sheet1 code is:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Not Intersect(Target, Me.Range("A5:A50")) Is Nothing Then SelectWorksheet
End Sub
and Module2 is:
Sub SelectWorksheet()
Dim strName As String
strName = Sheet1.Range("Target").Text (Error occurrs here: "Method 'Range' of object 'Worksheet' failed")
Sheets(strName).Select
End Sub
How do I get this to work as I expect? I know I could just click on the appropriate worksheet tab but I'm trying to learn how to code in VBA. Thanks. By the way, how do I get my post to show the code as typed in the question box?
Like this. You probably want to use the _SelectionChange event instead of the _Change event. Or you may find it necessary to use both events to trigger it. In any case here is how you pass the variable to another subroutine/module:
Sub Worksheet_SelectionChange(byVal Target as Range)
'Some code...
'
Call OtherMacro(Target)
'
End Sub
And then in your other macro, declare a range variable as required argument, like so:
Sub SelectWorksheet(rng as Range)
'
Dim strName as String
' at this point you can work with the "rng" variable, because it's been received from the other subroutine
strName = rng.Value
Sheets(strName).Activate
'
End Sub
You would need to add additional test to make sure user has not selected multiple cells, etc., but this should get you started.
Why not pass the sheet name from the cell to the sub?
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Not Intersect(Target, Me.Range("A5:A50")) Is Nothing _
And Target.Cells.Count = 1 Then SelectWorksheet (Target.Value)
End Sub
Sub SelectWorksheet(strName As String)
Sheets(strName).Select
End Sub
I've also done a check to make sure that only one cell is in the selection.
I have a macro which changes the tab colors. If there is any value in the sheet then the tab changes into green. If there is nothing then it changes into red. I combined this macro from the ready ones found on the internet. Currently I put this to ThisWorkbook but in this instance it applies to every sheet in the workbook and I wanted only those 2 sheets specified by me ("Our Data" and "Test"). I split this macro to sheets located above ThisWorkbook but then it doesn't work. Can somebody help me to amend it?
Private Sub Workbook_SheetChange(ByVal Test As Object, ByVal Target As Range)
If Cells.Find("*") Is Nothing Then
Test.Tab.ColorIndex = 3
Else
Test.Tab.ColorIndex = 10
End If
End Sub
Private Sub Workbook_SheetChange2(ByVal Test As Object, ByVal Target As Range)
If Cells.Find("*") Is Nothing Then
Our Data.Tab.ColorIndex = 3
Else
Our Data.Tab.ColorIndex = 10
End If
End Sub
You can't split it this way... Delete the second one and improve first as presented below:
Private Sub Workbook_SheetChange(ByVal Test As Object, ByVal Target As Range)
If Test.Name = "Our Data" Or Test.Name = "Test" Then
If Cells.Find("*") Is Nothing Then
Test.Tab.ColorIndex = 3
Else
Test.Tab.ColorIndex = 10
End If
End Sub
Keep it where you have it now (in ThisWorkbook module)
EDIT- additional information for all who will want to use it. Presented idea is very inefficient. The event will fire each time when any changes would be made in any of cell in any of sheet. Please consider using other events. I would suggest to use SheetActivate of SheetDeactivate.
I have an Excel workbook in which I have tabs representing dates along with sum in each tab. Although I can take the sum of all these in the final sheet, I want a formula/macro to get the sum in the total named sheet, when a new spreadsheet is being added.
Note:- the cell in all would remain the same (E56)
I do not understand what you are attempting. Until the user has placed information in the new sheet that results in a value in E56, I see little point to adding the value of NewSheet!E56 to the total sheet.
However I suspect you need to use events. Below are a number of event routines which must be placed in the Microsoft Excel Object ThisWorkbook for the workbook. These just output to the Immediate window so you can see when they are fired. Note: several can be fired for one user event. For example, creating a new worksheet, triggers: "Create for new sheet", "Deactivate for old sheet" and "Activate for new sheet".
Do not forget to include
Application.EnableEvents = False
Application.EnableEvents = True
around any statement within one of these routine that will trigger an event.
Perhaps you need to use SheetDeactivate. When the users leaves a sheet, check for a value in E56. If present, check for its inclusion in the totals sheet. Have a play. Do what your users do. Add to these routines to investigate further. Good luck.
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
Debug.Print "Workbook_SheetActivate " & Sh.Name
End Sub
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Call MsgBox("Workbook_BeforeClose", vbOKOnly)
End Sub
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Source As Range)
Debug.Print "Workbook_SheetChange " & Sh.Name & " " & Source.Address
End Sub
Private Sub Workbook_SheetDeactivate(ByVal Sh As Object)
Debug.Print "Workbook_SheetDeactivate " & Sh.Name
End Sub
Private Sub Workbook_NewSheet(ByVal Sh As Object)
Debug.Print "Workbook_NewSheet " & Sh.Name
End Sub
Sub Workbook_Open()
Debug.Print "Workbook_Open"
End Sub
Extra section in response to clarification of requirement
The code below recalculates the grand total of cell E56 for all worksheets except TOTAL and stores the result in worksheet TOTAL every time the workbook is opened and every time the user changes the current worksheet.
It is difficult to get consistent timings with Excel but according to my experimentation you would need between 500 and 1,000 worksheets before the user would notice a delay switching worksheets because of this recalculation.
I am not sure if you know how to install this code so here are brief instructions. Ask if they are too brief.
Open the relevant workbook.
Click Alt+F11. The VBA editor displays. Down the left you should see the Project Explorer. Click Ctrl+R if you do not. The Project Explorer display will look something like:
.
VBAProject (Xxxxxxxx.xls)
Microsoft Excel Objects
Sheet1 (Xxxxxxxxx)
Sheet10 (Xxxxxxxxx)
Sheet11 (Xxxxxxx)
:
ThisWorkbook
Click ThisWorkbook. The top right of the screen with turn white.
Copy the code below into that white area.
No further action is required. The macros Workbook_Open() and Workbook_SheetDeactivate() execute automatically when appropriate.
Good luck.
Option Explicit
Sub CalcAndSaveGrandTotal()
Dim InxWksht As Long
Dim TotalGrand As Double
TotalGrand = 0#
For InxWksht = 1 To Worksheets.Count
If Not UCase(Worksheets(InxWksht).Name) = "TOTAL" Then
' This worksheet is not the totals worksheet
If IsNumeric(Worksheets(InxWksht).Range("E56").Value) Then '###
TotalGrand = TotalGrand + Worksheets(InxWksht).Range("E56").Value
End If '###
End If
Next
'Write grand total to worksheet TOTAL
' ##### Change the address of the destination cell as required
Worksheets("TOTAL").Range("D6").Value = TotalGrand
End Sub
Sub Workbook_Open()
' The workbook has just been opened.
Call CalcAndSaveGrandTotal
End Sub
Private Sub Workbook_SheetDeactivate(ByVal Sh As Object)
' The user has selected a new worksheet or has created a new worksheet.
Call CalcAndSaveGrandTotal
End Sub
I know this is the programming forum, but this particular "need" seems to be solvable without all the plumbing.
I like the old hidden FIRST and LAST sheets trick.
Create a sheet called First
Create a sheet called Last
Place your current data sheets between these two sheets.
Hide the sheets First and Last
Now you can use 3D formulas to sum cells from all these sheets, like so:
=SUM(First:Last!E56)
Now just add sheets to your workbook AFTER the last visible data sheet and Excel will still slip it in ahead of the hidden LAST sheet, so your formula just expands itself that way