version=2.12.3 vdate=04.09.2019 fname=iterator.cls ns=%NAMESPACE% fpath=/vba/classes ====== [VBA] Iterator ====== //Eine Iterator-Klasse um die verschiedenen Listenmöglichkeiten (Array, Collection, Dictioary etc.) alle gleich handhaben zu können// ==Version %%version%% %%vdate%%== >//Das Modul hat versteckte Attribute. Damit diese aktiv übernommen werden reicht es nicht aus, den Code in ein neues Modul zu kopieren. Man muss das Modul aus der Datei nach VBA importieren.// >{{popup>:vba:vba_importfile.png|Bild zum Import}} {{..%%fname%%|Download %%fname%% (V-%%version%%)}} In VBA gibt es verschiedene Listen-Arten. Array, Collection und Dictionary. Wenn man noch mit %%RegExp%% arbeitet kommen %%MatchCollection%% und Match.Submatches dazu. Bei Recordset die Fields. Jeder dieser Listen muss anderst behandelt werden. Mal beginnen die Indexe irgendwo (array), mal bei 1 (Collection), meistens bei 0 oder im Fall von Dictionaries muss man über den Key zugriffen werden. Ich wollte ein einheitliches Konstrukt, durch dass ich bequem mittels Loop durchiterieren kann. Dabei kam die Iterator-Klasse heraus. Am besten mal die [[#anwendungsbeispiele|Anwendungsbeispiele]] anschauen um zu verstehen um was es geht. Die folgenden Listen sind ausprogrammiert: \\ Array, Collection(([[http://msdn.microsoft.com/de-de/library/yb7y698k%28v=vs.90%29.aspx|Collection bei MSDN]])), Dictionary(([[http://msdn.microsoft.com/en-us/library/x4k5wbx4%28v=vs.84%29.aspx|Dictionary bei MSDN]])), %%RegExp.MatchCollection%%(([[http://msdn.microsoft.com/en-us/library/system.text.regularexpressions.matchcollection%28v=vs.110%29.aspx|MatchCollection bei MSDN]])), %%RegExp.Submatches%%, DAO.Recordset, DAO.Fields DAO.QueryDef DAO.TableDef Excel.Workbook (Die Sheets) Excel.Sheets FSO.Folder ===== Definitionen ===== ==== Enumerator ==== === itParams === '/** ' * Parameter zur Steuerung des Verhalten des Iterators ' * Die einzelnen Parameter sind mit + kombinierbar ' */ Public Enum itParams itNone = 0 'Kein Parameter hat gültigkeit itNothingAsEmptyList = 2 ^ 0 'Leerer String, Nothing, Empty, Null wird als leere Liste akzeptiert und wirft kein Fehler itListNextNoParamsAsToNext = 2 ^ 1 'Wenn bei listNext keine Parameter angeben werden, einfach eins vorrücken. itErrorAtEmptyList = 2 ^ 2 'Fehler generieren, wenn die iListe leer ist itDaoValue = 2 ^ 3 'Field-Value anstelle von Field ausgeben itIndexInsteadKey = 2 ^ 4 'Gibt bei den toX() Funktionen den Index anstelle des Keys zurück itDefault = itNothingAsEmptyList + itListNextNoParamsAsToNext [_LAST] = 4 End Enum ==== Properties ==== Die Eigenschaften der Klasse. \\ r steht für Readable, w für writable === Properties zu den Daten === ***source**rw Quellliste, über die Iteriert wird (Array, Collection etc.) ***current**r Aktuelles Item ***key**r Aktueller Key ***index**r Aktueller Indes der Liste, beginnend mit dem listenspezifischen Anfang ***absolutePosition**r Absolute Position in der Liste, beginnend mit 0 ***BOF**r Beginn of File. Wir sind vor dem ersten Eintrag ***%%EOF%%**r End of File. Wir sind nach dem letzten Eintrag ***count**r Anzahl Items in der Liste ***isEmpty**r Flag, ob überhaubt Einträge vorhanden sind: count=0 === Propierties zur Iteratorsteuerung === Diese Properties sind die Einzeilzuordnungen zu [[##itparams|itparams]] ***Params**rw Die Parameter als Enum-Set ***paramNothingAsEmptyList**rw Leerer String, Nothing, Empty, Null wird als leere Liste akzeptiert und wirft kein Fehler ***paramListNextNoParamsAsToNext**rw Wenn bei listNext keine Parameter angeben werden, einfach eins vorrücken. ***paramErrorAtEmptyList**rw Fehler generieren, wenn die iListe leer ist ***paramDaoValue**rw Field-Value anstelle von Field ausgeben ***paramIndexInsteadKey**rw Gibt bei den toX() Funktionen den Index anstelle des Keys zurück ==== Methoden ==== === Collection Class Methodes === Diese Funktionen dienesn dazu, dass die Klasse wie eine Collection genutzt werden kann. \\ [[http://msdn.microsoft.com/en-us/library/aa262338%28v=vs.60%29.aspx]] == NewEnum() == '/** ' * Der NewEnum wird für die For Each.. Next Schleife verwendet ' * ' * Diese Funktion hat das Attribut "'Attribute NewEnum.VB_UserMemId = -4" ' * !! Diese Iterierung hat keinen Einfluss auf die aktuelle Position !! ' * ' * @return Das nächste element ' */ Public Function NewEnum() As IUnknown == item() == '/** ' * Entspricht dem item()-Befehl der Listenobjekte ' * Alle 3 Propert-Funktionen aben das versteckte Attribut "Attribute item.VB_UserMemId = 0" ' * Dadurch kann mittels it(1), it("MyFeild"), it!MyField etc darauf zugegriffen werden. ' * ' * !! Das Verhalten dieser Funktion ist NICHT standartisiert. Sie entspricht dem entsprechenden Verhalten der Source !! ' * ' * @param Variant Index/Key ' * @return Variant ' */ Public Property Get item(ByVal iIndex As Variant) As Variant === Initialisierungs-Methoden === == initialize() == '/** ' * Initialisuert den Iterator ' * @param Variant Die Liste, über welche Iteriert werden soll ' * @param itParams Parameters ' * @return Boolean true: iList ist eine Liste und hat Werte ' */ Public Static Function initialize( _ ByRef iList As Variant, _ Optional ByVal iParams As itParams = itDefault _ ) As Boolean == initializeRs() == Die Parameters findest du [[#itparams|hier]]. Sie sind mit + kombinierbar. '/** ' * Analog zu initialze. Erstellt aber direkt ein Recordset aus einem SQL ' * @param String SLQ String ' * @param itParams Parameters ' * @return Boolean true: iList ist eine Liste und hat Werte ' */ Public Static Function initializeRs( _ ByVal iSql As String, _ Optional ByVal iParams As itParams = itDefault _ ) As Boolean === Navigations-Methoden === == toNext() == '/** ' * Geht zum nächsten Datensatz. Der Key und er Datensatz werden als Parameter zurückgegeben, EOF als Return-Value ' * @param Variant Key oder pos des Items ' * @param Variant Das Item himself ' * @return Boolean EOF Public Function toNext( _ Optional ByRef oKey As Variant = Null, _ Optional ByRef oItem As Variant = Null _ ) As Boolean == toPrev() == '/** ' * Geht zum nächsten Datensatz. Der Key und er Datensatz werden als Parameter zurückgegeben, EOF als Return-Value ' * @param Variant Key oder pos des Items ' * @param Variant Das Item himself ' * @return Boolean EOF Public Function toPrev( _ Optional ByRef oKey As Variant = Null, _ Optional ByRef oItem As Variant = Null _ ) As Boolean == toFirst() == '/** ' * Geht zum ersten Datensatz. ' * Vorsicht, während reset() eins vor den ersten Datensatz geht, geht toFirst() bereits auf den ersten Datensatz ' * Somit ists das eigentlich reset()+toNext() ' * @param Variant Key oder pos des Items ' * @param Variant Das Item himself ' * @return Boolean EOF Public Function toFirst( _ Optional ByRef oKey As Variant = Null, _ Optional ByRef oItem As Variant = Null _ ) As Boolean == toLast() == '/** ' * Geht zum nächsten Datensatz. Der Key und er Datensatz werden als Parameter zurückgegeben, EOF als Return-Value ' * @param Variant Key oder pos des Items ' * @param Variant Das Item himself ' * @return Boolean EOF Public Function toLast( _ Optional ByRef oKey As Variant = Null, _ Optional ByRef oItem As Variant = Null _ ) As Boolean == toPosition() == '/** ' * Geht zum nächsten Datensatz. Der Key und er Datensatz werden als Parameter zurückgegeben, EOF als Return-Value ' * @param Long Absolute Position. Zwischen 0 und Count-1 ' * @param Variant Key oder index des Items ' * @param Variant Das Item himself ' * @return Boolean EOF Public Function toPosition( _ ByVal iAbsolutPosition As Long, _ Optional ByRef oKey As Variant = Null, _ Optional ByRef oItem As Variant = Null _ ) As Boolean == listNext() == Siehe auch [[:vba:functions:list]] '/** ' * Rückt den Zeiger um Eins vor und gibt die Elemente einer Unterliste aus. ' * @paramArray Variant Auflistung der Variablen, die abgefüllt werden ' * @return EOF ' */ Public Function listNext( _ ParamArray oParams() As Variant _ ) As Boolean == list() == Siehe auch [[:vba:functions:list]] '/** ' * Ggibt die Elemente der aktuellen Unterliste aus. ' * @paramArray Variant Auflistung der Variablen, die abgefüllt werden ' * @return EOF & BOF ' */ Public Function list( _ ParamArray oParams() As Variant _ ) As Boolean == reset() == '/** ' * Setzt den pos auf den Start-1 zurück, so dass bei einem toNext() der erste Datensatz kommt ' */ Public Sub reset() == subIterator() == Wenn das Current-Objekt selber wieder iterierbar ist, kann mit dieser Methode direkt ein Iterator für das Objekt erstellt werden. '/** ' * Gibt current als Iterator zurück ' * @return Iterator ' */ Public Property Get subIterator() As Iterator ===== Iterierbare Listen ===== Die folgenden Datentypen werden vom Iterator als Liste erkannt und können iteriert werden *Array *Dictionary *Collection *%%RegExp.MatchCollection%% *%%RegExp.Match%% *Dao.Recordset *Dao.Fields *Dao.Field *Properties ===== Anwendungsbeispiele ===== Die Beispiele sind unvollständig. Die Klasse hat noch einiges mehr auf Lager. Finde es heraus oder frag nach. ==== Iteration mit einem eindimensionalen Array ==== === Einfachstes Beispiel mit einem Array === Public Sub testIt() Dim arr() As Variant: arr = Array(1, 2, 3) Dim it As New Iterator: it.initialize arr Do While it.toNext Debug.Print it.key & ": " & it.current Loop End Sub === Dito, aber der Wert wird als Variable verarbeitet === Public Sub testIt() Dim arr() As Variant: arr = Array(1, 2, 3) Dim it As New Iterator: it.initialize arr Dim key As Variant Dim val As Variant Do While it.toNext(key, val) Debug.Print key & ": " & val Loop End Sub === Von hinten nach vorne iterieren === Public Sub testIt() Dim arr() As Variant: arr = Array(1, 2, 3) Dim it As New Iterator: it.initialize arr it.toEOF Do While it.toPrev Debug.Print it.KEY & ": " & it.current Loop End Sub === Und ein Array, der nicht bei 0 beginnt === Public Sub testIt() Dim arr(3 To 4) As Variant Dim it As New Iterator Dim key As Variant Dim val As Variant arr(3) = 33 arr(4) = 44 it.initialize arr Do While it.toNext(key, val) Debug.Print key & ": " & val Loop End Sub ==== Iteration über einen 2 Dimensionalen Array ==== === Mit direktem Auslesend er SubItems mit listNext() === Public Sub testIt() Dim arr(3 To 4) As Variant Dim it As New Iterator Dim v1, v2, v3 arr(3) = Array(31, 32, 33) arr(4) = Array(41, 42, 44) it.initialize arr Do While it.listNext(v1, v2, v3) Debug.Print v1 & "-" & v2 & "-" & v3 Loop End Sub 31-32-33 41-42-44 ==== Besipel mit MatchCoellection ==== Public Sub testIt() Dim rx As New regExp Dim mc As Variant Dim it As Iterator Dim x, y rx.Global = True rx.pattern = "([A-H])([1-8])" Set mc = rx.execute("Springer von A1 auf B3") Set it = New Iterator: it.initialize mc Do While it.toNext Debug.Print it.current.value 'Den gesamten gefunden String ausgeben it.list x, y 'Die 2 Submatches auf x und y verteilen Debug.Print x & ":" & y 'x & y ausegeben Loop End Sub A1 A:1 B3 B:3 ==== Beispiele zu Recordset ==== === Recordset durchiterieren === Public Sub testIt() 'Initialiseren des Iterators über initializeRs Dim it As New Iterator: it.initializeRs "SELECT [id], [number_one], [number_two] FROM [_test]" 'Jede ID ausgeben Do While it.toNext Debug.Print it!id Loop it.reset 'Postion zurücksetzen 'Die Felder des Recordsets direkt in die Variablen übertragen Dim id, n1, n2 Do While it.listNext(id, n1, n2) Debug.Print id, n1, n2 Loop End Sub 1 2 3 4 1 11 21 2 12 22 3 13 23 4 14 24 === FieldList durchiterieren - Ausgabe von current und value === Public Sub testIt() Dim v As Variant 'Initialiseren des Iterators über initializeRs Dim it As New Iterator: it.initializeRs "SELECT [id], [number_one], [number_two] FROM [_test]" 'Erste Zeile als [Fields] auslesen und neuen Iterator daruas erstellen it.toFirst Dim itFields As Iterator: Set itFields = it.subIterator Debug.Print "== Ausgabe: Property current" Do While itFields.toNext print_r itFields.current Loop Debug.Print "== Ausgabe: Property value" itFields.reset 'position zurücksetzen Do While itFields.toNext print_r itFields.value Loop Debug.Print "== Ausgabe: Referenziertes Property current, Iterator mit dem Parameter DaoValue" itFields.reset 'position zurücksetzen itFields.paramDaoValue = True 'Paraeter DaoValue setzen um das Verhalten von current zu ändern Do While itFields.toNext(, v) 'Der zweite Parameter von toNext() entspricht dem current print_r v Loop End Sub == Ausgabe Property current ( [OrdinalPosition] => 0 [name] => 'id' [value] => 1 [type] => 4 ) ( [OrdinalPosition] => 1 [name] => 'number_one' [value] => 11 [type] => 4 ) ( [OrdinalPosition] => 2 [name] => 'number_two' [value] => 21 [type] => 4 ) == Ausgabe Property value 1 11 21 == Ausgabe Property current, Iterator mit dem Parameter DaoValue 1 11 21 ===== Code =====