User Tools

Site Tools


vba:tutorials:cassesplus

This is an old revision of the document!


[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). 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 einie Interessante Details eingebaut.

Ich zeige mit verschiedenen Testscripts gewisse Funktionalitäten. Bei interessanten Sachen gehe ich dann noch auf den Code in der Klasse ein

Version 1.0.0 05.04.2017
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.
Bild zum Import

Download Counter.cls (V-1.0.0)

Standard

Hier mal die Standardfunktionalität, wie man sie aus OOP unter VB kennt.

testDefault
'/**
' * 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

initialize
'/**
' * 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

testWithInitialize
'/**
' * 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

instance
'/**
' * 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
copyOf
'/**
' * 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

testWithInstance
'/**
' * 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

testWithInstanceAndCopyOf
'/**
' * 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

testWithUserMemId0
'/**
' * 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()

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

testWithNewEnum
'/**
' * 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 Sigelton einem Objekt zugeordnet wird, so ist das Objekt mit der Singelton vweiterhin 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

Unable to display file "/vba/tutorials/Counter.cls": It may not exist, or permission may be denied.

vba/tutorials/cassesplus.1491397767.txt.gz · Last modified: 05.04.2017 15:09:27 by yaslaw