Run VBA after ALL tables have refreshed

MichaelSchulz

Board Regular
Joined
Apr 10, 2014
Messages
64
I want to have a block of VBA code run after all the tables in the workbook have refreshed:
  1. User clicks the Refresh All button which causes
  2. All tables in the workbook to be refreshed
    1. four tables, each on a separate sheet
    2. tables are refreshed via Power Query
  3. Once all tables have completed the refresh, the VBA code runs
I have accomplished this in a different project using WithEvents and setting a QueryTable variable but that was only one table being refreshed. I just can't seem to see the logic to extend that approach to encompass multiple tables.

The code used for one table, set in ThisWorkbook:
VBA Code:
Option Explicit
Private WithEvents QT as QueryTable

Private Sub Workbook_Open()
       Set QT = Sheet1.ListObjects(1).QueryTable
End Sub

Private Sub QT_AfterRefresh(ByVal Success As Boolean)
        If Success Then
              [I][block of code to be run after refresh completes][/I]
        End If
End Sub
 
Yeah, if you don't want to add a custom button on the Ribbon, a custom class would be an alternative. Actually, I mentioned it earlier in Post #22 of this thread. In any case, try the following code., test it throroughly, and let me know if there are any issues.

Note that the AfterRefresh event gets triggered every time a table is refreshed via RefreshAll or Refresh. The code keeps track of which table gets refreshed. When all tables are refreshed, your code runs, and then the process of keeping track starts over again.

Also, note that the code contains a number of debug statements. These statements are included so that you can see exactly what happens. After clicking on RefreshAll or Refresh, have a look at the Immediate Window (Ctrl+G) for the results. Of course, these statements can be removed once you've fully tested the code.

First insert a class module (Visual Basic Editor >> Insert >> Class Module), and then in the Properties Window (F4) under Name, rename the module clsQueryTable. Then place the following code in the code module...

VBA Code:
Option Explicit

Private WithEvents m_queryTable As QueryTable

Public Property Set QueryTable(ByVal qt As QueryTable)
    Set m_queryTable = qt
End Property

Private Sub m_queryTable_AfterRefresh(ByVal Success As Boolean)

    If Success Then
    
        Debug.Print "AfterRefresh: " & m_queryTable.ListObject.Name
        
        Dim tableName As String
        tableName = m_queryTable.ListObject.Name
        
        dicQueryTables(tableName)("refreshed") = 1
        
        myMacro 'calls your macro, change the name accordingly
        
    End If
    
End Sub

Then insert a regular module (Visual Basic Editor >> Insert >> Module), and place the following code in the code module...

VBA Code:
Option Explicit

Public dicQueryTables As Object

Public Sub init(Optional ByVal dummy As Long)

    Debug.Print "init . . ."
    
    Set dicQueryTables = CreateObject("Scripting.Dictionary")
    
    Dim ws As Worksheet
    Dim oListObject As ListObject
    Dim oQueryTable As QueryTable
    Dim cQueryTable As clsQueryTable
    Dim dicTableTracker As Object
    
    For Each ws In ThisWorkbook.Worksheets
        For Each oListObject In ws.ListObjects
            On Error Resume Next
            Set oQueryTable = oListObject.QueryTable
            On Error GoTo 0
            If Not oQueryTable Is Nothing Then
                Set cQueryTable = New clsQueryTable
                Set cQueryTable.QueryTable = oQueryTable
                Set dicTableTracker = CreateObject("Scripting.Dictionary")
                dicTableTracker.Add key:="query_table", Item:=cQueryTable
                dicTableTracker.Add key:="refreshed", Item:=0
                dicQueryTables.Add key:=oListObject.Name, Item:=dicTableTracker
                Set oQueryTable = Nothing
                Set dicTableTracker = Nothing
            End If
        Next oListObject
    Next ws
    
End Sub

Public Sub myMacro(Optional ByVal dummy As Long)

    Debug.Print "myMacro . . ."

    If dicQueryTables.Count > getRefreshCount() Then
        Debug.Print "Exit myMacro . . ."
        Exit Sub
    End If
    
    Debug.Print "Your code runs . . ."

    'Your code goes here
    '
    '
    
    resetTableTracking
    
End Sub

Public Function getRefreshCount() As Long

    Debug.Print "getRefreshCount . . ."

    Dim refreshCount As Long
    refreshCount = 0
    
    Dim key As Variant
    For Each key In dicQueryTables.keys()
        If dicQueryTables(key)("refreshed") = 1 Then
            refreshCount = refreshCount + 1
        End If
    Next key
    
    getRefreshCount = refreshCount
    
End Function


Public Sub resetTableTracking()

    Debug.Print "resetTableTracking . . ."

    Dim key As Variant
    
    For Each key In dicQueryTables.keys()
        dicQueryTables(key)("refreshed") = 0
    Next key
    
End Sub

Then, in the Project Explorer window (Ctrl+R), right-click ThisWorkbook, select View Code, and then place the following code in the code module...

VBA Code:
Option Explicit

Private Sub Workbook_Open()
    init
End Sub
 
Upvote 0
Solution

Excel Facts

Did you know Excel offers Filter by Selection?
Add the AutoFilter icon to the Quick Access Toolbar. Select a cell containing Apple, click AutoFilter, and you will get all rows with Apple
I fogot . . . once you've copied all of the code into your workbook, save, close, and re-open it. The Open event handler kicks things off.
 
Upvote 0
I have no experience using dictionaries so before I saw your post I had thought to use a dynamic string array variable to track each table name when it successfully refreshes. Both approaches work but I'm curious about the advantages/disadvantages of using a dictionary versus an array variable.
 
Upvote 0
When it comes to lookups, the dictionary has the advantage. In this case, though, I'm not sure it would make much of a difference.
 
Upvote 0
When it comes to lookups, the dictionary has the advantage. In this case, though, I'm not sure it would make much of a difference.
To check my understanding—in case I ever run into a situation where using the dictionary would make a difference—is what you're saying is that:
  1. a dictionary would perform better when storing and checking more elaborate/complex data; and/or
  2. makes it easier to write/maintain the code that performs the data lookup in the dictionary?
Do I have that right?

And if I do have that right then is the reason it does not make much of a difference in this situation because the data being stored and checked is simple data, i.e. a small number of string values (table names)?
 
Upvote 0
With straight index lookups, arrays are ideal. You know exactly where to find the desired value, and so it can be retrieved relatively quickly and efficiently.

When a linear search is required, though, the dictionary is usually more efficient. Although this may not necessarily be the case when there are few keys.
 
Last edited:
Upvote 0

Forum statistics

Threads
1,224,830
Messages
6,181,225
Members
453,025
Latest member
Hannah_Pham93

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top