This question already has answers here:
Excel VBA - QueryTable AfterRefresh function not being called after Refresh completes
(2 answers)
Closed 5 years ago.
I need to create an event to apply certain code after data connections (text files) have refreshed. How do i control when the data is refreshed.
Turn off background refresh on your connections before you do anything else. Then use ActiveWorkbook.RefreshAll any code placed after will not execute till after the refresh, as long as background refresh is off for all connections.
ActiveWorkbook.RefreshAll
other vba code here
How do i control when the data is refreshed.
Open the Connections menu, and then select the connection, and view/edit its Properties:
I need to create an event to apply certain code after data connections (text files) have refreshed.
I interpreted this literally, as you need an event. Fortunately, it's possible to do this. It's not a built-in event like Worksheet_Change but it's still something that can be done with VBA.
If you create a class object then a QueryTable object can be configured WithEvents, and there are two events which you can trap: AfterRefresh and BeforeRefresh. Sounds like you need the AfterRefresh event.
How to add Event Handler for QueryTable
Create a Class module named clsEvents_QueryTable (or you can name it something else, just be consistent in the rest of the code). Put this code, which will allow you to establish an event-handler for a QueryTable object, and the two events' procedures.
Option Explicit
Public WithEvents cQT As Excel.QueryTable
Private Sub Class_Initialize()
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets(1) '## Modify as needed
Set cQT = ws.QueryTables.Item(1) '## Modify as needed
End Sub
Private Sub cQT_AfterRefresh(ByVal Success As Boolean)
'###
' Code placed in, or called *from* this procedrure will run AFTER refresh
MsgBox Me.cQT.Name & " After refreshing..."
End Sub
Private Sub cQT_BeforeRefresh(Cancel As Boolean)
'###
' Code placed in, or called *from* this procedrure will run BEFORE refresh
MsgBox Me.cQT.Name & " Before refreshing..."
End Sub
Put this in the top of a standard module:
Public QT As clsEvents_QueryTable
In your ThisWorkbook module, do this:
Option Explicit
Private Sub Workbook_Open()
If QT Is Nothing Then
Set QT = New clsEvents_QueryTable
End If
End Sub
(You could do that in some other module, but this is just an example).
Now the table has the two event-handlers, and any time the QueryTable is refreshed, it will automatically invoke the code which is included or called from the event handler(s).
You can extend this to handle multiple QueryTables with some modification (using a collection of object, instead, etc.).
Related
I made my worksheet a template and want to know how I can force it to instead of the normal blank page when I press the new sheet button. This way I don't have to keep copying the same sheet every time I need to add another supplier.
Thank you in advance.
Consider implementing a Workbook.NewSheet event. As with all event functions, this needs to be defined in a module.
Private Sub Workbook_NewSheet(ByVal Sh as Object)
' apply the desired elements from the template to Sh
End Sub
I have a master file that opens a user form automatically when started, lets users select options and then saves the file as a new version modified based on these selections. The code that initiates this action is assigned to ThisWorkbook Object. I would like this version not to contain the macro that opens the form at the beginning. Is there a way to erase this part of the code when the new version is being saved?
The easiest way to do this is to perform a check before running any code. This won't strip out the code, it will just stop it from running.
The check could be the name of the workbook or a value in a cell
eg
Private Sub Workbook_Open()
If ThisWorkbook.Name <> "MyMacroWorkbook.xlsx" Then Exit Sub
'current code here
End Sub
or
Private Sub Workbook_Open()
If ThisWorkbook.Worksheets("HiddenWorksheet").Cells(1, 1).Text <> "RunMyMacro" Then Exit Sub
'current code here
End Sub
The other method is to modify the VBA code, but this is dangerous and normally blocked in most organisations as it is potentially very dangerous.
I have already looked at these two posts:
Closing a Userform with Unload Me doesn't work
Error when closing an opened workbook in VBA Userform
They both suggest that when you want to close a file from Form code, you need to Unload the Form first (using Unload Me). However, if I Unload, I have a global array that's getting dereferenced.
Take a look at my code below though (which crashes on assigning global_int(0,0) to test). I can't Unload the Form unless I remove the array. Is this really the only solution to this problem?
Create a fresh excel file. In it, create a Userform. On that, create a Command Button with the following Click event code and global declaration:
Private global_int(2, 10) As Integer
Private Sub CommandButton1_Click()
global_int(0, 0) = 23
Dim filename As String
Dim opened_workbook As Workbook
filename = Application.GetOpenFilename() ' User selects valid Excel file
Set opened_workbook = Application.Workbooks.Open(filename)
' File operations would occur here
Unload Me
opened_workbook.Close ' Exception thrown here
Dim test As Integer
test = global_int(0, 0)
MsgBox "If you got here, it worked!"
End Sub
I'm just adapting someone else's code to work on a Mac, so I'd like to avoid completely refactoring if possible.
Thanks.
based on what I can understand is you have a userForm and the code is inside there. You can't unload the user form from inside the userForm code and expect the rest of the code to work. One option would be to write the code in a separate module. Call the user form to run from there
Problem: I have found many postings (including Microsoft's own support site) containing information on how to run a macro when certain cells change in Excel. I can get it to work, but I need to store that sub in my Personal.xlsb workbook and have it affect named worksheets in another workbook.
Background: I receive an Excel file from a third party often and run a series of routines on it, with all Macros stored in my Personal.xlsb hidden workbook. Part of the process requires me to "undo" many of the formatting changes I have made automatically when a user enters a date in a specific cell. I would like to call those "undo" subs on 5 separate worksheets as soon as a user does make a date entry in cells specified on any of the given 5 worksheets. However, all of the help on this that I have already read online has me add the code to the exact worksheet to which the changes need to occur. I want to store that code in a module in my Personal workbook so that it will run on any Workbook containing the worksheet names...similar to the way I have my other queries laid out.
Code so far:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim KeyCells As Range
Set KeyCells = Range("W9")
If Not Application.Intersect(KeyCells, Range(Target.Address)) Is Nothing Then
MsgBox "You changed THE CELL!"
End If
End Sub
Question: How do I revise the sub in order to put it into effect in all of my workbooks containing the specified worksheets? Or is there a better option for me somewhere out there?
Since I faced the same problem, I worked my way through Chip Pearson's instructions and will provide you with a more comprehensive guide.
I can also really encourage you to read your way through Chip Pearson's great summary on events. I know it's a lot to digest, but it will greatly help you understand what we actually do here.
As an overview, these are the steps we are going to perform:
Add a class module for our event handlers (This is where the actual event handling code will go)
Add an event handler to our personal.xlsb's ThisWorkbook module to wire-up our event handler class
Enjoy infinite event handling loops because we changed the Target.Value :-P (optional)
Let's get started.
1. Add a class module
First, we need to create a cozy plaze to put all our event handlers. We will use a class module for this, since it provides some nice structure and you immediately know where to look for your application level event handlers.
In your personal.xlsb's VBA project create a new class by right clicking in the project browser -> insert -> class module.
Rename the class to CAppEventHandler by changing the name in the properties pane.
Add the source code listed below (both the setup part and the event handling part) to the class you just created. (Make sure to read the comments, because they add some additional information to what we are doing and why.)
2. Add the initializing event handler
Now we need to make sure our event handlers are "activated" when our personal.xlsb is opened (so anytime you open Excel :)).
To do this, double click your ThisWorkbook module and add the code below.
Now you are actually already good to go and can test your event handling. The only thing you will have to do beforehand (in order to trigger the "wiring-up" process) is restart Excel.
Notes
I added a check for the current sheet's name at the beginning of the event handler - so make sure your sheet is named "MySheet" :)
You can set a breakpoint inside the handler and watch it in action just like any other piece of VBA code
And a word of warning (aka 3. Enjoy infinite event handling loops)
When I tested this, I naively used Target.Value = "It wooooorks!" as the code to be executed if we changed the right cell on the right sheet. That was not good.
See the last snippet of code (also taken from Chip's post) for how to prevent such an event loop.
Source code
In the CAppEventHandler class (setup)
Option Explicit
' Declare our own reference to the Application object
' WithEvents is needed to capture the events of the application object
' (Note: The events 'bubble up' from the Worksheet to the Workbook and
' finally to the Application.
' So event handlers in the Sheet module are executed first
' (if any handlers are declared), then the ones in the Workbook
' module (again, if they are declared) and finally the ones
' in the Application module.)
Private WithEvents App As Application
' Whenever a new object of a class is instantiated, the _Initialize-Sub is called,
' that's why we use this Sub to get the reference to the current Application object
Private Sub Class_Initialize()
Set App = Application
End Sub
In the CAppEventHandler class (actual event handling)
' Here is the actual code executed whenever the event reaches the Application level
' (see above for the order of 'bubbling up' of the events) and hasn't been marked
' as handled before
Private Sub App_SheetChange(ByVal Sh As Object, ByVal Target As Range)
If Sh.Name <> "MySheet" Then
Exit Sub
End If
Dim rngKeyCells As Range
Set rngKeyCells = Sh.Range("A5")
If Intersect(rngKeyCells, Target) Is Nothing Then
Exit Sub
End If
' Do our actual work
MsgBox "It wooooorks!", vbInformation
' Note: If you want to change the contents of your keyCells, you will
' have to make sure to prevent an infinite event loop (i.e. using
' Application.EnableEvents = False because otherwise changing
' the value in your macro will trigger the event again
End Sub
In the ThisWorkbook module
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' The following code must be placed in the "ThisWorkbook" module of your personal.xlsb
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Option Explicit
Private OurEventHandler As CAppEventHandler
Private Sub Workbook_Open()
' Since we declared so in our _Initialize-Sub this wire-up the current
' Application object in our EventHandler class
Set OurEventHandler = New CAppEventHandler
End Sub
How to prevent event loops
Private Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
Target.Value = Target.Value + 1
Application.EnableEvents = True
End Sub
(from Chip Pearson's comprehensive post on events)
I have a workbook that is the main template for our group. Within this template I have several tabs that I would like to incorporate macros on for hiding and unhiding of rows based on values. I setup this macro on one tab, tested it out and it worked. However, when I went through the process of what the users will be doing the macro did not carry forward to the new workbook. I do not want to waste time doing the macro on the other sheets if I am unable to have the macro carry forward. Can someone please help as my template is due today?
In case more info is needed on the process of the template....The template opens up with a User form for the individual to enter in specific information and then during the process of the uploading of an information feed it creates a new workbook.
Write your template macro in a code module, then save and right-click the .bas module in the solution explorer, and export the module to a location you'll programmatically load it from later.
Then in another module, write the procedure that creates the workbook. Before saving the new workbook, load the exported module file into the workbook's VBProject.VBComponents collection, like this:
Dim targetWorkbook As Workbook
'...set up target workbook
targetWorkbook.VBProject.VBComponents.Import "module1.bas"
targetWorkbook.SaveAs "workbook.xls"
EDIT
If your macro code needs to listen to Excel's events (and/or worksheet events), you'll need to also add a class module where you're going to do exactly that. A code module will need to instantiate it for the class module's code to run.
Create a new class module and call it something like "clsExcelApp":
Private WithEvents xlApp As Excel.Application
Option Explicit
Private Sub Class_Initialize()
Set xlApp = Application
End Sub
Private Sub Class_Terminate()
Set xlApp = Nothing
End Sub
Then you can write event handlers for xlApp, such as:
Private Sub xlApp_SheetActivate(ByVal Sh As Object)
'do something whenever a worksheet gets activated.
End Sub
Private Sub xlApp_WorkbookBeforeSave(ByVal Wb As Workbook, ByVal SaveAsUI As Boolean, Cancel As Boolean)
'do something before a workbook gets saved.
End Sub
The code module would only need to instantiate it, like this:
private App As New clsExcelApp
Option Explicit
The rest of the module could be macros and/or functions:
Public Sub Macro1()
'do something
End Sub
Public Function Smurf(Range As Excel.Range) As Long
'Smurf the Range and then smurf a Long
End Function
Then save your hard work and export all modules and import them programmatically into the workbook you want to "inject" the functionality into.
Note You should always restrict access to VBProject, but to execute the Excel.Workbook.VBProject.VBComponents.Import(String)
method you'll need it enabled. Just remember to turn the security back
on when you're done - better safe than sorry!