How to call macro after Refresh or Refresh All button pressed? - excel

Ultimately, I would like to run a macro after anyone refreshes the workbook, specifically using the Refresh button under the Data tab in Excel.
For the time being, I would be satisfied just getting the BeforeRefresh or AfterRefresh QueryTable events to fire upon pressing the Refresh button.
In addition to the documentation on the Microsoft Dev Center website, the relevant posts I have read include:
Excel VBA - QueryTable AfterRefresh function not being called after Refresh completes
VBA For Excel AfterRefresh Event
There are other posts but I lack the reputation to post them.
Here is what I have:
Under Class Modules (qtclass)
Option Explicit
Private WithEvents qt As Excel.QueryTable
Private Sub qt_AfterRefresh(ByVal Success As Boolean)
MsgBox "qt_AfterRefresh called sucessfully."
If Success = True Then
Call Module2.SlicePivTbl
MsgBox "If called succesfully."
End If
End Sub
Private Sub qt_BeforeRefresh(Cancel As Boolean)
MsgBox "qt_BeforeRefresh called."
End Sub
Under the ThisWorkbook module
Private Sub Workbook_Open()
Dim qtevent As qtclass
Dim qt As QueryTable
Set qt = ThisWorkbook.Worksheets("Data-Fund").ListObjects(1).QueryTable
Set qtevent = New qtclass
End Sub
I have tried variations of the second code block under specific worksheets as well, but have yet to find anything that works. Do I need to somehow dim the QueryTable in question in the Worksheet module?

You haven't actually connected the querytable to the class instance.
Revised qtclass
Option Explicit
Private WithEvents qt As Excel.QueryTable
Public Property Set HookedTable(q As Excel.QueryTable)
Set qt = q
End Property
Private Sub qt_AfterRefresh(ByVal Success As Boolean)
MsgBox "qt_AfterRefresh called sucessfully."
If Success = True Then
Call Module2.SlicePivTbl
MsgBox "If called succesfully."
End If
End Sub
Private Sub qt_BeforeRefresh(Cancel As Boolean)
MsgBox "qt_BeforeRefresh called."
End Sub
New ThisWorkbook code:
Dim qtevent As qtclass
Private Sub Workbook_Open()
Set qtevent = New qtclass
Set qtevent.HookedTable = ThisWorkbook.Worksheets("Data-Fund").ListObjects(1).QueryTable
End Sub
Note that this is quite closely coupled. It would be more re-usable if you were to raise events in the class and declare your qtevent variable WithEvents.

Source: https://www.excelandaccess.com/create-beforeafter-query-update-events/
Class:
Note: Class Name = clsQuery
Option Explicit
Public WithEvents MyQuery As QueryTable
Private Sub MyQuery_AfterRefresh(ByVal Success As Boolean)
If Success Then
Debug.Print "After ReFresh"
End If
End Sub
Private Sub MyQuery_BeforeRefresh(Cancel As Boolean)
Debug.Print "Before ReFresh"
End Sub
Module:
Option Explicit
Dim colQueries As New Collection
Sub InitializeQueries()
Dim clsQ As clsQuery
Dim WS As Worksheet
Dim QT As QueryTable
For Each WS In ThisWorkbook.Worksheets
For Each QT In WS.QueryTables
Set clsQ = New clsQuery
Set clsQ.MyQuery = QT
colQueries.Add clsQ
Next QT
Next WS
End Sub
ThisWorkbook.Event:
Private Sub Workbook_Open()
Call InitializeQueries
End Sub

Related

Trap workbooks events from Excel add-in

I try to create an add-in, mostly as a POC, but it does not work. What am I missing ?
I just want the add-in to debug.print the name of the workbook when it is activated.
The class self instantiating, thanks to the PredeclaredId attribute set to True (the part before Option Explicit only works if you IMPORT the class, it cannot be typed in the VBE).
Here is the class module:
VERSION 1.0 CLASS
BEGIN
MultiUse = 0
END
Attribute VB_Name = "clsWb"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Option Explicit
Private WithEvents myApp As Application
Attribute myApp.VB_VarHelpID = -1
Private Sub Class_Initialize()
Set myApp = Application
End Sub
Private Sub myApp_WorkbookActivate(ByVal Wb As Workbook)
Debug.Print Now, Wb.Name
End Sub
No other class is needed to accomplish what you want. Please, try proceeding in the next way:
Copy the next code in add-in ThisWorkbook code module:
Public WithEvents myApp As Application
Sub ActivateMyAppH()
Set myApp = Application
End Sub
Sub InAactivateMyAppH()
Set myApp = Nothing
End Sub
Private Sub myApp_WorkbookActivate(ByVal Wb As Workbook)
Debug.Print Now, Wb.Name
End Sub
(Firstly, manually) run ActivateMyAppH and play with activating documents.
After seeing it working, place a call in the add-in Workbook_Open event:
Private Sub Workbook_Open()
ActivateMyAppH
'whatever necessary, if the event is already used for something else
End Sub
To be on the safe side, take care to inactivate the event:
Private Sub Workbook_BeforeClose(Cancel As Boolean)
InAactivateMyAppH
End Sub

VBA Excel: Get the clicked cell´s adress on another workbook

I have an excel file A with a macro and I have to retrive a cell´s adress in another excel file B by the user´s click on it.
The macro looks like this.
In the Class:
Public WithEvents appevent As Application
Private Sub appevent_SheetBeforeDoubleClick(ByVal Sh As Object, ByVal Target As Range, Cancel As Boolean)
ClickedCell = ActiveCell.Address
End Sub
In the Module
Sub ClickedCellSub()
Dim WbA As Variant, WbB As Variant
WbA = ThisWorkbook.Name
WbB = "B.xlsx"
MsgBox "Please double click on the Assembly SS 00 you want to compare"
Set myobject.appevent = Application
Workbooks(WbB).Sheets(1).Activate
Set myobject.appevent = Nothing
MsgBox ClickedCell
Workbooks(WbA).Activate
End Sub
The problem is, the macro doesn´t wait for the event DoubleClick on the other excel sheet and goes to the end.
How can I stop the macro until the event happens?
Many thanks in advance!
I would use event sinking, but not sure how you have approached it, but this is how i'd use.
In a class use the following :
Private WithEvents wb As Excel.Workbook
Private rng As Excel.Range
Public Event evtCellClicked(rngClicked As Excel.Range)
Public Event evtCellDoubleClicked(rngDoubleCliecked As Excel.Range)
Public Sub init(wbInput As Excel.Workbook)
Set wb = wbInput
End Sub
Public Property Get CellClicked() As Excel.Range
CellClicked = rng
End Property
Private Sub wb_SheetBeforeDoubleClick(ByVal Sh As Object, ByVal Target As Range, Cancel As Boolean)
Set rng = Target
RaiseEvent evtCellClicked(Target)
End Sub
Private Sub wb_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
Set rng = Target
RaiseEvent evtCellClicked(Target)
End Sub
I changed the code. In the module:
enter Option Explicit
Private mobjApplication As clsApplication
Global ClickedCell As String
Sub Vergleichen()
Dim StruktBer As Variant
StruktBer = Application.GetOpenFilename(, , "Strukturbericht öffnen") '####Name der Datei aus der Strukturberich
Set mobjApplication = New clsApplication
Workbooks.Open StruktBer
MsgBox "Bitte die Sachnummer der Strukturstufe '00' anklicken die man vergleichen möchte"
End Sub
Sub GOFURTHER
In the Class:
Option Explicit
Private WithEvents mobjApplication As Application
Private Sub Class_Initialize()
Set mobjApplication = Application
End Sub
Private Sub mobjApplication_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
ClickedCell = Target.Address
Set mobjApplication = Nothing
Call GOFURTHER
End Sub
The macro stops until the user clicks on the other excel table. Is there any way not to exit the first sub and to remain in the first one?
Why not simply using an InputBox ?
https://learn.microsoft.com/en-us/office/vba/api/excel.application.inputbox
In your prompt you ask the user to select a range in the appropriate document, and that's it.

How to run a macro when a cell updates through refreshing of data from web?

When the column is refreshing nothing is happening but when I go to the cell and change the value then it is changing.
I want when cells update through refresh it should run.
The column updates but the code doesn't trigger the macro.
Also tried Worksheet_Calculate().
The column is linked with online stock data from NSE website.
Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Target.Worksheet.Range("B:B")) Is Nothing Then
MsgBox "Cell Value Changed"
Call MyMacro()
End If
End Sub
On internet just told to use Worksheet_Calculate().
Also if trying to update the cell which is equal to a cell in Range("B:B"), the value changes but macro doesn't trigger.
Maybe give this a try by using Workbook_SheetChange instead of Worksheet_Change
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
If Not Intersect(Target, Target.Worksheet.Range("B:B")) Is Nothing Then
MsgBox "Cell Value Changed"
Call MyMacro()
End If
End Sub
Note that you need to put your code in ThisWorkbook and not your module
Edit : to test the Answer :
Sub TryMe()
For i = 1 To 100
Cells(2, 2).Value = i
Next
End Sub
TryMe Should be added inside a module like below
Workbook_SheetChange hould be added inside ThisWorkbook like below
When we execute test module we should have stuff like this:
and so on..
EDIT 2 If Value are changed by formula :
Give this a try :
This code should be placed in the sheet you are using (in my exemple Sheet1)
Private Sub Worksheet_Calculate()
Dim rng As Range
Set rng = Range("B:B")
If Not Intersect(rng, Range("B:B")) Is Nothing Then
MsgBox "Cell Value Changed"
End If
End Sub
In a Module execute this code once :
Sub TryMe()
ActiveWorkbook.RefreshAll
Application.Calculation = xlAutomatic
End Sub
then this should work
You can use events of QueryTable object behind the table linked to a web source. To do that, you first need to create a class module. Let's call it clsQryTebleEvents. In that module place a WithEvents variable of type Excel.QueryTable and set it to the QueryTable for which you want to capture events. Here's the code for clsQryTableEvents:
Option Explicit
Private WithEvents qryTable As Excel.QueryTable
Private Sub Class_Initialize()
'QueryTable connected to a webpage is on Sheet1, and it's the only table on that sheet, so we can access it with ListObjects(1)
Set qryTable = Sheet1.ListObjects(1).QueryTable
End Sub
Private Sub Class_Terminate()
'Free Memory
Set qryTable = Nothing
End Sub
'You can use other events as well
Private Sub qryTable_BeforeRefresh(Cancel As Boolean)
MsgBox "Refresh is about to start!", vbInformation
End Sub
Next, you need to initialize a variable of this class. You can declare a Public variable inside a standard module and the use Workbook_Open event to instantiate it. Code in a standard module:
Option Explicit
Public objQryTable As clsQryTableEvents
Code in ThisWorkbook:
Option Explicit
Private Sub Workbook_Open()
Set objQryTable = New clsQryTableEvents
End Sub
Private Sub Workbook_BeforeClose(Cancel As Boolean)
'Free memory
Set objQryTable = Nothing
End Sub
All done! Next time you open the workbook, the objQryTable will be initialised and will start listening to refresh events.

Excel VBA application event triggered twice

I am trying to change active printer according to worksheet name when click on Quick Print button, however, the App_WorkbookBeforePrint event is triggered twice. Tried App_WorkbookBeforeClose also triggered twice. I have already changed the Error Trapping to Break on All Error, but it seems like no errors have occurred.
ThisWorkbook:
Private XLApp As CExcelEvents
Private Sub Workbook_Open()
Set XLApp = New CExcelEvents
End Sub
Class Modules:
Option Explicit
Private WithEvents App As Application
Private Sub Class_Initialize()
Set App = Application
End Sub
Private Sub App_WorkbookBeforePrint(ByVal Wb As Workbook, Cancel As Boolean)
MsgBox Wb.FullName
assignPrinter
End Sub
Module:
Const printer1 As String = "Bullzip PDF Printer on Ne10:"
Const printer2 As String = "EPSONF7E8B5 (L565 Series) on Ne07:"
Public Sub assignPrinter()
Dim ws As Worksheet
Dim wsn As String
Set ws = ActiveWorkbook.ActiveSheet
wsn = ws.Name
Select Case wsn
Case "FGWIP"
Application.ActivePrinter = printer1
ws.PrintOut
Exit Sub
Case "Rework"
Application.ActivePrinter = printer2
ws.PrintOut
Exit Sub
Case Else
MsgBox "Else case."
Exit Sub
End Select
End Sub
Update:
Use App_SheetActivate instead of App_WorkbookBeforePrint to change active printer and remove ws.Printout as mentioned by Matley
I re-created your modules and the only error I received was in the code Debug.Print assignPrinter in which I replaced with assignPrinter.
The rest of the codes works fine. App_WorkbookBeforePrint did not trigger twice. You can set a breakpoint in App_WorkbookBeforePrint then look into Stack to see which triggers App_WorkbookBeforePrint for the second time.

Right-click withevents works on source .xlsm but not on .xlam addin

I've made a couple of macros that run through right click menu button based on the cell value. Typically, if I right click on cell with value 'XYZ', the menu button shows as 'Run macro for XYZ' and then does a bunch of operations: show a couple of user forms, run an SQL query, show and format result data.
On the original .xlsm file, on 'Thisworkbook' I have the following code:
Public WithEvents mxlApp As Application
Public WithEvents mxlSh As Worksheet
Private Sub mxlApp_SheetBeforeRightClick(ByVal Sh As Object, ByVal Target As Range, Cancel As
Boolean)
... (do stuff here) ...
End Sub
...
Private Sub Workbook_Open()
Call AutoExec
End Sub
...
On a separate module, I have the following function used to set my event handler
Public Sub AutoExec()
Set mxlApp = Application
Set ColectionOfMxlEventHandlers = New Collection
ColectionOfMxlEventHandlers.Add mxlApp
Debug.Print ThisWorkbook.Name & " Initialized"
End Sub
The problem: on the original .xlsm file, the code works fine: every time I right-click on a cell which meets certain criteria, I get the 'Run macro for XYZ' and all is fine.
Once I save the file as .xlam and load it as addin, the code won't work.
I have been looking everywhere on the internet and here and couldn't figure out how to resolve this issue.
EDIT:
After modifying the code as kindly suggested by creamyegg, this is what I have:
In class module clsAppEvents:
Private WithEvents mxlApp As Excel.Application
Private Sub Class_Initialize()
Set mxlApp = Excel.Application
End Sub
Private Sub mxlApp_SheetBeforeRightClick(ByVal Sh As Object, ByVal Target As Range, Cancel As Boolean)
Dim cBut As CommandBarButton
On Error Resume Next
Call CleanMenu
If Len(Target.Value) = 8 Then
MyId = Target.Value
With Application
Set cBut = .CommandBars("Cell").Controls.Add(Temporary:=True)
End With
With cBut
.Caption = "Run SQL Query for " & MyId
.Style = msoButtonCaption
.FaceId = 2554
.OnAction = "CallGenericQuery"
End With
End If
With Application
Set cBut = .CommandBars("Cell").Controls.Add(Temporary:=True)
End With
With cBut
.Caption = "Columns_Select"
.Style = msoButtonCaption
.FaceId = 255
.OnAction = "CallShowHide"
End With
On Error GoTo 0
End Sub
in Thisworkbook class I have
Public m_objMe As clsAppEvents
Private Sub Workbook_Open()
Set m_objMe = New clsAppEvents
Debug.Print ThisWorkbook.Name & " Initialized"
End Sub
Private Sub Workbook_BeforeClose(Cancel As Boolean)
On Error Resume Next
Call CleanMenu
On Error GoTo 0
Set m_objMe = Nothing
End Sub
Private Sub Workbook_Deactivate()
Call CleanMenu
End Sub
MyId is defined as a public string in the main module containing the CallShowHide and callGenericQuery subs
The issue sounds like your WithEvents is still in your ThisWorkbook Class? What you need to do is create a new class and then instantiate an instance of this on the Workbook_Open() event of your add-in. For example:
New Class (clsAppEvents):
Private WithEvents mxlApp As Excel.Application
Private Sub Class_Initialize()
Set mxlApp = Excel.Application
End Sub
Private Sub mxlApp_SheetBeforeRightClick(ByVal Sh As Object, ByVal Target As Range, Cancel As Boolean)
...
End Sub
Add-in ThisWorkbook Class:
Private m_objMe As clsAppEvents
Private Sub Workbook_Open()
Set m_objMe = New clsAppEvents
End Sub
Private Sub WorkbookBeforeClose(Cancel As Boolean)
Set m_objMe = Nothing
End Sub

Resources