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
Related
I have a combobox (drop down list) that is populated with the names of all the sheets in the workbook. When I select one of them, it activates the selected sheet.
This was working until I copied it in another workbook and did some changes.
Here's the code I use to populate the combobox (which still works):
Sub fillAllCombos()
Dim ws As Worksheet
For Each ws In ThisWorkbook.Sheets
If ws.Name <> PVTSHEET Then Call fillCombobox(ws.Name)
Next
End Sub
Sub fillCombobox(wsName As String)
Dim ws As Worksheet
Dim oCmbBox As Object
Set oCmbBox = ThisWorkbook.Sheets(wsName).Shapes("cmbSheet")
oCmbBox.ControlFormat.RemoveAllItems
For Each ws In ThisWorkbook.Sheets
oCmbBox.ControlFormat.AddItem ws.Name
Next
End Sub
Here's how I capture the event:
Sub CmbSheet_Change()
Dim oCmbBox As Object
Set oCmbBox = ActiveSheet.Shapes("cmbSheet")
With oCmbBox.ControlFormat
If .Value <> "" Then
ActiveWorkbook.Sheets(.Value).Activate
.ListIndex = 0
End If
End With
End Sub
The event-capture macros are in the sheet where the combobox is located.
In my search for answers I have also tried CmbSheet_Click()but same result.
I've named the combobox as in the image:
**Edit: Application.EnableEvents = True
It looks as if you have a Form Control Combobox. You can assign any parameter-less public sub to it, but you have to do this assignment manually. Just right-click on the control and select "Assign Macro...".
It's different for an ActiveX Control where the assignment is done via its name.
I can't seem to figure out why my UserForm code fails at the very end of the procedure. The end result is perfect. Here is what I'm trying to accomplish...
1) Using the active workbook, the procedure identifies the sheet names in the workbook and displays them in the Listbox, which is located on the userform.
UserForm Pic
2) Once the sheet is selected, the user will click the "Select Sheet" CommandButton
3) The CommanButton activates the sheet, renames the sheet to "LegacyBillHist"
4) The form closes, and cell A2 is selected
I get the Run-time error 91 message on the very last line.
I've tried a few different approaches to resolve the issue, but I can't seem to figure this one out using the info in StackOverflow and other sites. Any help would be greatly appreciated.
Here is the code..
Option Explicit
Private Sub CommandButton1_Click()
Worksheets(ListBox1.Value).Activate
ActiveSheet.Name = "LegacyBillHist"
Unload BillSelect
End Sub
Public Sub UserForm_Initialize()
Dim wb As Workbook
Dim sh As Worksheet
Set wb = ActiveWorkbook
For Each sh In wb.Sheets
ListBox1.AddItem sh.Name
Next sh
Load BillSelect
BillSelect.Show
sh.Range("A2").Select
End Sub
Mathieu
Great info. I ended up changing my approach significantly. However, I have a different problem that I haven't been able to figure out. Here is the Code...I got it from the article you referenced...
Option Explicit
Private cancelled As Boolean
Public Property Get IsCancelled() As Boolean
IsCancelled = cancelled
End Property
Private Sub CommandButton2_Click()
OnCancel
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = VbQueryClose.vbFormControlMenu Then
Cancel = True
OnCancel
End If
End Sub
Private Sub OnCancel()
cancelled = True
Hide
End Sub
Private Sub CommandButton1_Click()
Dim wb As Workbook
Dim ws As Worksheet
Worksheets(ListBox1.Value).Activate
ActiveSheet.Name = "LegacyBillHist"
Set wb = ActiveWorkbook
Set ws = wb.Sheets("LegacyBillHist")
ws.Activate
ws.Range("A1").Select
UserForm1.Hide
End Sub
Private Sub UserForm_Initialize()
Dim wb As Workbook
Dim lastSheet As Worksheet
Dim sh As Worksheet
Set wb = ActiveWorkbook
For Each sh In wb.Worksheets
ListBox1.AddItem sh.Name
Set lastSheet = sh
Next
UserForm1.Show
End Sub
Everything works great up to the End Sub under the CommandButton1 routine. Instead of hiding UserForm1, it reopens the form.
I tried incorporation the code you provided, but it keeps cycling through until I get the Out of stack space error 28. Perhaps I'm not putting it in the correct sequence.
Private Sub UserForm_Initialize()
Dim wb As Workbook
Dim lastSheet As Worksheet
Dim sh As Worksheet
Set wb = ActiveWorkbook
For Each sh In wb.Worksheets
ListBox1.AddItem sh.Name
Set lastSheet = sh
Next
With New UserForm1'<~ forms are objects too; avoid using their global state
.Show
End With
End Sub
Appreciate all the help.
For Each sh In wb.Sheets
ListBox1.AddItem sh.Name
Next sh
The object sh only ever makes sense inside the For...Next loop.
In theory, as per language specifications, sh should indeed still hold a reference to the last item in the iterated collection:
When the <for-each-statement> has finished executing, the value of <bound-variable-expression> is the data value of the last element in <collection>.
https://learn.microsoft.com/en-us/openspecs/microsoft_general_purpose_programming_languages/MS-VBAL/b132463a-fd25-4143-8fc7-a443930e0651
However it appears when VBA was implemented, that bullet point wasn't: a For Each variable that is Nothing before the loop, will still be Nothing after the loop - and any member call made against Nothing will always raise error 91.
To be honest, I think it's better that way, too: it forces your code to be more explicit:
Dim lastSheet As Worksheet
Dim sh As Worksheet
For Each sh In wb.Worksheets '<~ note: 'Sheets' collection can have charts, not just worksheets
ListBox1.AddItem sh.Name
Set lastSheet = sh
Next
With New BillSelect '<~ forms are objects too; avoid using their global state
.Show
End With
lastSheet.Activate
lastSheet.Range("A1").Select
Note: Newing-up the BillSelect can surface hidden bugs in your form code, read this article for more info.
The Initialize handler shouldn't be doing this work, especially if you don't New up the form and use its default instance: you do not control when that default instance gets created, VBA does.
Given this code:
UserForm1.Show
If no code ever referred to UserForm1 before, then the Initialize handler runs at UserForm1, before the reference is returned to the caller and the .Show call runs; if the form's default instance isn't destroyed, then the next time the form is shown, the initialize handler will not run again, because the instance is already initialized.
Consider implementing your logic in the Activate handler instead, which will make the logic run after the .Show call is entered, and every time the form is activated (and since that's a modal dialog, that means every time the form is actually shown).
I like to run code from an Excel add-in to the active workbook (xlsx). The code should apply some formatting to the active cell of the active workbook, when activated in the menu.
How can I achieve this?
Normally you can realize this by using the worksheet_change event in the active workbook, but this requires:
a macro-enabled workbook, AND
having the code in this particular workbook.
I like to have this applied via an add-in, independent of the workbook and thus without the need to insert code in this workbook and to make this a xlsm workbook first.
You can catch WorksheetChange events from one worksheet in another (class) module, but in this case you probably rather use Application.SheetChange as above (code in ThisWorkbook module in addin):
'Create an object to catch events
Dim WithEvents ExcelApp As Excel.Application
'Assign object when addin opens
Private Sub Workbook_Open()
Set ExcelApp = Application
End Sub
'Handle sheet changes in all workbooks
Private Sub ExcelApp_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Dim rng As Range
'Do some validation...
If Sh.Parent.Name = "Book1" And Sh.Name = "Sheet1" Then
'Ensure correct range
'Note: changes may occur in many cells at a time (delete, paste, ...)
Set rng = Intersect(Target, Workbooks("Book1").Worksheets("Sheet1").Range("A1:B2"))
If Not rng Is Nothing Then
rng.Interior.ColorIndex = vbRed 'Example
End If
End If
End Sub
Thanks both, seems I have new insights here I can test and try out.
I am working on a userform where a combobox gives user the names of every excel workbook that are already opened at that moment.
I want to put the names of all worksheets taking place in the workbook that is selected in the combobox on a listbox and I want it to be dynamic -that is, as the user selects another workbook from the combobox, the names (of the worksheets) appearing in the listbox should be automatically changing.
However, I can't figure out how I can access to the inventory of the combobox and make the desired additions. Any help/comment is appreciated. Thank you.
You can use the following functions
This one is for the UserForm, when it initializes it will fill in the combobox with all open sheets
Private Sub UserForm_Initialize()
Dim i As Long
For i = 1 To Workbooks.Count
ComboBox1.AddItem (Workbooks(i).Name)
Next
End Sub
This is about the Combobox itself, whenever you select some workbook from the combobox this function will be called and it is filling the list box with the sheets of that workbook.
Private Sub ComboBox1_Change()
Dim selected_wb As Workbook
Set selected_wb = Workbooks(ComboBox1.Text)
ListBox1.Clear
For Each ws In selected_wb.Worksheets
ListBox1.AddItem ws.Name
Next ws
End Sub
This function is called when you click the sheet name from the listbox, it will select the sheet and will close the user form
Private Sub ListBox1_Click()
Dim selected_wb As Workbook
Dim selected_ws As Worksheet
Set selected_wb = Workbooks(ComboBox1.Text)
Set selected_ws = selected_wb.Sheets(ListBox1.Text)
selected_ws.Activate
UserForm1.Hide
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.