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.
Related
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.
I am trying to call a macro whenever a cell got clicked. This applies to all the sheets in my workbook. But from the search I found that mostly it is defined for a specific worksheet. I want to make it for all sheets. Even in future if a new sheet got created, it should have the same functionality like other sheets. Can anybody say how to achieve this?
Note: I am using excel 2010. Answers related to other versions also OK for me.
You should customise the SheetSelectionChange event, making sure you confirm that you are triggered from a worksheet object
Option Explicit
Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
If TypeName(Sh) = "Worksheet" Then
Dim ws As Worksheet
Set ws = Sh
Dim r As Range
Set r = ws.Range(Target.Address)
With r.Interior
.Color = 65535
End With
End If
End Sub
This has been bothering me for a while. I want to access Worksheet by name instead of "how worksheet has been named in Excel". I would like also to include ThisWorkbook in the line, to eliminate possibility of having several Workbooks open with Worksheets with the same name. However I am getting an error:
I have named my Worksheet CalculationItem1. Why following does not work?
Sub TestTest()
ThisWorkbook.CalculationItem1.Range("A1:A2").Copy
End Sub
Following error appears:
Following works but there is a possibility of having same named Worksheet in another opened Workbook? Then errors can appear?
Sub TestTest()
CalculationItem1.Range("A1:A2").Copy
End Sub
Here is the name:
As long as the sheet is in the same Workbook as the code ("ThisWorkbook"), you can access the sheet via it's code name. That is true even if the Workbook is not active - it's like you have an object-variable with that name. So using CalculationItem1.Range("A1:A2").Copy will always refer to the sheet with the code name CalculationItem1 of ThisWorkbook.
If you want to access a worksheet of another workbook via code name, you have to iterate the sheets and look for the property CodeName (as FaneDuru shows in his answer).
Not sure that is possible to directly define the sheet using a specific CodeName.
But you can avoid confusing about such a sheet and another one of the active workbook, similarly named, using a function like this:
Function SetShByCN(strCodeName As String, wb As Workbook) As Worksheet
Dim sh As Worksheet
For Each sh In wb.Worksheets
If sh.CodeName = strCodeName Then Set SetShByCN = sh: Exit Function
Next
End Function
It must be adapted to send a warning in case of not existing such a sheet, or to return Nothing and this to be checked in the code calling the function. This is only made for testing reason...
It will do the job being called like in the next test code:
Sub testSetShByCN()
Dim wks As Worksheet
Set wks = SetShByCN("CalculationItem1", ThisWorkbook)
Debug.Print wks.Name
sh.Range("A1:A2").Copy
End Sub
In your first piece of code, you need to tell Excel that you are referring to a worksheet:
Sub TestTest()
ThisWorkbook.WorkSheets("CalculationItem1").Range("A1:A2").Copy
End Sub
Regards,
There's quite a bit of info about this to be found, but I haven't been able to get it to work for myself.
What I need to do is to have a global sub routine that catches hyperlink clicks for the whole document, not just the active sheet. Why? Because my workbook will have several links in several sheets, which are all "identical" except for their location (and contents of adjoining cells etc.).
This is easy to do with buttons - just connect all of them to the same macro - but I'm having problems doing the same with links.
This works, for one specific sheet:
In the Microsoft Excel Objects -> The worksheet in question:
Private Sub Worksheet_FollowHyperlink(ByVal Target As Hyperlink)
MsgBox "Link i Arket! " & Target.Range.Address
End Sub
When I click the link on the sheet in question, the VBA sub routine for the sheet catches the click and handles it.
The below code, I thought, "should" work for the whole document. That is, catch hyperlink clicks from any sheet in the worksbook. It does not (that is, nothing happens):
Sub Workbook_SheetFollowHyperlink(ByVal Sh As Object, ByVal Target As Hyperlink)
MsgBox "All sheets!"
End Sub
The links, in Excel, when hovering shows:
file://path/to/workbook.xlsm - click once to follow blablabla
What am I missing here?
You can reuse the same code for all worksheets by creating a class with a WithEvents worksheet object and storing the classes in a public collection.
Module1
Public objCollection As Collection
'Call on Workbook_Open
Sub CreateClasses()
Dim ws As Worksheet
Dim HyperlinksClass As cHyperlinks
'Create A New Instance Of The Collection
Set objCollection = New Collection
'Loop All Worksheets
For Each ws In Worksheets
'Create A New Class For This Worksheet
Set HyperlinksClass = New cHyperlinks
'Add The Worksheet To The Class
Set HyperlinksClass.obj = ws
'Add The Class To The Collection
objCollection.Add HyperlinksClass
Next ws
End Sub
Create a class called cHyperlinks
Private WithEvents pWS As Worksheet
Private Sub pWS_FollowHyperlink(ByVal Target As Hyperlink)
'Code to handle hyperlinks here
End Sub
Property Set obj(ByVal objWS As Worksheet)
Set pWS = objWS
End Property
I have been working on creating a module that has multiple subs and functions that all are applied to the same sheet. In my efforts and research to clean up my code I found that instead of declaring the "Dim" for each sub, I can declare it at the very top of the module by using either "Dim" or "Private".
Sub Sample()
Dim DataSheet As Range
'Only declared for this sub, doesn't apply to other subs
'on the other hand,
Private DataSheet As Range
Sub Sample()
'declares it for each sub in this module.
What I can't figure out is, is there a way to set the value or in this case the exact range that I want to assign to "DataSheet" that will apply to the entire module? Currently each of my subs contains,
Set DataSheet = ThisWorkbook.Sheets(1).Range("A3:FU5002")
which, since this range is constant and never changes, seems a little redundant.
Create a special sub to perform the initialization and run it first:
Dim DataSheet As Range
Sub RunMeFirst()
Set DataSheet = ThisWorkbook.Sheets(1).Range("A3:FU5002")
End Sub
add in a global variable in the ThisWorkbook module and use the workbook open event to set the value.
Public DataSheet As Range
Private Sub Workbook_Open()
Set DataSheet = ThisWorkbook.Sheets(1).Range("A3:FU5002")
End Sub