如何等待Power Query刷新完成?我用“BackgroundQuery = False”尝试了“DoEvents”,而且更多,没有任何作用

时间:2017-12-28 01:46:05

标签: excel vba refresh powerquery doevents

设定:

  • Windows 7(正在工作)
  • Windows 10(在家)
  • Excel 2016(在工作中构建4627)
  • Excel 2016(在家建立8730)
  • Power Query设置为导入,追加和转换Excel文件夹。这一步有效。

问题描述

使用任何技术的任意组合等待Power Query完成刷新后,如下所示的“我尝试过的事情:”部分所述。根据“查询和连接”窗格中显示的刷新指示器(微调器?)图标,可以在Power Query表完成更新之前显示消息框并执行任何其他代码。

上述语句的例外是Application类的“OnTime”方法,如下面的“代码”部分所示,它似乎不会中断电源查询刷新的轮询。但是,此方法的问题在于它使用硬编码的时间来暂停VBA代码,并且这并不总是有效,因为查询的数据的大小,数量和持续时间将随着时间的推移而变化。

我尝试过的事情:

  • 我已阅读所有说明使用“DoEvents”,“BackgrgoundQuery = False”和CalculateUntilAsyncQueriesDone方法和属性的StackOverflow(和其他网站资源)。
  • 我尝试按照此链接的建议创建一个类Create Before/After Query Update Events(未在下面的代码示例中显示)。
  • 我尝试使用DoTore / While循环使用QueryTable方法的.Refreshing = True / False属性来等待刷新完成。
  • 我尝试将Excel菜单中的BackgroundQuery属性(菜单栏 - >数据 - >连接 - >属性)设置为False,如“subro”所示:Wait until ActiveWorkbook.RefreshAll finishes - VBA,这里的菜单图片:

Excel menu for setting the BackgroundQuery property

代码:

Private Sub sht_sub_Refresh_AllConnections_dev()
'Name: sht_sub_Refresh_AllConnections_dev
'Purpose: An attempt at using VBA to wait for Queries to finish updating before displaying a message.
'Description: Waits for a hard coded period of time before dislpaying the message box.
'State: WIP.
'Dev: Needs a way to look at the connection stream to somehow detect when its finished.

  'DECLARATIONS:
  '------------'
  Dim procName As String              'Stores this procedure's name.
  Dim qTblLst As QueryTables          'A query table collection object.
  Dim qTblObj As QueryTable           'A query table object.
  Dim conLst As Connections           'A connection collection object.
  Dim conObj As WorkbookConnection    'A connection object.
  Dim idx As Long                     'A loop counter.

  'INITIALIZATIONS:
  '---------------'
  procName = "sht_sub_Refresh_AllConnections_dev"    'Store this procedure's name.
  Linit.ini_Setup_Project                            'Setup the project if needed.
  Set conLst = ThisWorkbook.Connections              'Set the connections list object.
  Set conObj = conLst.Item(conLst.Count)             'Set an initial connection object.
  idx = 0                                            'As an exit if the do loop continues without end.

  'MAIN CODE BODY:
  '--------------'
  'Turn off backgroundquery for each connection type.
  For Each conObj In conLst                           'For each connection object,
    With conObj
      Select Case .Type                               'Check the connection type,
        Case 1                                        'If its an OLEDB connection then,
          .OLEDBConnection.BackgroundQuery = False    'Set it's backgroundquery property to false.
        Case 2                                        'If its an ODBC connection the,
          .ODBCConnection.BackgroundQuery = False     'Set it's backgroundquery property to false.
      End Select
    End With
  Next conObj

  ThisWorkbook.RefreshAll                             'Refresh all connections.

'DEV: Using loops, DoEvents and a query name starting with the letters "zzzz" as suggsted here:
'https://social.technet.microsoft.com/Forums/en-US/bc3f7748-8a52-498d-951c-4566b8adf45a/in-excel-2016-power-queries-dont-refresh-in-the-background-anymore?forum=powerquery
'and here:
'https://www.myonlinetraininghub.com/excel-forum/vba-macros/pause-macro-until-power-queries-finished-refreshing
  'Attempt to wait until the last connection has finished refreshing.
  Do Until Linit.gvTbl_ZZZZZ.QueryTable.Refreshing = True   'Wait until the last table starts refreshing,
    idx = idx + 1                                           'Icrement a loop count,
    If idx > 3000 Then Exit Do                              'If the loop goes longer then 3000 iterations exit,
  Loop                                                      'otherwise continue waiting.
  VBA.DoEvents                                              'Do events before continueing (doens't work).
  Do Until Linit.gvTbl_ZZZZZ.QueryTable.Refreshing = False  'Wait until the last table finishes refreshing,
    idx = idx + 1                                           'Icrement a loop count,
    If idx > 3000 Then Exit Do                              'If the loop goes longer then 3000 iterations exit,
  Loop                                                      'otherwise continue waiting.
  VBA.DoEvents                                              'Do events before continueing (doens't work).
'DEV: The following is an attempt to get connections to
'     finish refreshing before code continues as suggested here:
'https://stackoverflow.com/questions/22083668/wait-until-activeworkbook-refreshall-finishes-vba
  Application.CalculateUntilAsyncQueriesDone         'This is placed here as well as after the refresh.
  VBA.DoEvents                                              'Do events before continueing (doens't work).
  Application.EnableEvents = False                          'Maybe turning off events helps? (nope...),
  Application.ScreenUpdating = False 'This is reset in the procedure called as an argument to the next line:
  Application.OnTime DateAdd("s", 3, Now), _
                     "Lwksh.sht_sub_Msg_RefreshDone"        'The called procedure just displays a message box.
  Application.EnableEvents = True                           'Restore events,
  Application.ScreenUpdating = True                         'Restore screen updating.

  'MEMORY CLEANUP:
  '--------------'
EXIT_CLEAN:
  procName = Empty                                     
  Set qTblLst = Nothing
  Set qTblObj = Nothing
  Set conLst = Nothing
  Set conObj = Nothing
  idx = 0
End Sub

代码备注:

  • 代码中的任何内容都以“Linit”开头。是一个对象或变量,它通过代码的“INITIALIZATIONS:”部分中的“Linit.ini_Setup_Project”过程调用在过程外部全局设置。
  • 例如,“Linit.gvTbl_ZZZZZ”是一个对象变量,指向空的一行表,其名称以字符“zzzz”加星,由Power Query生成并加载到Excel工作表。代码显示了网站的链接,其中提出了使用这样的空表的建议。

问题

  1. 由于Power Query没有内置回调让Excel知道它已经完成更新任何刷新过程,这是否是一个失败的原因?
  2. 如果这不是一个失败的原因,是否还有其他方法(此处未描述)可用于在连接尚未完成刷新时以某种方式触发错误,或者在触发错误时触发错误连接完成了吗? (这里的想法是,如果错误不能阻止查询完成,这个错误可能被捕获作为检测天气的可能方式或不刷新已完成)。
  3. 有没有办法直接使用VBA探测连接流以查找连接已关闭或已完成状态?
  4. 有没有办法通过调用用C#或Python等其他语言编写的Excel以外的程序来直接访问刷新过程?
  5. 您能想到其他可能尝试或测试过的其他功能吗?我会继续自己寻找答案,但经过一整年的搜索,我感觉有点不走运。
  6. 感谢您抽出时间和精力解决此问题。 如果我忘记包含任何信息或者可能需要重新编写某些内容以使此问题更加清晰,请在评论中告诉我。

3 个答案:

答案 0 :(得分:0)

我了解您的痛苦@neurojelly。我去过那里。但事实证明,该解决方案非常简单,并且没有使用VBA。 在“查询属性”窗口中,您需要取消选中“启用后台刷新”,然后使用DoEvents。 我肯定知道这是可行的,因为我已经使用这种方法一年多了。

请找到包含代码的示例文件的链接。
https://drive.google.com/open?id=1ZLxSMEXPLda3QhaQoTyGGv3_sC-tpN-X
Disable background refresh


至于第二个问题,可以使用Iferror/OnEror方法来检测查询是否返回错误,但是不一定要检查查询中的错误。它标识查询本身是否返回错误弹出窗口,在运行VBA代码时默认情况下会跳过该错误弹出窗口。此方法在大多数时间有效,但并非总是如此。

答案 1 :(得分:0)

这是一种解决方法

Sub MyProcedure()

    '
    ' Some procedures
    '
    Call ActiveWorkbook.RefreshAll
    Call NotifyWhenRefreshComplete
End Sub



Private Sub NotifyWhenRefreshComplete()
    Const PulseTimer As Currency = TimeValue("00:00:01")
    Dim b1 As Boolean, b2 As Boolean

    b1 = Sheet1.Range("ListObject1").ListObject.QueryTable.Refreshing
    b2 = Sheet1.Range("ListObject2").ListObject.QueryTable.Refreshing

    If b1 Or b2 Then
        Call Application.OnTime(Now + PulseTimer, "NotifyWhenRefreshComplete")
    Else
        Call MsgBox("Refresh Complete.", vbOKOnly)
    End If
End Sub

ListObject1和ListObject2是已发布的表。仅需要发布表才能检查刷新是否完成。您不必检查未发布的表。

但是,如果您有很多已发布的表,那么遍历所有ActiveWorkbook.connections并检查每个wbConn.OLEDBConnection.Refreshing状态是否都返回到false状态并替换就好了。 b1b2布尔值。

注意:出于某些原因,我拒绝使用DoEvents,我希望我的用户能够在连接仍在运行时继续使用Excel,并在刷新完成时向他们提示消息。但是,如果要使用Do... Loop迭代而不是如上所述的OnTime调用程序来实现它,请继续。

最后,在Public WithEvents qt As QueryTable下有一个回调,查找qt.refreshing。您也可以使用此方法。

希望这会有所帮助。

答案 2 :(得分:-1)

我在ThisWorkbook.Connections.Ranges(1).ListObject.QueryTable上调用.Refresh方法,使用BackgroundQuery:= False。它看起来非常可靠 - 到目前为止,我的Macro可能已经将该方法称为10,000次,误差/冻结率非常低。

据我了解,当PQ在Excel 2016的对象模型中正确公开时,所有这些DoEvents变通办法等都已过时。

这是一个粗略的代码示例:

Dim cn As WorkbookConnection
For Each cn In ThisWorkbook.Connections
    cn.Ranges(1).ListObject.QueryTable.Refresh BackgroundQuery:=False
Next cn

在For循环中,您可以检查cn.Name以控制各个查询的执行。 Name属性遵循工作簿连接名称,例如"查询 - " &安培; PQ查询名称。