This question already has an answer here:
Excel vba add code to sheet module programmatically
(1 answer)
Closed last year.
I have code that creates a worksheet and the worksheet contains a table.
I then programmatically add an advanced filter to the table in the created worksheet.
This works fine, but the advanced filter does not refresh on change of the worksheet: A known issue.
The standard solution is to put code behind the worksheet_change event so the filter is refreshed when the worksheet changes. This also works fine when I add this code manually.
But when I create a sheet I want to automatically create the code for the new sheets worksheet_change event. I don't know how to programmatically specify code for my new sheet in the worksheet_change event.
Is this possible? Or is there another way to accomplish this?
Maybe look at using this instead. It'll fire for all sheet changes and allows your code to work centrally ...
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
End Sub
You can create a class module with the event and hook that to your worksheets. So you can add a hidden sheet with all workbook names that you want to add that event to.
So if you programmatically add a new sheet you just need to add its name to that list.
So first add a sheet named MyClassSheets (you can make it hidden so no user sees it). And add some sheet names there:
So this means we want to run your worksheet_change event in Sheet2 and Sheet3 only.
Then we add a class module MyWsClass:
Option Explicit
Public WithEvents Ws As Worksheet
Private Sub Ws_Change(ByVal Target As Range)
MsgBox Target.Address(False, False) & " changed."
End Sub
This is the event we want to run. It just shows a message box which cell was changed. You need to adjust that to your wishes.
Finally we need to hook those events to the worksheets. So we add a normal module (non-class module) HookWsEvents:
Option Explicit
Dim MyWorksheets() As New MyWsClass
Public Sub HookEvents()
' get list of worksheets we want to add the class
With ThisWorkbook.Worksheets("MyClassSheets")
Dim MyClassSheets() As Variant
MyClassSheets = .Range("A1", .Cells(.Rows.Count, "A").End(xlUp)).Value
End With
ReDim MyWorksheets(UBound(MyClassSheets) - 1)
Dim i As Long
' add class to those worksheets
Dim Ws As Variant
For Each Ws In MyClassSheets
Set MyWorksheets(i).Ws = ThisWorkbook.Worksheets(CStr(Ws))
i = i + 1
Next Ws
End Sub
So now we just need to make sure that the procedure HookEvents is called everytime the workbook opens. So we add the following into ThisWorkbook:
Option Explicit
Private Sub Workbook_Open()
HookEvents
End Sub
After running HookEvents the event runs in every sheet that is listed in MyClassSheets.
If you now add a new worksheet that you want to add the event to, you just need to add it to the list in MyClassSheets and run HookEvents again. Therefore we can add a small helper procedure to our HookWsEvents module:
Public Sub AddWsToList(Ws As Worksheet)
With ThisWorkbook.Worksheets("MyClassSheets")
' add the worksheet name of Ws to the list in MyClassSheets
.Cells(.Rows.Count, "A").End(xlUp).Offset(RowOffset:=1).Value = Ws.Name
End With
End Sub
So now we can use the following code
' add a new worksheet as last worksheet
Dim NewWs As Worksheet
Set NewWs = ThisWorkbook.Worksheets.Add(After:=ThisWorkbook.Worksheets(ThisWorkbook.Worksheets.Count))
' give it a name
NewWs.Name = "Sheet5"
' add it to the list of MyClassSheets
AddWsToList NewWs
' hook events to all sheets
HookEvents
to add a new worksheet and add the event to it.
You may want to add some error checking if a worksheet in the list of MyClassSheets does not exist anymore (was deleted) so your code does not thorw an unhandled exception in this case. I left this out to have the above explanation more clear.
If all your sheets have someting common in their name you don't need to maintain that MyClassSheets list. So for example if all the sheets you want to add the event to start with XYZ_ you just need to loop over all sheets in the HookEvents procedure and check if they start with XYZ_ to add them to the MyWorksheets array then. Resizing that array may be a bit more complicated then.
Related
I'm totally new to Excel_vba, I'm trying to make kind of simple macros in excel.
I'm intending to make a macro: If active sheet is the first sheet in the workbook, move to last activated sheet. Else if, move to the first sheet in the workbook.
I googled and I added the code below to 'thisworkbook' module in addin (.xlam).
Private Sub Workbook_SheetdeActivate(ByVal Sh As Object)
Set LstSht = Sh
End Sub
In next, I create general module in addin(.xlam), added the code below.
Global LstSht As Worksheet
Sub GoToLast()
LstSht.Activate
End Sub
Private Sub Sht_tab()
If ActiveSheet.Index = 1 Then
Call GoToLast
ElseIf ActiveSheet.Index <> 1 Then
ActiveWorkbook.Sheets(1).Activate
End If
End Sub
The problem is, even though I move to any other sheet in workbook, sheetdeactivate event is not working, so LstSht object is not defined.
The event: sheetdeactivate only works in specific workbook? I want to assign a shorcut to this macro and use it in other workbooks, but I can't make it run.
Thank you for all in advance.
Let's say, there is a report that has already been created in a sheet in a work book using macros. I want to create another report within the same workbook in another sheet(tab) with the exact same code in VBA which was used to create the report that has already been created. How do i copy and make those codes work in a different sheet to create a similar report?
Any code in the Worksheet itself should use Me to refer to itself - this way, it will work when copied/duplicated
Any other code should take the Worksheet to act on as a Argument, or as Module Level Object:
Option Explicit
Sub CreateReport(TargetSheet As Worksheet)
TargetSheet.Cells(1,1).Value = "Hello"
End Sub
or
Option Explicit
Public TargetSheet AS Worksheet
Sub CreateReport()
If TargetSheet Is Nothing Then Exit Sub 'In case the object has not been set
TargetSheet.Cells(1,1).Value = "Hello"
End Sub
Then you can duplicate a "master" template sheet, and run the macros to target it
a lot of users in our network use an excel-workbook (.xlsm [office 2010]) created from a template.
Now, there are some important changes I've to do in the template and I want all the users to update their workbook but i'd like to avoid to contact all of them.
So, my Idea is to make an auto-update (copying the contents of their workbooks into new created workbooks and delete the former version).
Unfortunately there are no update-macros in the existing workbooks but they reference to a macro in another workbook.
Each time they open their workbooks the data connections become refreshed automatically.
Can I use this refreshing event to trigger a macro in the (data-source) excel-file (maybe by creating a WithEvents-class module)?
You can do something along these lines, where the user opens a workbook, but its job is to control the version. You can change this to have the code modify sheets etc.
The text file, correct, contains ver9, the workbook contains ver8 in the ver_cont worksheet.
Function get_version() As String
Open "c:\workspace\test_ver.txt" For Input As #1
Input #1, get_version
Close #1
End Function
Function check_version()
If get_version = Worksheets("Ver_cont").Range("a1") Then
' Open the workbook here
Else
' Copy the workbook
' Then open it
End If
End Function
You can try this. It uses withevents and runs when the data is updated.
First, you need to create a class name "clsQueryTable" and put this code in it
Option Explicit
Public WithEvents QTQueryTable As Excel.QueryTable
Private Sub QTQueryTable_BeforeRefresh(blnCancel As Boolean)
'Set blnCancel to true to stop the refresh
Debug.Print blnCancel
End Sub
Private Sub QTQueryTable_AfterRefresh(ByVal blnSuccess As Boolean)
'blnSuccess can be used to check for refresh success.
' I would put your update code here!
Debug.Print blnSuccess
End Sub
Then, you can put this code in your workbook_open event on ThisWorkbook
Option Explicit
Dim colQueryTables As Collection
Private Sub Workbook_Open()
Dim shtMySheet As Worksheet
Dim clsQT As clsQueryTable
Dim qtMyQuery As QueryTable
Dim loMyList As ListObject
Dim conn As WorkbookConnection
Set colQueryTables = New Collection
For Each shtMySheet In ThisWorkbook.Worksheets
For Each loMyList In shtMySheet.ListObjects
Set clsQT = New clsQueryTable
Set clsQT.QTQueryTable = loMyList.QueryTable
colQueryTables.Add clsQT
Next loMyList
Next shtMySheet
For Each conn In Connections
conn.Refresh
Next
End Sub
I have to create an Excel sheet automatically with vba.
Some cells need an Onchange Event Listener and I wanted to know if there is a way to create this Event Listener automatically by calling a macro instead of writing it down everytime in every sheet code ?
Thank you
I'm going to answer this question because I think it might have some relevance to quite a few other people too, so the code below should get you started in the right direction.
However, there is an expectation on this site that you try to help yourself at least to the same extent as we try to help you. The commenters have mentioned the VBA Object Model, Application Events and AddIns. It shouldn't be beyond the wit of most people to then research these key words (say with a google search). It's not really acceptable simply to say you "dunno where to put the code or what to do", and, in all honesty, doesn't particularly motivate people to help you - put another way, you're pretty lucky to get an answer with that post and comment.
I don't want to get into a huge comment exchange on exactly how to code your specific case. The code here is an example and I would expect you then to research it further. So here goes ...
Insert a class module (research that if you don't know how) and name it - I've called mine cApp. This will enable you to access the Application object and capture its events, like so:
Option Explicit
Private WithEvents mApp As Application
Private mSheetList As Collection
Private Sub Class_Initialize()
Dim ws As Worksheet
'Create instance of the sheet collection
Set mSheetList = New Collection
'If you wanted to add any existing sheets to be checked for changes,
'then you'd do it here.
'Just for an example, I'm using any existing sheets whose name contains "LoP".
For Each ws In ThisWorkbook.Worksheets
If InStr(ws.Name, "LoP") > 0 Then
mSheetList.Add ws
End If
Next
'Create instance of Application
Set mApp = Application
End Sub
Private Sub mApp_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Dim ws As Worksheet
'Test if the changed sheet is in our list.
'Check if the Sh object is a worksheet.
If TypeOf Sh Is Worksheet Then
'Loop through out list of sheets and see if the Sh object is in the list.
For Each ws In mSheetList
If Sh Is ws Then
'Check if the changed range is in the desired range of your sheet.
'In this example, we'll say it has to been in the range "A1:B2".
If Not Intersect(Target, ws.Range("A1:B2")) Is Nothing Then
MsgBox ws.Name & "!" & Target.Address(False, False) & " has changed."
End If
Exit For
End If
Next
End If
End Sub
Private Sub mApp_WorkbookNewSheet(ByVal Wb As Workbook, ByVal Sh As Object)
'A new sheet has been created so add it to our sheet list.
If Wb Is ThisWorkbook Then
If TypeOf Sh Is Worksheet Then
mSheetList.Add Sh
End If
End If
End Sub
You then want to create an instance of this class. I've done it in a standard Module:
Option Explicit
Private oApp As cApp
Public Sub RunMe()
'Create instance of your app class
Set oApp = New cApp
End Sub
You'd then call the RunMe routine somewhere within your code. You might choose to do this in your Workbook_Open() event, but it could be anywhere of your choosing.
I've commented the code pretty heavily so you can see what it's doing and you can always research each of the keywords if you're not sure what they're doing.
I have a userform which opens one of a list of worksheets. I want to create a macro that recognises the last sheet opened by the userform and then runs data validation based on data types in the sheet.
How can I reference the sheet opened so that it can be called later my the validation macro?
All help gratefully accepted!
You need to "remember" it in a Public variable.
In a Standard Module, near the top of that module, include:
Public LastSheet As Worksheet
and in the UserForm, code like:
Sub WithinUserForm()
Dim x As String
x = Application.InputBox(Prompt:="pick a worksheet", Type:=2)
Sheets(x).Select
Set LastSheet = ActiveSheet
End Sub
Finally within the DV macro:
Sub MacroForDV()
LastSheet.Select
End Sub