2013-08-08 32 views
5

Tôi đang phát triển ứng dụng Excel (2010+) sử dụng VBA và đã gặp sự cố khi chức năng sự kiện AfterRefresh không được gọi khi truy vấn kết thúc thực thi.Excel VBA - chức năng QueryTable AfterRefresh không được gọi sau khi Refresh hoàn thành

Tôi chưa thể tìm thấy nhiều tài nguyên hoặc tài liệu phong nha để biết cách kích hoạt chức năng sự kiện này trong Mô-đun lớp học. Tôi quyết định sử dụng tuyến đường thiết kế mô-đun lớp thay vì đặt trình xử lý sự kiện trong trang tính sau khi nhận được câu trả lời cho câu hỏi trước đó về QueryTables (được tìm thấy tại đây Excel VBA AfterRefresh).

Đây là mã cho Mô-đun lớp tôi gọi 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 

Đây là một ảnh chụp nhanh của mã này tạo ra một thể hiện của lớp này, tìm thấy một QueryTable có liên quan, sau đó gọi Refresh

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 

Ngoài ra, đây là ảnh chụp màn hình Cấu trúc mô-đun http://imgur.com/8fUcfLV

Suy nghĩ đầu tiên của tôi về vấn đề có thể là gì khi truy cập QueryTable theo giá trị. Tôi không phải là nhà phát triển VBA có kinh nghiệm nhất, nhưng tôi đã lý luận điều này sẽ tạo một bản sao và gọi sự kiện trên một bảng không liên quan. Tuy nhiên, đây không phải là trường hợp và đi qua tham chiếu cũng không khắc phục được vấn đề.

Ngoài ra truy vấn được xác nhận để chạy thành công khi dữ liệu được hiển thị chính xác và đang được làm mới.

EDIT tôi đã thêm chức năng sự kiện BeforeRefresh để CQtEvents lớp Module và xác nhận chức năng này được gọi là một lần Refresh được gọi

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

Làm thế nào tôi có thể thay đổi mã này được QueryTable tôi từ RefreshDataQuery của QTableModule() Thường trình con để có hàm AfterRefresh được gọi khi truy vấn được chạy thành công?

+0

nhưng không có 'MsgBox' trong sự kiện' AfterRefresh' của bạn Sub. Vì vậy, làm thế nào để bạn biết nếu nó đã hoàn thành/làm mới? Tôi không thể nhìn thấy mã đang kiểm tra trạng thái của sự kiện đó. Bạn có thể thêm 'MsgBox' vào sự kiện để kiểm tra xem (nếu) nó hoạt động như thế nào. –

+0

@KazJaw Tôi đặt một MsgBox trong chức năng sự kiện và xác nhận nó không được gọi. Ngoài ra, một cách khác tôi có thể xác nhận là xem truy vấn QueryTable để xác minh rằng bản gốc đã được khôi phục, đó là những gì tôi đã làm thay vì sử dụng một MsgBox. –

+0

giữ hộp thông báo trong 'sự kiện AfterRefresh' và gọi lại làm mới trong chính bạn theo cách sau:' qt.Refresh false'. Bạn có nhận được MsgBox ngay bây giờ không? –

Trả lời

3

Cách nắm bắt số AfterRefresh event của QueryTable?

Giải thích: trong trường hợp của bạn, trước khi sự kiện được kích hoạt, bạn đã mất tài liệu tham khảo QueryTable bằng cách đặt nó thành không có gì khi bạn làm sạch hoặc kết thúc thủ tục.

Giải pháp chung: bạn phải chắc chắn rằng mã của bạn vẫn đang chạy và/hoặc bạn cần giữ bất kỳ tham chiếu nào đến số QueryTable của mình.

Giải pháp thứ nhất. Khi gọi QT.Refresh method thiết lập các tham số để false theo cách này:

qt.Refresh false 

mà sẽ ngừng thực thi mã hơn nữa cho đến khi qt của bạn được làm mới. Nhưng tôi không coi giải pháp này là giải pháp tốt nhất.

Giải pháp thứ hai. Đặt classQtEvents variable công khai của bạn và sau RefreshDataQuery sub hoàn tất kiểm tra trạng thái bằng một số mã khác.

  1. trong bạn CQtEvents class module thêm biến nào sau đây:

    Public Refreshed As Boolean 
    
  2. trong bạn BeforeRefresh event thêm này:

    Refreshed = False 
    
  3. trong AfterRefresh event bạn thêm dòng mã này:

    Refreshed = True 
    
  4. Đặt công khai tuyên bố classQtEvents variable của bạn. Đặt điều này trước khi Sub RefreshDataQuery()

    Public classQtEvents as CQtEvents 
    

nhưng loại bỏ tuyên bố thích hợp từ bên trong tiểu của bạn.

Bây giờ, ngay cả khi phụ của bạn đã hoàn tất, bạn sẽ có thể kiểm tra trạng thái của giải khát bằng cách kiểm tra .Refreshed property. Bạn có thể làm điều đó ngay lập tức hoặc trong Sub khác. Điều này sẽ hoạt động cho Immediate:

Debug.Print classQtEvents.Refreshed 

Giải pháp thứ ba. (hơi giống với số 1) Thực hiện theo các bước từ 1 đến 3 từ giải pháp thứ 2. Sau khi bạn gọi qt.Refresh method bạn có thể thêm vòng lặp này mà sẽ ngừng thực thi mã hơn nữa cho đến khi qt được làm mới:

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

cuối cùng nhận xét. Tôi hy vọng tôi đã không trộn lẫn qt variable với classQtEvents variable. Tôi đã không cố gắng và thử nghiệm bất kỳ giải pháp bằng cách sử dụng các biến của bạn nhưng đã viết tất cả ở trên với tham chiếu đến mã tôi sử dụng.

+0

@Paul, tôi nghĩ rằng tôi đã làm ... –

+0

Cảm ơn bạn điều này là rất hữu ích. Vì vậy, trong tóm tắt, bởi vì thủ tục của tôi đã hoàn thành thực hiện trước khi truy vấn hoàn thành thực hiện, tôi bị mất tất cả các tài liệu tham khảo để QueryTable mà dẫn đến không có cuộc gọi lại AfterRefresh? –

+0

có, đó là chính xác những gì tôi mong đợi (thậm chí tôi không thể nhìn thấy hoàn thành mã của bạn) –

1

Một repo github thể hiện mã tối thiểu cần thiết để làm việc này có thể được tìm thấy here.

Như đã đề cập, nếu trình xử lý sự kiện của bạn không nằm trong phạm vi hoặc tham chiếu QueryTable của bạn bị mất, bạn sẽ không bắt sự kiện. Các yếu tố quan trọng để đảm bảo bạn nắm bắt sự kiện này bao gồm:

  1. Khai báo một biến toàn cầu của lớp mô-đun kiện xử lý của bạn loại ngoài của bất kỳ chương trình con/phương pháp, ở phía trên cùng của một tập tin (tôi chọn ThisWorkbook tập tin).

  2. Thêm Workbook_Open trình xử lý sự kiện và khởi tạo biến đó ở đó, để nó có sẵn ngay lập tức và sẽ vẫn nằm trong phạm vi (vì toàn cầu).

  3. Tại thời điểm đó hoặc tại bất kỳ điểm hạ lưu nào khi bạn có QueryTable mà bạn quan tâm, hãy chuyển QueryTable đó đến cá thể chung để kết nối các sự kiện của nó.

(Tôi đã mất một vài lần để tìm hiểu điều này, khi ai đó chỉ cho tôi theo hướng này làm câu trả lời cho this question.)