VBA in Excel/ Klassenmodule

Aus Wikibooks


Module in Objekten[Bearbeiten]

Module sind Container für Code und für Variablen. Code ist jede Funktion, die einen oder mehrere Werte zurückgibt oder ein Makro, das keine Werte zurückliefert. Ein Modul ist also ein Container für VBA-Routinen.

Excel/VBA kennt Standard- und Klassenmodule. In Standardmodule wird Code zum allgemeinen Programmablauf hinterlegt, Klassenmodule verwalten Objekte mit ihren Eigenschaften, Methoden und Ereignissen.

In Excel gibt es eine Vielzahl von vordefinierten Klassen, um einige zu nennen:

WorkBook
In der Entwicklungsumgebung standardmäßig mit dem Objektnamen DieseArbeitsmappe bzw. ThisWorkbook benannt.

WorkSheet
In der Entwicklungsumgebung standardmäßig mit den jeweiligen Arbeitsblattnamen benannt.

Chart
In der Entwicklungsumgebung standardmäßig mit den jeweiligen Chart-Namen benannt.

UserForm
In der Entwicklungsumgebung standardmäßig mit dem jeweiligen UserForm-Namen benannt.

Die vorgenannten eingebauten Excel-Klassen können mit ihren Ereignissen in neue Klassen eingebunden werden. Sinnvoll ist dies beispielsweise, wenn eine Worksheet_Change-Ereignisprozedur allgemeingültig werden, sich also nicht nur auf die Arbeitsmappe beschränken soll, in der sich der Code befindet.

Allgemeingültiges Worksheet_Change-Ereignis[Bearbeiten]

Hier wird eine dem WorkBook-Objekt übergeordnete Klasse, also das Application-Objekt als Ausgangspunkt benötigt. In der Entwicklungsumgebung wird über das Menü Einfügen ein neues Klassenmodul erstellt. Der Name des neuen Klassenmoduls kann mit dem Aufruf der Eigenschaften mit der F4-Taste geändert werden ( in diesem Fall 'clsApp' ).

In das Klassenmodul wird zum einen eine Public-Variable für das Ereignis des Application-Objekts und zum anderen der zugehörige Ereigniscode eingetragen:

Public WithEvents App As Application   

Private Sub App_SheetChange( _  
   ByVal Sh As Object, _   
   ByVal Target As Range)   
   MsgBox "Zelle " & Target.Address(False, False) & _ 
      " aus Blatt " & ActiveSheet.Name & _
      " aus Arbeitsmappe " & ActiveWorkbook.Name & _
      " wurde geändert!"
End Sub

In der Workbook_Open-Prozedur wird die neue App-Klasse deklariert und initialisiert:

Dim AppClass As New clsApp   

Private Sub Workbook_Open()   
   Set AppClass.App = Application 
End Sub

Eine Ereignisprozedur für mehrere CommandButtons[Bearbeiten]

In das Klassenmodul 'clsButton' wird zum einen eine Public-Variable für das Ereignis des CommandButton-Objekts und zum anderen der zugehörige Ereigniscode eingetragen:

Public WithEvents Btn As CommandButton   

Private Sub Btn_Click()  
   MsgBox "Aufruf erfolgt von Schaltfläche " & Right(Btn.Caption, 1)
End Sub

Die Deklaration und Initialisierung der Btn-Klasse erfolgt in der Workbook_Open-Prozedur (das Workbook muss übrigens ein Worksheet 'Buttons' mit (mindestens) vier aus der Steuerelement-Toolbox eingefügten Befehlsschaltflächen beinhalten):

Dim CntBtn(1 To 4) As New clsButton    

Private Sub Workbook_Open()   
   Dim intCounter As Integer    
   For intCounter = 1 To 4  
      Set CntBtn(intCounter).Btn = ThisWorkbook.Worksheets("Buttons").OLEObjects(intCounter).Object  
   Next intCounter 
End Sub

Ein- und Auslesen einer Kundenliste[Bearbeiten]

Zusätzlich zu diesen vordefinierten können neue, benutzerdefinierte Klassen geschaffen werden, mit denen es auf programmiertechnisch elegante Art möglich ist, eigene Typen zu bilden und z.B. mit Plausibilitätsprüfungsroutinen auf diese zuzugreifen.

In das Klassenmodul 'clsKunden' werden zum einen die Public-Variablen für Elemente des Kunden-Objekts und zum anderen eine Prüfroutine eingetragen:

Option Explicit 
Public strNA As String   
Public strNB As String   
Public strS As String   
Public strC As String   
Public strPLZ As String   

Property Let strP(strP As String)  
   If Not IsNumeric(strP) Then    
      MsgBox strP & " ist eine ungültige Postleitzahl"
      strPLZ = "?????"
   Else 
      strPLZ = strP
   End If  
End Property

Die Deklaration und die allgemeinen Codes werden in einem Standardmodul hinterlegt:

Dim NeuerKunde As New clsKunden   
Dim colKunden As New Collection   

Sub Einlesen() 
   Dim intCounter As Integer    
   Set colKunden = Nothing  
   For intCounter = 2 To 11  
      Set NeuerKunde = New clsKunden  
      With NeuerKunde 
         .strNA = Cells(intCounter, 1).Value
         .strNB = Cells(intCounter, 2).Value
         .strS = Cells(intCounter, 3).Value
         .strP = Cells(intCounter, 4).Value
         .strC = Cells(intCounter, 5).Value
      End With  
      colKunden.Add NeuerKunde
   Next intCounter 
End Sub  

Sub AdressenAusgeben() 
   Dim knd As clsKunden  
   For Each knd In colKunden   
      With knd 
         MsgBox .strNA & vbLf & .strNB & vbLf & .strS & _
              vbLf & .strPLZ & " " & .strC
      End With  
   Next 
End Sub

Ereignissteuerung einer Serie von Labels[Bearbeiten]

Mit den nachfolgenden Prozeduren werden 256 Labels einer UserForm mit MouseMove, MouseClick- und anderen Ereignissen versehen.

In das Klassenmodul 'clsFrm' werden zum einen die Public-Variable für die Ereignisse des Label-Objekts und zum anderen die zugehörigen Ereigniscodes eingetragen:

Public WithEvents LabelGroup As MSForms.Label    

Private Sub LabelGroup_Click()  
   With frmChar.txtString  
      .Text = .Text & Me.LabelGroup.Caption
   End With  
End Sub  

Private Sub LabelGroup_DblClick( _  
   ByVal Cancel As MSForms.ReturnBoolean)    
   frmChar.txtString.Text = Me.LabelGroup.Caption 
End Sub  

Private Sub LabelGroup_MouseDown(ByVal Button As Integer, _       
   ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)     
   Me.LabelGroup.ForeColor = &H80000009 
   Me.LabelGroup.BackColor = &H80000012
End Sub  

Private Sub LabelGroup_MouseMove(ByVal Button As Integer, _      
   ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)     
   Dim strChar As String   
   Dim intChar As Integer    
   frmChar.lblChar.Caption = Me.LabelGroup.Caption
   strChar = Me.LabelGroup.Name
   intChar = CInt(Right(strChar, Len(strChar) - 5)) - 1 
   frmChar.lblShortCut.Caption = "Alt+" & intChar
   frmChar.lblZeichen.Caption = "=ZEICHEN(" & intChar & ")"
End Sub  

Private Sub LabelGroup_MouseUp(ByVal Button As Integer, _      
   ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)     
   Me.LabelGroup.ForeColor = &H80000012 
   Me.LabelGroup.BackColor = &H80000009
End Sub

Die Deklaration und Initialisierung der Labels-Klasse erfolgt in einem Standardmodul:

Dim Labels(1 To 256) As New clsFrm    

Sub ClsSymbolAufruf() 
   Dim intCounter As Integer    
   For intCounter = 1 To 256  
      Set Labels(intCounter).LabelGroup = frmChar.Controls("Label" & intCounter) 
   Next intCounter 
   frmChar.Show
End Sub

Eigenständige Klassenmodule[Bearbeiten]

Während die Klassenmodule des vorherigen Abschnitts Teil eines anderen Objektes waren (Arbeitsmappe, Formular, Tabelle, Diagramm ...) sollen die folgenden Beispiele einige Anwendungen von Klassenmodulen geben, die in Standard- und anderen Klassenmodulen verwendbar sind. In der Entwicklungsumgebung (VBE) handelt es sich um ein Modul, welches mit "Einfügen -> Klassenmodul" erzeugt werden kann.

Klasse als intelligente Variable[Bearbeiten]

Das folgende einfache Beispiel nutzt eine Klasse als Variable. Die eigentliche Aufgabe ist es, einen einfachen Mechanismus zu finden, mit dem ein Programm Winkel in einer Variablen zu speichern kann. Dabei sollen mehrere Variable unabhängig erzeugt und verwaltet werden. Ziel soll es sein, dass die Variable drei bekannte Winkelformate speichern, zurückgeben und konvertieren kann:

  • Grad oder Altgrad, Winkel am Vollkreis zwischen 0° … 360°
  • Radiant, Winkel am Vollkreis zwischen 0 … π
  • Neugrad oder Gon, Winkel am Vollkreis zwischen 0 gon … 400 gon

Nebenbei soll die Möglichkeit bestehen, den Wert des Winkels zu normalisieren, d.h. ein Winkel in Altgrad auf den Bereich von 0 … 360° zurückgeführt werden, so dass die Variable beispielsweise 540° in 180° oder -90° in 270° umwandelt und dies als normalisierten Wert zurückgibt.

Anwendung der Klasse als Variable[Bearbeiten]

Die folgende Funktion zeigt den Einsatz der nachfolgend definierten Klasse. Zunächst wird eine (oder mehrere) Variable mit dem Objekttyp der Klasse (hier wurde clsWinkel gewählt) und dem schlüsselwort New dimensioniert.

Die Zuweisung eines Zahlenwertes zum Winkel erfolgt über eine der Methoden DEG, RAD oder GON, je nach dem, welches Winkelformat der Zahlenwert hat. Zurückrufen kann man den (umgerechneten) Wert ebenfalls über die Methoden DEG, RAD oder GON im jeweiligen Winkelformat. Über die Eigenschaft Normalisiert kann festgelegt werden, ob die Rückgabe normalisiert wird oder ob Winkel ihren ursprünglichen Zahlenwert behalten.

Public Sub WinkelTest()
    Dim Winkel1 As New clsWinkel, Winkel2 As New clsWinkel
    
    With Winkel1
        .Normalisieren = True
        .DEG = 540
    End With
    MsgBox "Winkel in Radiant, normalisiert: " & Winkel1.RAD

    Winkel1.RAD = WorksheetFunction.Pi / 2
    MsgBox "Winkel in Gon, normalisiert: " & Winkel1.GON

    Winkel2.GON = 500
    MsgBox "Winkel in Gon, nicht normalisiert: " & Winkel1.GON
End Sub

Programmcode der Klasse[Bearbeiten]

Damit das vorherige Beispiel funktioniert, muss die folgende Klasse unter dem Namen clsWinkel abgespeichert werden.

Option Explicit

' Aufzählung für das Winkelmass
Private Enum WinkelEinheit
    wDeg = 0 ' Altgrad  0° ... 360°
    wRad = 1 ' Radiant  0  ... 2×Pi
    wGon = 2 ' Neugrad  0 ... 400 Gon
End Enum

' Interne Liste der Umrechnungsfaktoren
Private Faktor(wDeg To wGon) As Double

' Interne Speicherung des Wertes
Private WinkelWert  As Double

' Interne Speicherung der Einheit
Private WinkelMass  As WinkelEinheit

' Variable, um das Normalisieren einzuschalten.
' Dadurch wird Wert auf max. 360°/2×Pi/400 begrenzt
Public Normalisieren As Boolean

Private Sub Class_Initialize()
    Faktor(wDeg) = 180
    Faktor(wRad) = 3.14159265358979
    Faktor(wGon) = 200
End Sub

' Als Altgrad speichern
Public Property Let DEG(Wert As Double)
    WinkelWert = Wert
    WinkelMass = wDeg
End Property

' Wert in Altgrad zurückrufen
Public Property Get DEG() As Double
    If Normalisieren Then NormiereWinkel
    DEG = WinkelWert * Faktor(wDeg) / Faktor(WinkelMass)
End Property

' Als Radiant speichern
Public Property Let RAD(Wert As Double)
    WinkelWert = Wert
    WinkelMass = wRad
End Property

' Wert in Radiant zurückrufen
Public Property Get RAD() As Double
    If Normalisieren Then NormiereWinkel
    RAD = WinkelWert * Faktor(wRad) / Faktor(WinkelMass)
End Property

' Als Neugrad (Gon) speichern
Public Property Let GON(Wert As Double)
    WinkelWert = Wert
    WinkelMass = wGon
End Property

' Wert in Neugrad (Gon) zurückrufen
Public Property Get GON() As Double
    If Normalisieren Then NormiereWinkel
    GON = WinkelWert * Faktor(wGon) / Faktor(WinkelMass)
End Property

' Winkel in den Bereich 0...360° verschieben
Private Sub NormiereWinkel()
    If Abs(WinkelWert) > 2 * Faktor(WinkelMass) Then
        WinkelWert = Sgn(WinkelWert) * (Abs(WinkelWert) Mod (2 * Faktor(WinkelMass)))
    End If
    If WinkelWert < 0 Then WinkelWert = WinkelWert + 2 * Faktor(WinkelMass)
End Sub

Erklärung zu den einzelnen Funktionen:

  • Die Variable Normalisieren ist öffentlich sichtbar. Sie wird beim Erzeugen auf FALSE gesetzt, so dass die Normalisierung nicht durchgeführt wird.
  • Da die drei Methoden DEG, RAD und GON jeweils ein unterschiedliches Verhalten zeigen sollen, je nach dem, ob man einen Wert in die Variable speichert oder aus ihr zurückruft, müssen Property Let den Code für das Speichern in die Klasse (die wir als Variable benutzen) enthalten und Property Get Prozeduren für den Rückruf der Variablen aus der Klasse.
  • Da in VBA Arrays nicht als Konstanten deklariert werden können, muss die Class_Initialize-Prozedur ein Array belegen.