0

我正在编写/维护一个 Excel VBA 应用程序,其中有多个 QueryTables 链接到 MS sql 服务器数据库。该应用程序的用户可以通过操作 Excel 文档上的各种 UI 控件来更改对每个表的 SQL 查询。

我在使用 QueryTables 时遇到的问题之一是使用了多线程。文档上的每个 QueryTable 都有一个原始状态,必须在运行查询后恢复该状态。例如,如果 QueryTable1 有一个基本查询

Select * from example_table

并且用户在控件上选择了某些输入来创建

Select * from example_table Where object_oid = '10'

我需要恢复原始状态。下面的代码是我目前如何完成此任务的快照

Sub RefreshDataQuery()
'Dependencies: Microsoft Scripting Runtime (Tools->References) for Dictionary (HashTable) object

Dim querySheet As Worksheet
Dim interface As Worksheet

Set querySheet = Worksheets("QTable")
Set interface = Worksheets("Interface")

Dim sh As Worksheet
Dim qt As QueryTable
Dim qtDict As New Scripting.Dictionary

Set qtDict = UtilFunctions.CollectAllQueryTablesToDict

Set qt = qtDict.Item("Query from fred2")

''' Building SQL Query String '''
Dim sqlQueryString As String
Dim originalQueryCache As String
originalQueryCache = qt.CommandText
sqlQueryString = qt.CommandText

QueryBuilder.BuildSQLQueryStringFromInterface interface, sqlQueryString

MsgBox sqlQueryString

qt.CommandText = sqlQueryString

If Not qt Is Nothing Then
    qt.Refresh
Else
    'Error Messages and handling here
    ' Cut out to keep code short
End If


''' CLEAN UP '''

'Restore the original base SQL query
' Problem is here
' This, or any other altering statement, will error out if the query is still refreshing
qt.CommandText = originalQueryCache
' Other original state restoring code below...

' Free the dictionary
Set qtDict = Nothing


End Sub

理想情况下,如果我用另一种现代语言编写此代码,我将创建一个回调函数或在我自己的线程中使用完成通知程序运行刷新。我花了很多时间研究如何为 qt.Refresh 调用获取回调函数,但没有运气。我意识到我可以稍微“破解”这个问题,但我宁愿不从事不良做法,因为将来很多人将不得不保持这种做法。

此应用程序必须支持 Excel 2010 及更高版本

那么如何为在不同线程中运行的 VBA 函数创建回调函数呢?或者,我应该考虑另一种方法吗?

4

1 回答 1

1

除了通过自定义类模块和 WithEvents 关键字外,不会公开 QueryTables 事件。首先,创建一个名为 CQtEvents 的自定义类模块并将其放入其中:

Private WithEvents mQryTble As QueryTable
Private msOldSql As String

Public Property Set QryTble(ByVal QryTble As QueryTable): Set mQryTble = QryTble: End Property
Public Property Get QryTble() As QueryTable: Set QryTble = mQryTble: End Property
Public Property Let OldSql(ByVal sOldSql As String): msOldSql = sOldSql: End Property
Public Property Get OldSql() As String: OldSql = msOldSql: End Property

Private Sub mQryTble_AfterRefresh(ByVal Success As Boolean)

    Me.QryTble.CommandText = Me.OldSql

End Sub

这是两个属性:一个用于保存 QueryTable,另一个用于存储旧的 sql。然后你的程序看起来像

Sub RefreshDataQuery()

    Dim interface As Worksheet
    Dim qt As QueryTable
    Dim qtDict As New Scripting.Dictionary
    Dim clsQtEvents As CQtEvents
    Dim sqlQueryString As String

    Set qtDict = UtilFunctions.CollectAllQueryTablesToDict
    Set qt = qtDict.Item("Query from fred2")

    sqlQueryString = qt.CommandText
    QueryBuilder.BuildSQLQueryStringFromInterface interface, sqlQueryString

    'Create class for events and store old sql
    Set clsQtEvents = New CQtEvents
    Set clsQtEvents.QryTble = qt
    clsQtEvents.OldSql = qt.CommandText

    qt.CommandText = sqlQueryString

    If Not qt Is Nothing Then
        qt.Refresh 'after this is done, the event in the class will fire
    Else
        'Error Messages and handling here
    End If

End Sub

因为您使用 WithEvents 定义 mQryTble,所以它的两个事件(BeforeRefresh 和 AfterRefresh)在类中公开。通过将 CQtEvents.QryTble 设置为您的 QueryTable,该类将侦听该 QueryTable 上的事件。CommandText 在更改之前存储在 OldSql 中。然后当 Refresh 完成时,事件触发并恢复 CommandText。当然,在事件中没有完成刷新,但我假设你想要旧的 sql 语句,如果它被刷新或重新处理。

接下来,您应该考虑创建一个集合类来保存一堆 QtEvents 实例。我假设您的代码处理一个作为示例,但您确实做得更多。然后,您可以将 CollectAllQueryTables 移动到该集合类中,并将 BuildSQL 部分移动到 CQtEvents 类中。

于 2013-08-07T15:51:45.223 回答