ActiveX DLL clsUDF   strona główna:
A po co ten Excel ;-)
 
    Od jakiegoś czasu chciałem stworzyć własny dodatek z najróżniejszymi rzeczami które są mi potrzebne do pracy, jednak    
dodatek inny niż standardowe xla. Czemu? Chciałbym żeby kod funkcji i procedur był możliwie dobrze zabezpieczony choćby   dodatek można pobrać:
przed niechcianym kopiowaniem itp... Uznałem że najlepiej będzie stworzyć dodatek Automatyzacji, a że teoretycznie zadania   tkCOMAddIn.dll
realizowane za pomocą funkcji/procedur wykonywanych ze skompilowanej biblioteki powinny się wykonywać szybciej utwierdza  
mnie w przekonaniu że to właściwa droga.    
    Docelowo chciałbym ten dodatek rozbudowywać jednak dziś pokażę jedynie funkcje użytkownika, które w nim zamieszczę.  
Plan tego artykułu:  
 - Tworzenie ActiveX DLL w VB6  
 - Rejestrowanie dodatku automatyzacji  
 - Wstawienie dodatku do Excela  
 - Funkcja DefPozaTbl   
 - Funkcja DanePodWarunkiem.  
 - Funkcja VBALike  
 - Funkcje SortowanieBabelkoweV i  SortowanieBabelkoweH  
 - Funkcje tblUnionZakresowV i tblUnionZakresowH  
 - Funkcje dot. Unikatów  
 - Porównanie czasów wykonania VBA i DLL.  
 
Krótko na temat tworzenia ActiveX DLL.  
 
    Aby utworzyć dodatek automatyzacji (dll) korzystam z Microsoft Visual Basic 6.0 (VB6)  
 - Po włączeniu programu należy wybrać: Menu File / New Project -> ActiveX DLL  
 - W oknie Project Klikamy na Project1 i w oknie Properties nadajemy nazwę (Name) dla naszej biblioteki (moja tkCOMAddIn)  
 - to samo z nazwą modułu Class. (clsConnection)  
 - Następnie Menu Project / References. Dodajemy referencję do Microsoft Add-In Designer  
 
 - do modułu clsConnection kopiujemy taki kod:  
 
Option Explicit                        
Implements IDTExtensibility2    
          
Private Sub IDTExtensibility2_OnConnection(ByVal Application As Object, _    
                            ByVal ConnectMode As AddInDesignerObjects.ext_ConnectMode, _    
                            ByVal AddInInst As Object, _    
                            custom() As Variant)    
      
    Set modPublic.xlApp = Application    
     
End Sub    
     
Private Sub IDTExtensibility2_OnDisconnection(ByVal RemoveMode As AddInDesignerObjects.ext_DisconnectMode, _  
                            custom() As Variant)    
    Set modPublic.xlApp = Nothing    
     
End Sub    
     
Private Sub IDTExtensibility2_OnStartupComplete(custom() As Variant)    
    'Komentarz, aby VB nie usunął metody    
End Sub    
     
Private Sub IDTExtensibility2_OnBeginShutdown(custom() As Variant)    
    'Komentarz, aby VB nie usunął metody    
End Sub    
     
Private Sub IDTExtensibility2_OnAddInsUpdate(custom() As Variant)    
    'Komentarz, aby VB nie usunął metody    
End Sub                          
 
Tworzę obiekt Application jako publiczny obiekt zadeklarowany w module Standardowym modPublic a w nim.  
 
Public xlApp As Object 'Excel.Application                
 
Dodaję kolejny moduł class i nadaję mu nazwę clsUDF a w nim funkcje które chciałbym móc wykorzystywać w Excelu z tego   
dodatku.  
 
Następnie tworzymy bibliotekę dll i zapisujemy projekt.  
 - Menu File / Make tkCOMAddIn.dll  
 - Menu File / Save Project As..  
 
Zapis do pliku dll od razu podejmie próbę zarejestrowania taj biblioteki w systemie. Jeżeli jednak chcemy żeby działała na innej    Rejestrowanie formantu ActiveX (.ocx) ręcznie
maszynie trzeba ją tam zarejestrować. Robi się to poprzez program Microsoft Register Server (Regsvr32.exe) --->  
 
   Mamy więc zarejestrowany dodatek automatyzacji (dll). Żeby korzystać z jego zawartości trzeba:  
 - korzystanie z publicznych funkcji zdefiniowanych w dodatku z poziomu Arkusza (jako funkcje arkuszowe)  
Excel 2003: Zakładka: Narzędzia / Dodatku / Przycisk: Automatyzacja / W oknie dialogowym należy odszukać i wybrać:  
                              tkCOMAddIn.clsConnection  
                              tkCOMAddIn.clsUDF  
 
Excel 2007/2010: Włączanie lub wyłączanie dodatków w programach pakietu Office  
 
 - z poziomu VBA  
     - poprzez "wczesne wiązanie": VBE / Tools / References / klik przy tkCOMAddIn  
       w procedurze / funkcji:  
 
    Dim objCls As tkCOMAddIn.clsUDF  
    Set objCls = new tkCOMAddIn.clsUDF  
    With objCls  
        tbl = .DanePodWarunkiem(tbl, .VBALike(tblWar, "A*"))  
 
     - poprzez "późne wiązanie"  
 
    Dim objCls As Object 'tkCOMAddIn.clsUDF  
    Set objCls = CreateObject("tkCOMAddIn.clsUDF")  
    With objCls  
        tblWyn2 = .DanePodWarunkiem(tbl, .VBALike(tblWar, "A*"))  
 
Teraz co do zawartości clsUDF  
 
   Funkcja DefPozaTbl   
 
Public Function DefPozaTbl(tbl As Variant, _                
                           Optional vArg As Variant = vbNullString)    
    Dim w As Long, k As Integer    
    Dim nowaTbl() As Variant    
    Dim minW As Long, maxW As Long    
    Dim minK As Integer, maxK As Integer    
    Dim i As Long, j As Integer    
     
    With xlApp.Caller    
        w = .Rows.Count    
        k = .Columns.Count    
    End With    
        
    minW = LBound(tbl, 1): maxW = UBound(tbl, 1)    
    minK = LBound(tbl, 2): maxK = UBound(tbl, 2)    
     
    If maxW <= w Or maxK <= k Then    
        ReDim Preserve nowaTbl(minW To w, minK To k)    
        For i = minW To w    
            For j = minK To k    
                If i > maxW Or j > maxK Then    
                    nowaTbl(i, j) = vArg    
                Else    
                    nowaTbl(i, j) = tbl(i, j)    
                End If    
            Next    
        Next    
        DefPozaTbl = nowaTbl    
    Else    
        DefPozaTbl = tbl    
    End If    
End Function                        
 
Jest to przeróbka procedury DefiniowanieZawartosciZakresuPozaTablica. Procedura tą zagnieżdżałem z kodach funkcji jednak    Definiowanie zawartosci zakresu poza tablicą
często przydaje się również przy innych formułach tablicowych gdzie zwracana tablica jest dynamiczna a poza nią formuła zwraca  
błąd dopasowania wielkości zwracanej tablicy w porównaniu do miejsca na nią przeznaczoną.  
Np.:  
    Przeznaczmy zakres 5x5 (A1:E5) na taką tablicę: ={1;2;3;4\11;12;13;14} otrzymamy  
 
  A B C D E  
1 1 2 3 4 #N/D!  
2 11 12 13 14 #N/D!  
3 #N/D! #N/D! #N/D! #N/D! #N/D!  
4 #N/D! #N/D! #N/D! #N/D! #N/D!  
5 #N/D! #N/D! #N/D! #N/D! #N/D!  
6            
 
Zapiszmy jednak: =DefPozaTbl({1;2;3;4\11;12;13;14};"brak")  
 
  A B C D E  
1 1 2 3 4 brak  
2 11 12 13 14 brak  
3 brak brak brak brak brak  
4 brak brak brak brak brak  
5 brak brak brak brak brak  
6            
 
Drugi argument jest opcjonalny. Pominięty zwraca poza tablicą pusty ciąg.  
 
    Funkcja Transponuj2. Nieodzownie konieczna przy tworzeniu dynamicznych tablic. Wykorzystywana zarówno jako "pod funkcja"   Funkcja Transponuj2
funkcji użytkownika które zwracają nieokreśloną od początku tablicę, jak i jako samodzielną funkcja.  
 
    Funkcja DanePodWarunkiem.  
 
Public Function DanePodWarunkiem(tblDane As Variant, vWarunki As Variant) As Variant      
    Dim tbl As Variant, i As Long, j As Integer    
    Dim tblWyniki() As Variant, w As Long, k As Integer    
     
    tbl = tblDane: k = UBound(tbl, 2)    
     
    For i = 1 To UBound(tbl, 1)    
        If vWarunki(i, 1) Then    
            w = w + 1    
            ReDim Preserve tblWyniki(1 To k, 1 To w)    
            For j = 1 To k    
                tblWyniki(j, w) = tbl(i, j)    
            Next    
        End If    
    Next    
    If w > 0 Then    
        DanePodWarunkiem = Transponuj2(tblWyniki)    
    End If    
End Function                        
 
Funkcja ta to taki mój filtr działający na dowolnych, czasem nawet dość skomplikowanych warunkach. Przykład podam wraz z   Funkcja DanePodWarunkiem
następną funkcją VBALike. Zawsze mi brakowało tego w Excelu. Może by to i jakoś obszedł, ale tak jest po prostu łatwiej :-)  
 
Public Function VBALike(vDane As Variant, strLike As String) As Variant          
    Dim tbl As Variant, tblWyniki() As Boolean    
    Dim i As Long    
        
    tbl = vDane    
    ReDim tblWyniki(1 To UBound(tbl), 1 To 1)    
        
    For i = 1 To UBound(tbl)    
        tblWyniki(i, 1) = tbl(i, 1) Like strLike    
    Next    
    VBALike = tblWyniki    
End Function                        
 
Mamy taki przykład danych:  
 
  A B C D E  
1 Lp Test Test2 Wart kod  
2 1 A1 Test1 323,35 500-09-405-00  
3 2 B1 Test2 274,9 500-05-404-09  
4 3 C1 Test3 332,76 500-09-401-15  
5 4 A2 Test4 318,31 500-04-406-19  
6 5 B2 Test5 252,25 500-04-402-11  
7 6 C2 Test6 51,89 500-04-401-12  
8 7 A2 Test7 119,48 500-08-405-17  
9 8 B2 Test8 226,33 500-09-406-20  
10 9 C2 Test9 240,42 500-04-403-01  
11 10 A3 Test10 375,67 500-08-406-06  
12 11 B3 Test11 72,15 500-04-403-11  
13 12 C3 Test12 257,1 500-02-401-06  
14 13 A3 Test1 301,21 500-00-403-12  
15 14 B3 Test2 102,32 500-06-405-18  
16 15 C3 Test3 398,88 500-04-406-16  
17 16 A4 Test4 218,78 500-07-403-18  
18 17 B4 Test5 294,01 500-09-401-07  
19 18 C4 Test6 304,39 500-10-403-03  
20 19 A4 Test7 227,73 500-03-406-17  
21 20 B4 Test8 266,34 500-01-402-07  
 
Chciałbym z tych danych wyfiltrować do nowego zakresu te rekordy które w kol.kod mają "*401*"  
dane takie zwróci formuła =DefPozaTbl(DanePodWarunkiem(A2:E21;VBALike(E2:E21;"*401*")))  
 
3 C1 Test3 332,76 500-09-401-15  
6 C2 Test6 51,89 500-04-401-12  
12 C3 Test12 257,1 500-02-401-06  
17 B4 Test5 294,01 500-09-401-07  
           
           
           
           
 
Puste komórki poniże danych oznaczają że formuła została wprowadzona do większego zakresu niż rozmiary tablicy z wynikami.  
Normalnie w takich sytuacjach do tych komórek zostałby zwrócony błąd. Tu zawartość tej części tablicy zostaje wypełniona  
zgodnie z drugim arg. f. DefPozaTbl.  
 
Jednak chcielibyśmy żeby zwrócone dane były posortowane malejąco po czwartej kolumnie (wart)  
Potrzebujemy zatem funkcji SortowanieBabelkoweV.   Funkcja SortowanieBabelkowe2D
 
Public Function SortowanieBabelkoweV(rngDane As Variant, _            
                                     CzyRosnaco As Boolean, _    
                                     ParamArray nrKolumn() As Variant) As Variant    
    Dim tabl As Variant    
    Dim nr As Variant    
    Dim xMax As Long, yMax As Integer, jTbl As Integer    
    Dim i As Long, j As Long, a As Long    
    Dim Temp    
               
    tabl = rngDane    
    xMax = UBound(tabl, 1): yMax = UBound(tabl, 2)    
    For Each nr In nrKolumn    
        a = 2    
        For i = 2 To xMax    
            For j = xMax To a Step -1    
                If CzyRosnaco Then    
                    If tabl(j - 1, nr) > tabl(j, nr) Then    
                        For jTbl = 1 To yMax    
                            Temp = tabl(j - 1, jTbl)    
                            tabl(j - 1, jTbl) = tabl(j, jTbl)    
                            tabl(j, jTbl) = Temp    
                        Next    
                    End If    
                Else    
                    If tabl(j - 1, nr) < tabl(j, nr) Then    
                        For jTbl = 1 To yMax    
                            Temp = tabl(j - 1, jTbl)    
                            tabl(j - 1, jTbl) = tabl(j, jTbl)    
                            tabl(j, jTbl) = Temp    
                        Next    
                    End If    
                End If    
            Next    
            a = a + 1    
        Next    
    Next    
        
    SortowanieBabelkoweV = tabl    
End Function                        
 
Naszą formułe rozbudujemy o koleją funkcję..  
=DefPozaTbl(SortowanieBabelkoweV(DanePodWarunkiem(A2:E21;VBALike(E2:E21;"*401*"));FAŁSZ;4))  
 
3 C1 Test3 332,76 500-09-401-15  
17 B4 Test5 294,01 500-09-401-07  
12 C3 Test12 257,1 500-02-401-06  
6 C2 Test6 51,89 500-04-401-12  
 
 
Możemy też potrzebować sortować dane, czy część danych po wierszu/wierszach  
Przyda się funkcja Sortowania w poziomie - SortowanieBabelkoweH   Sortowanie bąbelkowe tablicy 2D Horizontal po wielu wierszach
 
Public Function SortowanieBabelkoweH(rngDane As Variant, _            
                                     CzyRosnaco As Boolean, _    
                                     ParamArray nrWiersza() As Variant) As Variant    
    Dim tabl As Variant    
    Dim nr As Variant    
    Dim xMax As Long, yMax As Integer, jTbl As Integer    
    Dim i As Long, j As Long, a As Long    
    Dim Temp    
               
    tabl = rngDane    
    xMax = UBound(tabl, 1): yMax = UBound(tabl, 2)    
    For Each nr In nrWiersza    
        a = 2    
        For i = 2 To yMax    
            For j = yMax To a Step -1    
                If CzyRosnaco Then    
                    If tabl(nr, j - 1) > tabl(nr, j) Then    
                        For jTbl = 1 To xMax    
                            Temp = tabl(jTbl, j - 1)    
                            tabl(jTbl, j - 1) = tabl(jTbl, j)    
                            tabl(jTbl, j) = Temp    
                        Next    
                    End If    
                Else    
                    If tabl(nr, j - 1) < tabl(nr, j) Then    
                        For jTbl = 1 To xMax    
                            Temp = tabl(jTbl, j - 1)    
                            tabl(jTbl, j - 1) = tabl(jTbl, j)    
                            tabl(jTbl, j) = Temp    
                        Next    
                    End If    
                End If    
            Next    
            a = a + 1    
        Next    
    Next    
        
    SortowanieBabelkoweH = tabl    
End Function                        
 
Formuła: =DefPozaTbl(SortowanieBabelkoweH(Transponuj2(DanePodWarunkiem(A2:D21;VBALike(E2:E21;"*401*")));PRAWDA;3))  
Zwróci nam część danych pod warunkiem:  
 - DanePodWarunkiem(A2:D21;VBALike(E2:E21;"*401*"))  
Dane Transponujemy i Sortujemy malejąco po trzecim wierszu.  
 
12 3 17 6      
C3 C1 B4 C2      
Test12 Test3 Test5 Test6      
257,1 332,76 294,01 51,89      
 
 
Jednak nieraz dane nie są w jednym miejscu. Przydałaby się funkcja która stworzy jedną tablicę z wielu zestawów danych, żeby  
inna funkcja mogła pracować na danych w kilku miejscach tak jakby był to jeden zakres.  
Po to powstały funkcje tblUnionZakresowV i tblUnionZakresowH  
 
Public Function tblUnionZakresowV(ParamArray Zakresy() As Variant) As Variant        
    Dim zakres As Variant    
    Dim wTbl As Long, kTbl As Integer    
    Dim tblWyniki() As Variant, w As Long    
    Dim vDane As Variant, iTbl As Long, jTbl As Integer    
    Dim rngDane As Object    
        
    For Each zakres In Zakresy    
        With zakres    
            Select Case TypeName(zakres)    
                Case "Range":    
                    Set rngDane = zakres    
                    With rngDane    
                        wTbl = wTbl + .Rows.Count    
                        If kTbl < .Columns.Count Then kTbl = .Columns.Count    
                    End With    
                    Set rngDane = Nothing    
                Case "Variant()"    
                    wTbl = wTbl + UBound(zakres, 1)    
                    If kTbl < UBound(zakres, 2) Then kTbl = UBound(zakres, 2)    
                Case Else    
                    wTbl = wTbl + 1    
            End Select    
        End With    
    Next    
     
    ReDim tblWyniki(1 To wTbl, 1 To kTbl)    
    For Each zakres In Zakresy    
        vDane = zakres    
        If IsArray(vDane) Then    
            For iTbl = LBound(vDane, 1) To UBound(vDane, 1)    
                w = w + 1    
                For jTbl = LBound(vDane, 2) To kTbl    
                    If jTbl <= UBound(vDane, 2) Then    
                        tblWyniki(w, jTbl) = vDane(iTbl, jTbl)    
                    Else    
                        tblWyniki(w, jTbl) = vbNullString    
                    End If    
                Next    
            Next    
        Else    
            w = w + 1    
            tblWyniki(w, 1) = IIf(IsEmpty(vDane), vbNullString, vDane)    
            For jTbl = 2 To kTbl    
                tblWyniki(w, jTbl) = vbNullString    
            Next    
        End If    
    Next    
        
    tblUnionZakresowV = tblWyniki    
End Function    
     
Public Function tblUnionZakresowH(ParamArray Zakresy() As Variant) As Variant    
    Dim zakres As Variant    
    Dim wTbl As Long, kTbl As Integer    
    Dim tblWyniki() As Variant, k As Integer    
    Dim vDane As Variant, iTbl As Long, jTbl As Integer    
    Dim rngDane As Object    
        
    For Each zakres In Zakresy    
        With zakres    
            Select Case TypeName(zakres)    
                Case "Range":    
                    Set rngDane = zakres    
                    With rngDane    
                        kTbl = kTbl + .Columns.Count    
                        If wTbl < .Rows.Count Then wTbl = .Rows.Count    
                    End With    
                    Set rngDane = Nothing    
     
                Case "Variant()"    
                    kTbl = kTbl + UBound(zakres, 2)    
                    If wTbl < UBound(zakres, 1) Then wTbl = UBound(zakres, 1)    
                Case Else    
                    kTbl = kTbl + 1    
            End Select    
        End With    
    Next    
     
    ReDim tblWyniki(1 To wTbl, 1 To kTbl)    
    For Each zakres In Zakresy    
        vDane = zakres    
        If IsArray(vDane) Then    
            For jTbl = LBound(vDane, 2) To UBound(vDane, 2)    
                k = k + 1    
                For iTbl = 1 To wTbl    
                    If iTbl <= UBound(vDane, 1) Then    
                        tblWyniki(iTbl, k) = vDane(iTbl, jTbl)    
                    Else    
                        tblWyniki(iTbl, k) = vbNullString    
                    End If    
                Next    
            Next    
        Else    
            k = k + 1    
            tblWyniki(1, k) = IIf(IsEmpty(vDane), vbNullString, vDane)    
            For iTbl = 2 To wTbl    
                tblWyniki(iTbl, k) = vbNullString    
            Next    
        End If    
    Next    
        
    tblUnionZakresowH = tblWyniki    
End Function    
                           
 
Poniżej naszych danych stwórzmy ich "dalszy ciąg".   
 
  A B C D E  
24 21 A1 Test1 240,42 500-06-405-18  
25 22 B1 Test2 375,67 500-04-406-16  
26 23 C1 Test3 72,15 500-07-403-18  
27 24 A2 Test4 257,1 500-09-401-07  
28 25 B2 Test5 301,21 500-10-403-03  
29 26 C2 Test6 102,32 500-06-405-18  
30 27 A2 Test7 398,88 500-04-406-16  
31 28 B2 Test8 398,88 500-07-403-18  
 
Teraz chcielibyśmy wiedzieć jaka jest sumaryczna wartość (kol.Wart) jeżeli kol.Test jest "Like" A*  
=SUMA.ILOCZYNÓW(VBALike(tblUnionZakresowV(B2:B21;B24:B31);"A*")*(tblUnionZakresowV(D2:D21;D24:D31)))  
 
Może się jednak okazać że "proste" Like nam nie wystarczy. Czasem najłatwiej jest coś określić wyrażeniami regularnymi. Do   Funkcje Użytkownika oparte o Wyrażenia Regularne
mojego dodatku dodam funkcje które do wykonania określonego zadania używają właśnie RegExp, no i, co nie obojętne, mogą  
zwracać wyniki tablicowo.  
Będzie to zestaw funkcji: Fragment_RegExp, Replace_RegExp, ZgodneZWzorcem_RegExp  
    Pierwszym przykładem wykorzystania będzie: Zwrócić dane z obu zakresów które w kol.Test mają "A1" lub "A2"  
=DefPozaTbl(DanePodWarunkiem(tblUnionZakresowV(A2:E21;A24:E31);
ZgodneZWzorcem_RegExp(tblUnionZakresowV(B2:B21;B24:B31);"A(1|2)")))
 
Formuła zwróci..  
 
1 A1 Test1 323,35 500-09-405-00  
4 A2 Test4 318,31 500-04-406-19  
7 A2 Test7 119,48 500-08-405-17  
21 A1 Test1 240,42 500-06-405-18  
24 A2 Test4 257,1 500-09-401-07  
27 A2 Test7 398,88 500-04-406-16  
           
           
 
Wyrażenia regularne mogą nam tez posłużyć do sortowania danych po kluczu określonym przez użytkownika..  
Powiedzmy że chcemy posortować dane po kol.kod jednak nie po całej a po ostatnich dwóch liczbach tj np.: 500-07-403-18  
   Tablicę z trzecią liczbą kodów zwróci nam formuła =Fragment_RegExp(E2:E21;"\d+";3)  
   Tablicę z czwartą liczbą - analogiczna. Teraz jeżeli stworzymy poziomą unie zakresów i posortujemy ją po "dodatkowych"  
kolumnach… :-) Formuła mogłaby wyglądać tak:  
=SortowanieBabelkoweV(tblUnionZakresowH(A2:E21;Fragment_RegExp(E2:E21;"\d+";3);Fragment_RegExp(E2:E21;"\d+";4));
PRAWDA;7;6)
 
 
nie musimy zwracać wszystkich danych, wystarczy pięć pierwszych kolumn. Tj nasze dane źródłowe. Kolumny które zwracają  
funkcje oparte o Reg.Exp z danymi "przyklejonymi" do naszych danych źródłowych mogą nie być zwrócone. W ten sposób te  
dodatkowe kolumny pozostaną ukryte jednak będziemy mogli po nich sortować :-)  
Formuła zwraca taki zestaw danych:  
 
12 C3 Test12 257,1 500-02-401-06  
17 B4 Test5 294,01 500-09-401-07  
6 C2 Test6 51,89 500-04-401-12  
3 C1 Test3 332,76 500-09-401-15  
20 B4 Test8 266,34 500-01-402-07  
5 B2 Test5 252,25 500-04-402-11  
9 C2 Test9 240,42 500-04-403-01  
18 C4 Test6 304,39 500-10-403-03  
11 B3 Test11 72,15 500-04-403-11  
13 A3 Test1 301,21 500-00-403-12  
16 A4 Test4 218,78 500-07-403-18  
           
           
 
Zauważcie w jaki sposób posortowane są dane - jak rosną kody w części dotyczącej zadania ;-)  
 
Następną grupą funkcji są funkcji dotyczące unikatów i ich ilości.  
Ich szerszy opis wraz z przykładami wykorzystania można przejrzeć w artykułach na stronie www.excelperfect.pl   Unikaty excelperfect.pl
zmieniam jedynie podejście do definiowania zakresu poza tablicą wyników.  
 
Public Function Unikaty(rngDane As Variant) As Variant              
    Dim tblDane As Variant, iTbl As Long, vItem As Variant    
    Dim colUnikaty As New VBA.Collection    
    Dim tblWyniki() As Variant, w As Long    
        
    tblDane = rngDane    
    On Error Resume Next    
    For iTbl = 1 To UBound(tblDane)    
        vItem = tblDane(iTbl, 1)    
        If Len(vItem) > 0 Then    
            colUnikaty.Add Item:=vItem, _    
                           Key:=CStr(vItem)    
            If Err.Number <> 0 Then    
                Err.Clear    
            Else    
                w = w + 1    
                ReDim Preserve tblWyniki(1 To 1, 1 To w)    
                tblWyniki(1, w) = vItem    
            End If    
        End If    
    Next    
    On Error GoTo 0    
        
    If w > 0 Then    
        Unikaty = Transponuj2(tblWyniki)    
    End If    
        
End Function    
     
Public Function ileUnikatow(rngDane As Variant) As Variant    
    Dim tblDane As Variant, iTbl As Long, vItem As Variant    
    Dim colUnikaty As New VBA.Collection    
        
    tblDane = rngDane    
    On Error Resume Next    
    For iTbl = 1 To UBound(tblDane)    
        vItem = tblDane(iTbl, 1)    
        If Len(vItem) > 0 Then    
            colUnikaty.Add Item:=vItem, _    
                           Key:=CStr(vItem)    
        End If    
    Next    
    On Error GoTo 0    
    ileUnikatow = colUnikaty.Count    
     
End Function    
     
Public Function UnikatyPodWarunkiem(rngDane As Variant, _    
                             vWarunki As Variant) As Variant    
    Dim tblDane As Variant, iTbl As Long, vItem As Variant    
    Dim colUnikaty As New VBA.Collection    
    Dim tblWyniki() As Variant, w As Long    
        
    tblDane = rngDane    
    On Error Resume Next    
    For iTbl = 1 To UBound(tblDane)    
        If vWarunki(iTbl, 1) Then    
            vItem = tblDane(iTbl, 1)    
            colUnikaty.Add vItem, CStr(vItem)    
            If Err.Number = 0 Then    
                w = w + 1    
                ReDim Preserve tblWyniki(1 To 1, 1 To w)    
                tblWyniki(1, w) = tblDane(iTbl, 1)    
            Else    
                Err.Clear    
            End If    
        End If    
    Next    
    On Error GoTo 0    
        
    If w > 0 Then    
        UnikatyPodWarunkiem = Transponuj2(tblWyniki)    
    End If    
End Function    
     
Public Function ileUnikatowPodWarunkiem(rngDane As Variant, _    
                                 vWarunki As Variant) As Variant    
    Dim tblDane As Variant, iTbl As Long, vItem As Variant    
    Dim colUnikaty As New VBA.Collection    
        
    tblDane = rngDane    
    On Error Resume Next    
    For iTbl = 1 To UBound(tblDane)    
        If vWarunki(iTbl, 1) Then    
            vItem = tblDane(iTbl, 1)    
            colUnikaty.Add vItem, CStr(vItem)    
        End If    
    Next    
    On Error GoTo 0    
     
    ileUnikatowPodWarunkiem = colUnikaty.Count    
     
End Function    
     
Public Function UnikatowyRekord(rngDane As Variant, _    
                         ParamArray nrKol() As Variant) As Variant    
    Dim nr As Variant, bFlaga As Boolean    
    Dim tblDane As Variant, iTbl As Long, jTbl As Integer, strItem As String    
    Dim colUnikaty As New VBA.Collection    
    Dim tblWyniki() As Variant, w As Long, k As Integer    
        
    tblDane = rngDane: k = UBound(tblDane, 2)    
        
    If IsMissing(nrKol()) Then bFlaga = True    
        
    On Error Resume Next    
    For iTbl = 1 To UBound(tblDane, 1)    
        
        If Not bFlaga Then    
            For Each nr In nrKol    
                strItem = strItem & tblDane(iTbl, nr)    
            Next    
        Else    
            For jTbl = 1 To k    
                strItem = strItem & tblDane(iTbl, jTbl)    
            Next    
        End If    
                    
        colUnikaty.Add strItem, strItem    
        If Err.Number <> 0 Then    
            Err.Clear    
        Else    
            w = w + 1    
            ReDim Preserve tblWyniki(1 To k, 1 To w)    
            For jTbl = 1 To k    
                tblWyniki(jTbl, w) = tblDane(iTbl, jTbl)    
            Next    
        End If    
        strItem = vbNullString    
    Next    
    On Error Resume Next    
        
    If w > 0 Then    
        UnikatowyRekord = Transponuj2(tblWyniki)    
    End If    
     
End Function                        
 
Sprawdźmy jeszcze szybkość działania tych funkcji wywoływanych poprzez dodatek automatyzacji a wprost z projektu VBA.  
Teoretycznie funkcje wywoływane z takiego dodatku powinny być szybsze. Dodatek ten to skompilowana biblioteka dll, żeby  
działała funkcja z VBA musi być najpierw przetłumaczona "na język maszyn" a dopiero następnie wykonana. Sprawdźmy zatem   
jak to wygląda w praktyce..  
    Do testów posłuży nam procedura…  
 
Option Explicit                        
Declare Function GetTickCount Lib "Kernel32" () As Long    
     
Sub test()    
    Dim objCls As Object 'tkCOMAddIn.clsUDF    
    Dim tbl() As Variant, tblWar() As Variant    
    Dim i As Long, iMax As Long: iMax = 1000    
    Dim tblWyn1 As Variant, tblWyn2 As Variant    
        
    ReDim tbl(1 To iMax, 1 To 5): ReDim tblWar(1 To iMax, 1 To 1)    
        
    For i = 1 To iMax    
        tbl(i, 1) = i    
        tbl(i, 2) = Chr(Int((66 - 64 + 1) * Rnd + 64)) & Int(Rnd * 5)    
        tbl(i, 3) = "test" & i Mod 10    
        tbl(i, 4) = Round(Rnd * 400, 2)    
        tbl(i, 5) = Round(Rnd * 400, 2)    
            
        tblWar(i, 1) = tbl(i, 2)    
    Next    
        
    Dim s1 As Single: s1 = GetTickCount    
    tblWyn1 = SortowanieBabelkoweVA(DanePodWarunkiemA(tbl, VBALikeA(tblWar, "A*")), True, 5, 4)    
    Debug.Print "VBA: " & (GetTickCount - s1) / 1000 & " s"    
        
    Set objCls = CreateObject("tkCOMAddIn.clsUDF")    
    With objCls    
        
        Dim s2 As Single: s2 = GetTickCount    
        tblWyn2 = .SortowanieBabelkoweV(.DanePodWarunkiem(tbl, .VBALike(tblWar, "A*")), True, 5, 4)  
        Debug.Print "DLL: " & (GetTickCount - s2) / 1000 & " s"    
            
    End With    
    Set objCls = Nothing    
End Sub                          
 
Funkcje z "dopiskiem A" to odpowiedniki funkcji zapisanych w dodatku które są napisane w drugim module standardowym.  
 
    tblWyn1 = SortowanieBabelkoweVA(DanePodWarunkiemA(tbl, VBALikeA(tblWar, "A*")), True, 5, 4)  
 
Procedura tworzy tablicę 1 to iMax, 1 to 5, wyciąga z nich ~1/3 danych tworząc dynamiczną tablicę którą transponuje, a następnie  
dwa razy sortuje po kolumnach 4,5. Roboty zatem trochu ma. Wyniki dla 5 testów podane w sekundach.  
 
 
iMax = 1000 iMax = 5000 iMax = 10000  
VBA DLL VBA DLL VBA DLL  
1 0,265 0,173 6,161 4,072 23,978 15,82  
2 0,279 0,171 6,255 4,149 23,901 16,038  
3 0,313 0,188 5,696 3,791 23,041 15,212  
4 0,249 0,141 5,819 3,868 22,886 15,212  
5 0,297 0,173 6,006 3,976 24,461 16,147  
 
60,30% 66,33% 66,32%  
 
Jak widać procedury zapisane pliku dll wykonują zadanie w 60/66% czasu który byłby potrzebny do zrealizowania zadania poprzez  
VBA. Więc choćby z tego powodu warto zastanowić się nad przenosinami na dodatki automatyzacji.