2013-08-08 4 views
5

Zajmuję się tworzeniem aplikacji Excel (2010+) przy użyciu języka VBA i napotkam problem, w którym funkcja zdarzenia AfterRefresh nie jest wywoływana po zakończeniu wykonywania kwerendy.Excel VBA - funkcja QueryTable AfterRefresh nie jest wywoływana po zakończeniu odświeżania

Nie byłem w stanie znaleźć wielu przyzwoitych zasobów ani dokumentacji, jak uruchomić tę funkcję zdarzenia w module klasy. Zdecydowałem się użyć trasy projektowania modułu klasy zamiast umieszczania procedur obsługi zdarzeń w arkuszu po otrzymaniu odpowiedzi na wcześniejsze pytanie dotyczące QueryTables (tutaj: Excel VBA AfterRefresh).

Oto kod dla mojego moduł klasy zwane CQtEvents

Option Explicit 

Private WithEvents mQryTble As Excel.QueryTable 
Private msOldSql As String 

' Properties 
Public Property Set QryTble(ByVal QryTable As QueryTable): Set mQryTble = QryTable: 
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 Class_Initialize() 
    MsgBox "CQtEvents init" 
End Sub 

' Resets the query sql to the original unmodified sql statement 
' This method is invoked when the Refresh thread finishes executing 
Private Sub mQryTble_AfterRefresh(ByVal Success As Boolean) 
    ' Problem is here 
    ' This function is never called :(Even if the query successfully runs 
    Me.QryTble.CommandText = Me.OldSql 

End Sub 

Oto szybki zrzut kodzie tworzy instancję tej klasy, znajdzie odpowiednie QueryTable, następnie wywołuje Odśwież

Option Explicit 

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

'From MGLOBALS 
cacheSheetName = "Cache" 
Set cacheSheet = Worksheets(cacheSheetName) 

Dim querySheet As Worksheet 
Dim interface As Worksheet 
Dim classQtEvents As CQtEvents 

Set querySheet = Worksheets("QTable") 
Set interface = Worksheets("Interface") 
Set classQtEvents = New CQtEvents 

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 
sqlQueryString = qt.CommandText 
Set classQtEvents.QryTble = qt 
classQtEvents.OldSql = sqlQueryString ' Cache the original query string 


QueryBuilder.BuildSQLQueryStringFromInterface interface, sqlQueryString 

' Test message 
MsgBox sqlQueryString 
qt.CommandText = sqlQueryString 

If Not qt Is Nothing Then 
    qt.Refresh 
Else 
    ' ... Error handling code here... 
End If 


''' CLEAN UP ''' 

' Free the dictionary 
Set qtDict = Nothing 

End Sub 

Tutaj znajduje się zrzut ekranu struktury modułu http://imgur.com/8fUcfLV

Moja pierwsza myśl na temat tego, co może być problemem, to przekazanie QueryTable według wartości. Nie jestem najbardziej doświadczonym programistą VBA, ale rozumowałem, że stworzy to kopię i wywoła zdarzenie na niepowiązanym ze sobą stole. Jednak tak się nie stało i przekazanie przez Reference nie rozwiązało problemu.

Również zapytanie jest poprawnie uruchamiane, ponieważ dane są poprawnie wyświetlane i odświeżane.

EDIT dodałem funkcję zdarzeń BeforeRefresh do modułu klasy CQtEvents i potwierdzone ta funkcja jest wywoływana raz Refresh nazywa

Private Sub mQryTble_BeforeRefresh(Cancel As Boolean) 
    MsgBox "Start of BeforeRefresh" 
End Sub 

Jak mogę zmienić ten kod dostać mój QueryTable od tego QTableModule za RefreshDataQuery() Podrzędna procedura wywoływania funkcji AfterRefresh po pomyślnym uruchomieniu zapytania?

+0

ale nie ma 'MsgBox' w twoim zdarzeniu" AfterRefresh ". Skąd wiesz, czy jest gotowy/odświeżony? Nie widzę, żeby kod sprawdzał status tego wydarzenia. Możesz dodać "MsgBox" do zdarzenia, aby sprawdzić, jak działa (jeśli). –

+0

@KazJaw Umieściłem MsgBox w funkcji zdarzenia i potwierdziłem, że nie jest wywoływane. Innym sposobem, który mogę potwierdzić, jest przeglądanie kwerendy QueryTable w celu sprawdzenia, czy oryginał został przywrócony, co właśnie robiłem zamiast używać MsgBox. –

+0

zachować okno komunikatu w ramach zdarzenia "AfterRefresh" i wywołać odświeżenie w głównej podsieci w ten sposób: 'qt.Refresh false'. Czy otrzymujesz teraz MsgBox? –

Odpowiedz

3

Jak złapać AfterRefresh event z QueryTable?

Wyjaśnienie: w twojej sytuacji, zanim wydarzenie zostało zwolnione, straciłeś referencję swojego QueryTable, ustawiając go na nic, gdy zakończyłeś czyszczenie lub procedurę.

Ogólne rozwiązanie: musisz mieć pewność, że twój kod nadal działa i/lub musisz zachować wszelkie odniesienia do twojego QueryTable.

Pierwsze rozwiązanie. Dzwoniąc QT.Refresh method ustawić parametr do false w ten sposób:

qt.Refresh false 

który zatrzyma dalsze wykonywanie kodu aż qt jest odświeżany. Ale nie uważam tego rozwiązania za najlepsze.

Drugie rozwiązanie. Udostępnij swoją publiczną i po zakończeniu RefreshDataQuery sub sprawdź stan za pomocą innego kodu.

  1. w CQtEvents class module dodać następującą zmienną publiczną:

    Public Refreshed As Boolean 
    
  2. w swojej BeforeRefresh event dodać to:

    Refreshed = False 
    
  3. w swojej AfterRefresh event dodać ten wiersz kodu:

    Refreshed = True 
    
  4. Udostępnij publicznie swoją deklarację classQtEvents variable. Umieścić to przed Sub RefreshDataQuery()

    Public classQtEvents as CQtEvents 
    

ale usunąć odpowiednie oświadczenie z poziomu sub.

Teraz, nawet twój okręt podwodny jest skończony, będziesz mógł sprawdzić status odświeżenia, sprawdzając .Refreshed property. Możesz to zrobić w Natychmiastowym lub w innym Sub. To powinno działać do natychmiastowego:

Debug.Print classQtEvents.Refreshed 

3rd rozwiązanie. (trochę podobne do pierwszego) Wykonaj kroki od 1 do 3 z drugiego rozwiązania. Po wywołaniu qt.Refresh method można dodać tę pętlę, która zatrzyma dalsze wykonywanie kodu aż qt jest odświeżana:

'your code 
If Not qt Is Nothing Then 
    qt.Refresh 
Else 
    ' ... Error handling code here... 
End If 
'checking 
Do Until classQtEvents.Refreshed 
    DoEvents 
Loop 

ostatnia uwaga. Mam nadzieję, że nie pomieszałem qt variable z classQtEvents variable. Nie próbowałem i testowałem żadnego rozwiązania przy użyciu twoich zmiennych, ale napisałem wszystko powyżej z odniesieniem do kodu, którego używam.

+0

@Paul, myślę, że zrobiłem ... –

+0

Dziękuję, to jest bardzo pomocne. Podsumowując, ponieważ moja procedura została zakończona przed zakończeniem wykonywania kwerendy, utraciłem wszystkie odwołania do QueryTable, które nie prowadzą do oddzwaniania AfterRefresh? –

+0

tak, dokładnie tego oczekuję (nawet nie widzę pełnego kodu) –

1

Można znaleźć repozytorium github, które demonstruje minimalny kod wymagany do uzyskania tego działającego kodu: here.

Jak już wspomniano, jeśli Twój moduł obsługi zdarzeń nie znajduje się w zasięgu lub utracono odniesienie do QueryTable, nie będzie możliwe przechwycenie zdarzenia. Kluczowymi czynnikami dla zapewnienia złapiesz zdarzenie to:

  1. Zadeklaruj zmienną globalną twoich obsługi zdarzeń klasa modułu typu poza żadnej podprogramy/metod, w górnej części pliku (wybrałem ThisWorkbook plik).

  2. Dodawanie obsługi zdarzeń Workbook_Open i wystąpienia tam tej zmiennej, dzięki czemu jest ona dostępna natychmiast i pozostanie w zakresie (ponieważ jest to globalna).

  3. W tym momencie lub w dowolnym punkcie niższego rzędu, gdy interesuje Cię QueryTable, przekaż tę QueryTable do instancji globalnej, aby połączyć swoje zdarzenia.

(zajęło mi kilka próbuje dowiedzieć się tego sam, kiedy ktoś wskazał mi w tym kierunku jako odpowiedź na this question.)