version=1.0.0
vdate=05.04.2017
fname=counter.cls
ns=%NAMESPACE%
fpath=/vba/tutorials
====== [VBA] Komplexere Anwendung von Klassenmodulen ======
Für dieses Tutorial sind Grundkentnisse zum Thema "Objekte in VB" von erforderlich.
Ich habe für dieses Tutorial eine Klasse "Counter" geschrieben (vollständiger Code am Ende des Tutorials). Sie dient hier als Besipiel. Mit einem Objekt der Klasse Counter kann man den Start und die Schrittgrösse definieren und dann Schritt für Schritt weiterzählen.
Ich habe aber einige Interessante Details eingebaut.
==== Klasse Counter ====
Der Counter hat für die klassiache Anwendung die folgenden Methoden:
^ Methode/Property ^ Rückgabewert ^ Beschreibung ^
| **initialize** | | Setzt die Startwerte |
| **toNext** | Long | Zählt eins hoch und gibt den Wert zurück |
| **toMax** | Long | Führt toNext bis zu maximal dem mitgegeben Wert |
| **reset** | | Alles auf die Startwerte zurücksetzen |
| **current** | Long | Den aktuellen Wert |
| **start** | Long | Startwert |
| **step** | Long | Schrittgrösse |
Spezielle Methoden/Property, auf die ich eingehen möschte
^ Methode/Property ^ Rückgabewert ^ Beschreibung ^
| **instance** | Counter | Eine neue Instanz der Klasse. Ist auch als Default definiert |
| **copyOf** | Counter | Eine neue Instanz der Klasse welche die Settings eines anderen Counters übernimmt |
| **NewEnum** | IUnknown | wird für die For Each.. Next Schleife verwendet |
Ich zeige mit verschiedenen Testscripts gewisse Funktionalitäten. Bei interessanten Sachen gehe ich dann noch auf den Code in der Klasse ein
==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%%)}}
===== Standard =====
Hier mal die Standardfunktionalität, wie man sie aus OOP unter VB kennt.
'/**
' * Standardanwendung mit Standardwerten
' */
Public Sub testDefault()
Debug.Print "--- testDefault() ---"
Dim cnt As New Counter
Debug.Print "default", cnt.current, cnt.toNext, cnt.toNext, cnt.toNext
End Sub--- testDefault() ---
default 0 1 2 3
===== Klasse initialisieren =====
Ich habe der Klasse eine Methode namens initialze() gegeben. Damit lassen sich die Startwerte festlegen und alles andere zurücksetzen
'/**
' * Setzt die StartWerte für eine Instanz
' * @param Long StartWert
' * @param Long Schrittgrösse
' * @return Counter Die Singleton-Instanz
'*/
Public Sub initialize(Optional ByVal iStart As Long = C_DEFAULT_START, Optional ByVal iStep As Long = C_DEFAULT_STEP)
...
End Sub
Und zum Testen setze ich einen anderen Startwert und eine andere Schrittgrösse
'/**
' * Standard, aber mit anderen Startwert und Schrittgrösse
' */
Public Sub testWithInitialize()
Debug.Print "--- testWithInitialize() ---"
Dim cnt As New Counter
cnt.initialize 10, 5
Debug.Print "initialize()", cnt.current, cnt.toNext, cnt.toNext, cnt.toNext
End Sub--- testWithInititalze() ---
initialize() 10 15 20 25
===== VB_PredeclaredId =====
Wenn wir die Klasse exportieren und mit einem Texteditor betrachten, dann sehen wir am Anfang der Datei den folgenden Header
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "Counter"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Standardmässig ist der Eintrag VB_PredeclaredId aus False. Diesen habe ich im Texteditor auf True umgestellt. Wenn ich jetzt die Klasse wieder importiere, dann hat sich das Verhalten ein wenig geändert.
Ich kann jetzt Direkt auf die Methoden der Klasse zugreifen, ohne dass ich eine Instanz besitze. Das macht nur beschränkt Sinn. Aber so kann man verschieden Instance() Funktionen schreiben, die ein Objekt der Klasse zurückgeben
Ich hbae 2 Methoden dazu geschrieben. instance() und copyOf(). Beide geben eine neue Instanz des Counters zurück
'/**
' * Eine neue Instanz der Klasse. Ist auch als Default definiert
' * @param Long StartWert
' * @param Long Schrittgrösse
' * @return Counter Die Singleton-Instanz
'*/
Public Function instance(Optional ByVal iStart As Long = C_DEFAULT_START, Optional ByVal iStep As Long = C_DEFAULT_STEP) As Counter
Set instance = New Counter
instance.initialize iStart, iStep
End Function'/**
' * Eine neue Instanz der Klasse welche die Settings eines anderen Counters übernimmt
' * @param Counter StartWert
' * @return Counter Die Singleton-Instanz
'*/
Public Function copyOf(ByRef iCounter As Counter) As Counter
Set copyOf = New Counter
copyOf.initialize iCounter.start, iCounter.step
End Function
Und so kann man diese Methoden anwenden
'/**
' * Test mit Instance. instance() wird direkt auf der Klasse aufgerufen, nicht aus einem Objekt
' */
Public Sub testWithInstance()
Debug.Print "--- testWithInstance() ---"
Dim cnt As Counter
Set cnt = Counter.instance(, 3)
Debug.Print "instance()", cnt.current, cnt.toNext, cnt.toNext, cnt.toNext
End Sub--- testWithInstance() ---
instance() 0 3 6 9
Und noch ein Test mit beiden Methoden
'/**
' * Test mit Instance und copyOf
' */
Public Sub testWithInstanceAndCopyOf()
Debug.Print "--- testWithInstanceAndCopyOf() ---"
Dim cnt1 As Counter
Dim cnt2 As Counter
Set cnt1 = Counter.instance(, 3)
Debug.Print "instance()", cnt1.current, cnt1.toNext, cnt1.toNext, cnt1.toNext
Set cnt2 = Counter.copyOf(cnt1)
Debug.Print "copyOf()", cnt2.current, cnt2.toNext, cnt2.toNext, cnt2.toNext
End Sub--- testWithInstanceAndCopyOf() ---
instance() 0 3 6 9
copyOf() 0 3 6 9
===== Standardfunktion der Klasse =====
Eine Funktion will ich als Standard setzen. Dass bedeutet, dass diese Funktion aufgerufen wird, wenn man die Klasse mit () startet, gleich eine bestimmte Funktion ausgeführt wird.
In diesem Fall habe ich die Funktion instance() gewählt
Dazu setze ich das Attribut VB_UserMemId der Funktion auf 0. Dass muss man auch Ausserhalb des VBA-Editors mit einem Texteditor machen, da auch dieses Setting versteckt ist.
Public Function instance(Optional ByVal iStart As Long = C_DEFAULT_START, Optional ByVal iStep As Long = C_DEFAULT_STEP) As Counter
Attribute instance.VB_UserMemId = 0
'Attribute instance.VB_UserMemId = 0
...
End Function
Die 2te Zeile mit dem Komentar habe ich immer dabei. Wenn ich die Funktion anpasse kann es vorkommen, dass die versteckte Zeile rausfällt. Das merkt mann dann, wenn bei der Anwendung ein Fehler kommt.
Einfach die Funktion wieder exportieren, die den Text der Komentarzeile nach oben kopieren und die Klasse wieder importieren
Und so wendet man das dann an
'/**
' * Test mit Instance. instance() wird direkt auf der Klasse aufgerufen, nicht aus einem Objekt
' */
Public Sub testWithUserMemId0()
Debug.Print "--- testWithUserMemId0() ---"
Dim cnt As Counter
Set cnt = Counter(99, -3)
Debug.Print "()", cnt.current, cnt.toNext, cnt.toNext, cnt.toNext
End Sub--- testWithUserMemId0() ---
() 99 96 93 90
===== For Each auf das Objekt anwenden =====
Manchmal hat man eine Klasse, die eine Liste mitverwaltet. Diese will mit For Each durchiterieren.
Dazu verwende ich wieder das versteckte Funktionsattribut VB_UserMemId. Dieses mal aber mit dem Wert 4
Zudem muss ich eine ganz Bestimmte Funktion bauen. Sie heisst 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
Attribute NewEnum.VB_UserMemId = -4
'Attribute NewEnum.VB_UserMemId = -4
Set NewEnum = pNewEnum.[_NewEnum]
End Function
Darin komm das Objekt pNewEnum vor. Das ist eine Collection. Diese beinhaltet alle Werte, die man mit For Each herauslesen kann.
Private pNewEnum As Collection
In diesem Fall einfach alle Werte, die der Counter hatte. Das wird in der Funktion toNext der Collection hinzugefügt
pNewEnum.add pCurrent
Jetzt habe ich also eine Collection, in der alle Einträge gespeichert sind, die durchiteriert wurden.
Machen wir mal ein Beispiel um zu zeige, wei man das anwenden kann
'/**
' * Test mit toMax und NewEnum
' */
Public Sub testWithNewEnum()
Debug.Print "--- testWithNewEnum() ---"
Dim cnt As New Counter
Dim i As Long
Dim nr As Variant
'Cointer initializieren
cnt.initialize 271, 333
'Weiterzählen bis Maximum 2000
Do
Loop While (cnt.toNext + cnt.step) < 2000
'und mal schauen, was für Werte der Counter bereits hinter sich hat
For Each nr In cnt
Debug.Print nr
Next nr
End Sub--- testWithNewEnum() ---
271
604
937
1270
1603
1936
===== Singelton =====
Dank dem Attribute VB_PredeclaredId = True ist die Klasse auch Singelton fähig. Sie ist also immer auch eine Instanz von sich selber.
Dazu kann ich einfach den Klassennamen nehmen und die Methoden aufrufen. Aber Achtung
Counter.toNext 'Zählt den Singelton-Counter hoch
Counter().toNext 'Erstellt eine neue Instanz (instance()) und zählt diese um eines hoch
Der Vorteil der Singelton ist, dass sie von überall her die Werte behält. Auch wenn wie die Singelton einem Objekt zugeordnet wird, so ist das Objekt mit der Singelton weiterhin verknüpft.
'/**
' * Erster Test mit Singelton
' */
Public Sub testWithSingleton1()
Debug.Print "--- testWithSingleton1 ---"
'Sigelton zurücksetzen
Counter.initialize
Debug.Print "singleton 1", Counter.current, Counter.toNext, Counter.toNext, Counter.toNext
End Sub
'/**
' * weiterführende Test mit Singleton
' */
Public Sub testWithSingleton2()
Debug.Print "--- testWithSingleton2 ---"
Debug.Print "singleton 2", Counter.current, Counter.toNext, Counter.toNext, Counter.toNext
Debug.Print "Der Sigelton neue Werte zuweisen"
Counter.initialize 1000, -5
Debug.Print "singleton 3", Counter.current, Counter.toNext, Counter.toNext, Counter.toNext
Debug.Print "Eine Referenz der Sigelton als Objekt verwenden"
Dim cnt As Counter
Set cnt = Counter
Debug.Print "singleton 4", cnt.current, cnt.toNext, cnt.toNext, cnt.toNext
Debug.Print "Auswirkungen des Objektes auf die referenzierte Sigelton"
Debug.Print "singleton 5", Counter.current, Counter.toNext, Counter.toNext, Counter.toNext
End Sub--- testWithSingleton1 ---
singleton 1 0 1 2 3
--- testWithSingleton2 ---
singleton 2 3 4 5 6
Der Sigelton neue Werte zuweisen
singleton 3 1000 995 990 985
Eine Referenz der Sigelton als Objekt verwenden
singleton 4 985 980 975 970
Auswirkungen des Objektes auf die referenzierte Sigelton
singleton 5 970 965 960 955
===== Code von Counter =====