Tipps & Tricks: VBA allgemein
Auf dieser Seite finden Sie 15 Tipps & Tricks für VBA allgemein.
Wenn in den folgenden Erläuterungen Texte wie MsgBox formatiert sind, handelt es sich um konkret einzugebende Inhalte wie VBA-Code oder die Eingabe von Werten. Menüs wie Datei Speichern sind wie hier zu sehen formatiert. Schaltflächen oder Registerkarten auf Dialogen werden wie Menüs behandelt.
Alle Tipps sind nach bestem Wissen geprüft, aber selbstverständlich ohne Gewähr. Sollten Sie doch einen Fehler darin entdecken, würden wir uns freuen, wenn Sie uns per E-Mail Bescheid sagen.
Andere Programme einbinden ID 441
Wenn Ihr VBA-Code beispielsweise von Word aus auf Excel-Daten zugreifen soll, müssen Sie Excel zur Laufzeit einbinden. Ebenso gilt dies für den Zugriff von Access etwa auf Outlook-Daten. Das ist eine sogenannte "späte Bindung" (Late Binding), weil nicht schon im Entwurf, sondern erst zur Laufzeit das jeweils fremde Objektmodell hinzugebunden wird. Das können Sie standardisiert in einer Funktion kapseln, ohne irgendwelche Verweise hinzufügen zu müssen:
Set AnderesProg = GetObject(, strProgName & ".Application")
If AnderesProg Is Nothing Then
If AnderesProg Is Nothing Then
On Error GoTo 0
Dabei versucht der Code mit GetObject eine schon laufende Instanz des gesuchten Programms zu finden. Erst wenn das scheitert, wird eine neue Instanz geöffnet. Der Aufruf dieser Funktion erfolgt beispielsweise für Word so:
Set appWord = AnderesProg("Word")
If Not appWord Is Nothing Then
Die Anzeige der Beschriftung mittels MsgBox soll lediglich demonstrieren, dass der Zugriff nun möglich ist.
Die vier Standard-Operatoren +, -, * und / kennen Sie sicherlich. Es gibt aber noch ein paar mehr, die das Rechnen sehr erleichtern. Mit dem Caret-Zeichen (das "Dach" auf dem accent d'aigue) können Sie potenzieren:
lässt sich kürzer als
schreiben. Dem Caret-Zeichen (^) muss bei der Eingabe immer noch eine Leertaste folgen, bevor es auch wirklich auf dem Bildschirm erscheint. Für das Wurzelzeichen gibt es keinen Operator. Das braucht es auch nicht, denn die Quadratwurzel ist:
oder wenn Sie es lieber als Bruch schreiben:
Achten Sie dabei vor allem auf die Klammern, sonst würden Sie zuerst 2 ^ 1 rechnen und das dann durch 2 teilen. Damit erhielten Sie fälschlicherweise 1 als Ergebnis.
Außerdem können Sie den ganzzahligen Teil einer Division ermitteln, indem Sie statt des üblichen rechtsgeneigten Schrägstrichs (/, "Slash") den linksgeneigten (, "Backslash") benutzen:
Das Gegenstück dazu ist der modulo-Operator, der den verbleibenden Rest der obigen Division errechnet:
Einen Einsatz dieses Operators sehen im Tipp "Gerade/ungerade Seiten".
Neben den mathematischen Operatoren gibt es noch logische Operatoren, die ebenfalls "Berechnungen" durchführen. Sicherlich werden sie meistens als Vergleichsoperatoren eingesetzt wie im folgenden Beispiel:
Mit Hilfe der logischen Operatoren können Sie aber auch rechnen und in sogenannter Binärcodierung bitweise Informationen abspeichern. Während das Argument des gesparten Speicherplatzes heutzutage kaum noch überzeugt, erlaubt diese Technik Ihnen, statt vieler Boole'scher Einzelwerte einfach eine (Long-)Zahl zu speichern.
Sobald solche Daten gespeichert werden müssen, ist das Sichern als Zahl viel einfacher als mehrere Boole'sche Werte. Diese werden nämlich je nach System
- direkt als Text (Excel: -1 wird zu WAHR) angezeigt oder
- verbrauchen viel Platz (Textdateien: 14 entspräche 0, 0, 0, 0, -1, -1, -1, 0) oder
- brauchen je ein eigenes Datenfeld (Access: statt eines Long-Zahlenfelds 16 Ja/Nein-Felder).
Dazu braucht es jedoch vorab einen kurzen Blick auf die Art, wie ein Computer Zahlen "versteht". Ein einzelnes Bit kann nur die Zustände Nein/Ja (bzw. 0/-1) annehmen. Wenn Sie acht davon zusammenpacken, nennt sich das "Byte" und kann 28 Kombinationen aus Nein/Ja annehmen. Jedes Bit darin steht für eine Zweierpotenz, wobei von rechts gelesen wird und die erste Zweierpotenz 20=1 ist:
27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | |
0 | 0 | 0 | 0 | | | 0 | 0 | | | |
0+ | 0+ | 0+ | 0+ | 8+ | 0+ | 0+ | 1= | 9 |
0 | 0 | 0 | 0 | | | | | | | | | |
0+ | 0+ | 0+ | 0+ | 8+ | 4+ | 2+ | 1= | 15 |
Die Summe der jeweiligen Zweierpotenzen, deren Bit auf Ja steht, ergibt den Zahlenwert. Soweit ist das natürlich für einen normalen Gebrauch viel zu kompliziert. Sie können aber ebenfalls jedes einzelne Bit setzen und abfragen, so dass sich damit in einer Byte-Zahl 8 Ja/Nein-Entscheidungen oder in einer Long-Zahl 16 Entscheidungen speichern lassen.
Dabei müssen die Zahlenwerte der jeweiligen Bit-Vergleichswerte Zweierpotenzen sein, denn dann ist genau nur ein Bit gesetzt. Einzelne Bits einer Zahl verändern Sie, indem Sie diese mit einer anderen Zahl (welche exakt das zu ändernde Bit enthält) über OR verknüpfen:
0 | 0 | 0 | 0 | | | 0 | 0 | | |
OR | |||||||
0 | 0 | 0 | 0 | 0 | 0 | | | 0 |
= | |||||||
0 | 0 | 0 | 0 | | | 0 | | | | |
Der OR-Operator vergleicht jedes einzelne Bit mit dem jeweils passenden der anderen Zahl und setzt das jeweilige Ergebnis-Bit auf True bzw. -1, wenn wenigstens eines der beiden gesetzt war. Dadurch sind alle Ergebnis-Bits "angeschaltet", die bei einer der Zahlen auch "an" waren. Einzig die Bits, die wirklich bei beiden Zahlen "aus" waren, bleiben "aus". Daher können Sie bei einer Zahl genau ein Bit einschalten, indem Sie eine zweite Zahl wählen, die genau nur dieses eine Bit gesetzt hat. Alle "0"-Bits ändern nichts.
Das gezielte Ausschalten erfolgt etwas komplizierter mit dem "Kehrwert" des Bitschalters und dem AND-Operator. Das hat zur Folge, dass im Bitschalter jetzt alle außer dem zu ändernden Bit auf True stehen und über die AND-Verknüpfung nur dieses eine abgeschaltet wird:
0 | 0 | 0 | 0 | | | 0 | | | | | |
AND | ||||||||
| | | | | | | | | | | | 0 | | | (Kehrwert des vorigen Beispiels) |
= | ||||||||
0 | 0 | 0 | 0 | | | 0 | 0 | | |
Im folgenden Beispiel sollen zu einem fiktiven Dokument Informationen gesichert werden, wie dessen Status ist, also ob es schon bearbeitet wurde, ob es eine Zweitunterschrift benötigt usw. Dazu bereiten Sie als Bit-Vergleichswerte am besten ein paar Konstanten vor, damit es besser lesbar wird:
Dim lngStatus As Long
Const Unbearbeitet = 0
Const Erledigt = 1
Const Intern = 2
Const Zweitunterschrift = 4
Const AnfahrtSkizze = 8
Const MaxWert = 255
Dann ist eine Funktion Status hilfreich, welche das jeweilige Bit bequem ein und ausschaltet:
Die Differenz zwischen MaxWert und lngTeil ist genau der gesuchte Kehrwert von lngTeil, so dass dieses Ergebnis dann mit dem And-Operator verknüpft werden kann.
Außerdem sollten Sie noch eine Funktion HatStatus bereitstellen, mit der Sie ermitteln können, ob das gesuchte Bit gesetzt ist:
Diese Funktion versucht das Bit mittels OR-Operator zu setzen und vergleicht das mit dem vorherigen Wert. Wenn das Ergebnis identisch ist, war das Bit bereits gesetzt.
Damit können Sie wie im folgenden Beispiel in einer einzigen Variablen lngStatus bis zu 16 verschiedene Informationen abspeichern:
Zeige
lngStatus = Status(Erledigt, lngStatus, True)
Zeige
lngStatus = Status(Intern, lngStatus, False)
Zeige
lngStatus = Status(Zweitunterschrift, lngStatus, True)
Zeige
lngStatus = Status(Intern, lngStatus, False)
Zeige
"Zweitunterschrift: " & HatStatus(Zweitunterschrift, lngStatus) & ", " & _
"Anfahrt: " & HatStatus(AnfahrtSkizze, lngStatus)
Das hat in der Programmierung vor allem die Bequemlichkeit, dass eine neue Information einfach durch eine weitere Konstante wie Const perEmail = 16 berücksichtigt werden kann
Wenn Sie in einer Office-Datei Meta-Daten wie "Letzter Zugriff", "Änderung durch" o.ä. speichern wollen, bieten sich die benutzerdefinierten Eigenschaften an. Diese erreichen Sie per Menü Datei Eigenschaften auf der Registerkarte "Anpassen". Lästig ist bei diesen nur, dass noch nicht vorhandene Eigenschaften erst erzeugt werden müssen, ansonsten gibt es eine Fehlermeldung. Daher bietet es sich an, das Problem in eine eigene Prozedur auszulagern, welche das abfängt:
With ActiveDocument.CustomDocumentProperties
varTest = .Item(strName).Value
If Err.Number = 0 Then
On Error GoTo 0
Die Erläuterungen zum Einsatz einer Property finden Sie im Tipp "Properties statt Sub/Function". Bitte beachten Sie, dass ActiveDocument ein spezifisches Word-Objekt ist, bei Excel heißt es beispielsweise ActiveWorkbook. Dies müssen Sie für das gewünschte Office-Programm anpassen.
Hier geht es vor allem darum, dass bei ausgeschalteter Fehlerbehandlung per Zugriff auf den Inhalt der benutzerdefinierten Eigenschaft zuerst geprüft wird, ob diese schon existiert. lässt sich fehlerfrei ein Inhalt auslesen, kann direkt ein neuer Wert (gleichen Typs!) hineingeschrieben werden. Im Fehlerfall muss diese Eigenschaft erst neu erzeugt werden.
Entsprechend soll auch beim Auslesen des Wertes eine gar nicht vorhandene Eigenschaft keinen Fehler auslösen, sondern einfach einen Leerstring zurückgeben. Dies erreichen Sie mit dem folgenden Code:
With ActiveDocument.CustomDocumentProperties
CustomProp = .Item(strName).Value
If Err.Number <> 0 Then
On Error GoTo 0
Zum Testen brauchen Sie noch eine Prozedur, welche verschiedene Werte speichert und diese anschließend wieder ausliest:
CustomProp("Letzter Benutzer") = Application.UserName
MsgBox "Inhalt von 'Letzter Benutzer': " & _
"Inhalt von 'Gibt es nicht': " & _
CustomProp("Gibt es nicht")
Da die benutzerdefinierte Eigenschaft Gibt es nicht nie erzeugt wurde, liefert die Property wie gewünscht einen Leerstring zurück.
Achtung! Word betrachtet die Änderungen an benutzerdefinierten Eigenschaften nicht als Änderung am Dokument. Sie würden also vor dem Schließen nicht gefragt, ob dieses Dokument gespeichert werden soll. Um das zu erzwingen, setzen Sie einfach das Dokument auf ungeändert mit ActiveDocument.Saved = False.
Eigene Variablen mit mehreren Inhalten ID 444 Aktuell
Vor allem, wenn Sie mit kompexen Daten aus Tabellen arbeitet, ist es oft praktisch, unterschiedliche Datentypen in einem Paket zusammenzufassen. Das klassische Beispiel sind die Informationen rund um eine Person: Vor- und Nachname als Datentyp String, der Geburtstag als Datentyp Date und evtl. ein Status wie "ledig" als Datentyp Boolean.
Deklarieren Sie dazu auf Modulebene einen neuen Datentyp Person (dieser Name ist frei wählbar, darf aber kein VBA-Schlüsselwort sein) wie folgt:
Nachname As String
Geburtstag As Date
Ledig As Boolean
Jetzt können Sie in Ihren Prozeduren zum Beispiel so darauf zugreifen:
Dim Lieferanten(3) As Person
Kunde.Vorname = "Willi"
Kunde.Nachname = "Wichtig"
Kunde.Geburtstag = #12/24/1960#
Kunde.Ledig = True
Lieferanten(0).Nachname = "Meier"
Lieferanten(1).Nachname = "Müller"
Lieferanten(2).Nachname = "Schmidt"
Lieferanten(2).Vorname = "Pauline"
Lieferanten(3).Nachname = "Schmitt"
Lieferanten(3).Vorname = "Michael"
Wie Sie sehen, lässt sich dieser Datentyp auch als Array benutzen und muss nicht alle Datenwerte gefüllt haben.
Fehlerbehandlung ID 443
Obwohl die Fehlerbehandlung in Office-VBA noch aus dem tiefsten Mittelalter der Programmierung (vielleicht würde "frühe Kreidezeit" das Alter besser beschreiben?) stammt, nämlich mit dem berüchtigten Go To und kaum verkappten Basic-Zeilennummern, die hier als Labels/Text auftauchen, führt manchmal doch kein Weg dran vorbei. Also mutig eine Prozedur erfunden, welche auch einen Laufzeitfehler erzeugt, der dann VBA-gerecht behandelt werden soll:
Dim intA As Integer
Dim intB As Integer
Sub FehlerUnbehandelt()
intB = 0
MsgBox "Ergebnis: " & intA / intB
Die beiden Integer-Variablen sind auf Modul-Ebene deklariert, damit sie nun nicht in jeder Beispiel-Prozedur erneut deklariert werden müssen. Alle folgenden Prozeduren müssen sich also im gleichen Modul befinden.
Die Prozedur lässt sich zwar mit F5 starten, scheitert aber schon in der intA = 123456-Zeile, weil der höchste zulässige Wert für eine Integer-Variable eben 32.767 und damit hier überschritten ist. Natürlich wäre es einfach, daraus eine Variant-Variable zu machen, um den Laufzeitfehler zu beseitigen. Das ist aber nicht Sinn der Sache, denn wir wollen ja möglichst einfach zu einem Laufzeitfehler kommen, um den Umgang damit zu zeigen.
Das zweite Beispiel aktiviert die Fehlerbehandlung mit On Error GoTo Mist ab dieser Zeile. Tritt danach ein Fehler auf, springt die Code-Ausführung sofort zu der mit dem Label Mist: (Achtung, diesmal mit Doppelpunkt dahinter!) gekennzeichneten Zeile. Damit der Code nicht immer dorthin gerät, muss vorher ein Exit Sub stehen:
intA = 123456
intB = 0
MsgBox "Ergebnis: " & intA / intB
Exit Sub
Der Fehler ist behebbar, indem einfach der Maximalwert stattdessen genommen und dann das Programm mit Resume Next in der Folgezeile weitergeführt wird:
intA = 32767
Resume Next
Jetzt bleibt allerdings noch ein zweiter Fehler übrig: die Division durch 0 ist nicht erlaubt. Das testen Sie am besten mit F8 zeilenweise, denn ansonsten hängen Sie in einer Endlosschleife: der Divisionsfehler tritt auf, die Variable intA wird korrigiert, es wird unverändert durch intB = 0 dividiert und der Fehler tritt wieder auf. Sie müssen also die Fehler unterschiedlich behandeln, was letztlich zu folgendem Code führt:
On Error GoTo FehlerB
On Error GoTo FehlerC
Exit Sub
intA = 32767
Resume Next
Resume KorrekturB
intB = 1
Resume KorrekturC
End Sub
Nur zur Erinnerung sind die eigentlichen Programmzeilen fett markiert. Aus bescheidenen 3 Zeilen Code sind wegen der Fehlerbehandlung nun 27 Zeilen geworden. Das kann es ja nun wirklich nicht sein! Stellen Sie sich vor, nach diesem Konzept wollen Sie größere Prozeduren behandeln...
Da es leider nicht (wie in modernen Programmiersprachen und in zukünftigen VBA-Versionen übrigens auch) eine try...catch...fail-Anweisung gibt, welche die Fehlerbehandlung blockweise ohne das furchtbare Hin- und Herspringen im Code erledigt, simulieren wir das einfach ein bißchen:
intA = 123456
If Err.Number <> 0 Then
intA = 32767
Err.Clear
intB = 0
If Err.Number <> 0 Then
Err.Clear
If Err.Number <> 0 Then
intB = 1
Err.Clear
GoTo Nochmal
Auch hier sind die ursprünglichen Code-Zeilen wieder fett. Auf den ersten Blick sind es natürlich fast ebenso viele Zeilen, aber der Code ist nun gradlinig und springt nicht zwischen Zeilen hin- und her. Meistens können Sie auf diese Fehlerbehandlung sogar noch weitgehender verzichten, wenn es nämlich egal ist, ob es geklappt hat, wie im folgenden Beispiel:
On Error Resume Next
Kill "c:meinPfadmeineDatei.txt"
On Error GoTo 0
Mit On Error Resume Next wird die Fehlerbehandlung ausgeschaltet. Falls die Kill-Zeile scheitert, weil die genannte Datei gar nicht existiert oder aus anderen Gründen nicht gelöscht werden kann, wird das ignoriert. Mit On Error GoTo 0 ist die Original-Fehlerbehandlung von VBA wieder aktiv (und die Zeilennummer 0 aus frühesten Basic-Tagen feiert fröhliche Urständ!).
Leerzeichen entfernen ID 440
Vor allem beim Import von Daten aus Textdateien oder dem Internet enthalten die Texte oft überflüssige Leerzeichen. Während es noch recht leicht ist, die Leerzeichen vor oder nach dem Text mit der Trim()-Funktion zu entfernen, berücksichtigt diese keine mehrfachen Leerzeichen mitten im Text. Da müssen Sie selbst etwas programmieren, beispielsweise so:
Da die Trim()-Funktion enorm schnell ist, lohnt es sich, mit dieser erst die äußeren Leerzeichen zu entfernen, bevor die eher langsame Replace()-Funktion eingesetzt wird. Testen Sie das beispielsweise mit folgender Prozedur:
Das ist sehr erfolgreich, vielleicht sogar zu erfolgreich, denn es sind nun alle Leerzeichen entfernt: "HiersindvieleLückenimText!". Also brauchen Sie eine Funktion, welche nur mehrfache Leerzeichen entfernt und zwischen den Worten jeweils eines übrig lässt. Die gibt es leider nicht fertig, also folgt der zweite Versuch:
Dim intPos As Integer
strAlt = Trim(strText)
For intPos = 1 To Len(strAlt)
LeerRaus2a = LeerRaus2a
Dabei wird jeweils ein Buchstabe von der Zeichenkette strAlt in die Zeichenkette LeerRaus2a umkopiert, es sei denn, deren letztes Zeichen ist schon ein Leerzeichen. So kommt maximal ein Leerzeichen dort an. Das funktioniert zwar, liest sich aber doch eher umständlich.
Als Kompromiß lässt sich in der nächsten Funktion auf die Replace()-Funktion zurückgreifen, welches diesmal zwei Leerzeichen gegen eines austauscht. Bei drei Leerzeichen sind dann allerdings noch zwei übrig, so dass dies also so lange wiederholt werden muss, bis keine doppelten Leerzeichen mehr enthalten sind:
Dim intPos As Integer
str1 = Trim(strText)
intPos = InStr(str1, " ")
Do Until intPos = 0
intPos = InStr(str1, " ")
LeerRaus2b = str1
Das liefert ebenso ein brauchbares Ergebnis wie LeerRaus2a, ist aber deutlich kompakter geschrieben.
Sobald Ihr VBA-Projekt ein wenig größer wird, braucht es eine vernünftige Bedienoberfläche und damit früher oder später Menüs. Diese lassen sich zwar auch manuell in der jeweiligen Office-Oberfläche mit Extras Anpassen erstellen, aber das ist zum einen eher umständlich und hat zum anderen weitaus weniger Möglichkeiten als die programmgesteuerte Erstellung von Menüs. Dabei sind Symbolleisten und Menüs technisch gesehen identisch, die einen eben mit Symbolen und die anderen textorientiert.
Da mit Office 2007 die "Ribbons" (Registerkarten-Menüs/Symbolleisten) als komplett neues Konzept eingeführt wurden und diese über XML-Dateien gesteuert werden, gilt dieser Tipp nur für alle vorherigen Versionen. Obwohl der Code erfreulicherweise einheitlich in allen Office-Programmen funktioniert, gibt es doch ein paar wenige Unterschiede:
- Visio ist kleinlich und verlangt Application.Commandbars, während Word, Excel oder Access auch schon mit Commandbars zufrieden wären. Das ist aber im Sinne einer exakten Angabe des Elternobjekts auch sauberer programmiert.
- Outlook hat die Symbolleisten nicht als Objekt der Application (also der Anwendung im Hintergrund, welche die Daten verwaltet!) eingerichtet, sondern hat diese sinnvollerweise an die sichtbare Oberfläche gebunden, so dass es hier ActiveExplorer.Commandbars heißen muss.
Damit existiert eine neue, leere Symbolleiste. Wenn Sie diesen Code aber ein zweites Mal aufrufen, wird noch eine gleichnamige Symbolleiste erstellt, daher sollten Sie auch schon vorbereiten, diese Symbolleiste automatisch löschen zu können:
Application.CommandBars("Test").Delete
On Error GoTo 0
Die "Fehlerbehandlung" ist notwendig, falls diese Symbolleiste noch gar nicht existiert. Jetzt können wir der Symbolleiste zwei Menütitel hinzufügen, die deshalb vom Typ msoControlPopUp sein müssen. Normale Symbole oder Menüeinträge wären vom Typ msoControlButton, wie Sie weiter unten noch sehen werden. Wenn der Debugger eine undefinierte Variable (nämlich die msoControl...-Konstante) meldet, müssen Sie unter Extras Verweise noch "Microsoft Office nn.0 Object Library" (mit nn als passender Versionsnummer) hinzufügen.
With .Controls.Add(msoControlPopup)
Zu den Menütiteln braucht es anschließend noch die ausklappenden Menüs, die wiederum selber Untermenüs enthalten können. Daher ist die Menüstruktur rekursiv, das heißt an einer Symbolleiste hängen Symbole, an diesen (wenn Sie vom Typ msoControlPopUp sind) wiederum Symbolleisten mit Symbolen (nämlich die Menüeintragsliste). Sollte sich unter den Menüeinträgen ein Untermenü befinden, wiederholt sich diese Konstruktion. Fangen wir daher zuerst mit einem einfachen Menü ohne Schnickschnack an:
.OnAction = "SagHallo"
With .CommandBar.Controls.Add(msoControlButton)
.OnAction = "SagHallo"
Die OnAction-Eigenschaft nennt den Namen der Prozedur, die beim Klick auf den Menüeintrag aufgerufen wird. Die muss natürlich vorhanden sein, und zwar schon beim Erstellen, also braucht es diese Prozedur wie folgt:
Damit die Prozedur weiß, von wem sie ausgelöst wurde, können Sie auf ActionControl zurückgreifen, welches automatisch das entsprechende Objekt enthält. Hier zeigt es nur die Beschriftung des jeweiligen Menüeintrags an, damit Sie sehen, dass es korrekt arbeitet.
Damit ist die Grundstruktur eines einfachen Menüs fertig und wir können uns auf etwas Komplizierteres stürzen wie ein Menü mit Untermenü oder auf Menüeinträge, die ihren Status ändern:
With .CommandBar.Controls.Add(msoControlButton)
.OnAction = "MeldeFarben"
.Tag = "Kirschen und Erdbeeren"
With .CommandBar.Controls.Add(msoControlButton)
.OnAction = "MeldeFarben"
.Tag = "Bananen"
With .CommandBar.Controls.Add(msoControlButton)
.OnAction = "MeldeFarben"
.Tag = "Himmel und Wasser"
With .CommandBar.Controls.Add(msoControlButton)
.OnAction = "Umschalten"
Mit obigem Code wird im Menü "Kompliziert" ein Untermenü namens "Auswahl" eingefügt und an dieses die drei Menüeinträge "rot", "gelb" und "blau" angehängt. Damit diese auch mal etwas anderes machen, als später per MsgBox ihre Beschriftung zu nennen, lassen sich beispielsweise in der jeweiligen Tag-(Marke-)Eigenschaft beliebige Hilfsinformationen speichern, auf welche die Prozedur MeldeFarben später zugreifen kann:
' eigentlich gehört das folgende in eine Zeile!
MsgBox "Die Farbe '" & Replace(.Caption, "&", "") & _
Die Replace-Funktion dient hier nur dazu, das &-Zeichen aus der Menüeintrag-Beschriftung zu entfernen, damit es hübscher aussieht.
Sicherlich kennen Sie in vielen Office-Programmen auch die Menüeinträge, die durch ein Häkchen davor anzeigen, dass irgendetwas aktiviert ist (wie in Word beispielsweise Ansicht Normal). Diese Fähigkeit zum Umschalten bringen Menüeinträge schon mit, allerdings ist die zugehörige StateEigenschaft ärgerlicherweise nicht in der automatisch ausklappenden Methoden- und Eigenschaften-Liste enthalten. Schreiben Sie dazu folgende Prozedur:
Das ist die einfachste Variante, welche mit/ohne Häkchen umschaltet, was Sie erst beim nächsten Ausklappen des Menüs sehen. Offiziell gibt es zur State-Eigenschaft die passenden msoButton...-Konstanten, wie das nächste Beispiel zeigt:
MsgBox "Ohne Häkchen!"
MsgBox "Mit Häkchen!"
Im Echtbetrieb werden Sie natürlich anstatt solcher Meldungen eine passende Aktion auslösen. Diese Beispiele hier zeigen ja auch nur die grundsätzliche Funktionalität. Damit sollten Sie nun in der Lage sein, beliebige Menüs oder Symbolleisten per VBA zu erstellen.
Wenn in einem Formular die ausgewählte Option ermittelt werden soll, ist oft ein Code wie dieser zu finden:
Die viel übersichtlichere Select Case-Struktur scheint nicht möglich zu sein, weil hier ja nicht eine Variable, sondern eben viele unterschiedliche Objekt-Eigenschaften geprüft werden müssen. Wenn Sie das Pferd aber von hinten aufzäumen, klappt es dennoch. Prüfen Sie also statt der Objekt-Eigenschaft Value die Konstante True:
Case Me.OptionButton1.Value
Damit lassen sich die Ergebnisse von Optionsschaltflächen viel übersichtlicher verarbeiten.
Oft ist es hilfreich, für die Argumente in selbstgeschriebenen Prozeduren eine eingeschränkte Auswahlliste vorzugeben, wie das beim Aufruf der vorhandenen VBA-Prozeduren ja auch der Fall ist. Im Beispiel eines Fehlerprotokolls etwa sollen die Fehler in leichte, schwere und sehr schwere Typen unterschieden werden. Normalerweise würden Sie eine Prozedur wie die folgende schreiben:
Bei jedem späteren Aufruf im Code schreiben Sie als zweites Argument die Zahlen 0, 1 oder 2 hin. Oder war es doch 1, 2 oder 3? Eventuell -1, 0 oder +1? Oder gar 10, 50, 100? Jetzt hilft Ihnen eine Enumeration: wann immer zukünftig das zweite Argument bytTyp auszufüllen ist, bietet der VBA-Editor eine begrenzte und vor allem sinnvolle Liste ausgesuchter Konstanten nur für diesen Fall an. Schreiben Sie am Anfang des Moduls:
Schwer
SehrSchwer
Dabei ist Enum die Abkürzung für Enumeration, also Aufzählung. Die Definition der obigen Prozedur ändert sich bei:
Sobald Sie ab jetzt diese Prozedur im Code aufrufen, bietet der Editor automatisch eine Auflistung von eigenen Konstanten an. Damit kennen Sie jederzeit alle jeweils zulässigen Konstanten und erhalten mit wenig Aufwand viel übersichtlicheren Code.
Properties statt Sub/Function ID 447 Top-Tipp!
Um interne Variablen mit neuen Inhalten zu füllen oder diese auszulesen, werden Sie typischerweise auf ein Sub/Function-Paar wie im folgenden Code zurückgreifen:
Dim mstrWert As String
Sub SchreibeWert(strInhalt As String)
Function LiesWert() As String
Sub TesteSubFunction()
MsgBox "Inhalt: " & LiesWert()
Das mag auf den ersten Blick ja ganz nett aussehen, aber erstens brauchen Sie zwei Namen (nämlich SchreibeWert und LiesWert) und zweitens wäre es gut, wenn der Zugriff auf eine (hier übrigens Modul-öffentliche) Variable auch wie ein Zugriff aussähe. Schreiben Sie das doch einfach als Properties, denn diese können gleiche Namen in Schreiben und Lesen unterscheiden, wie dieser Code zeigt:
Dim mstrWert As String
Property Let Wert(strInhalt As String)
Property Get Wert() As String
Sub TesteProperties()
Diesmal reicht ein einziger Name Wert zum Lesen und Schreiben und er wird außerdem wie eine Variable behandelt, da ein neuer Inhalt einfach per Gleichheitszeichen zugewiesen wird.
Im Tipp "CustomDocumentProperties schreiben und lesen" finden Sie ein Beispiel, wie Sie einer solchen Property einen (oder bei Bedarf auch mehrere) Parameter mitgeben, in diesem Fall den Namen der benutzerdefinierten Eigenschaft.
Textdateien lesen ID 450 Top-Tipp!
Auch wenn in der jeweiligen Office-Anwendung ein spezialisiertes Dateiformat zur Verfügung steht, gibt es oft genug Anlässe für den direkten Zugriff auf Textdateien. Beispielsweise können Sie damit vor dem eigentlichen Import noch störende Datei-Header aus den Daten entfernen oder einfach Protokolldateien erzeugen, die auch beim Absturz des Programms noch Informationen über die letzten Aktionen enthalten.
Um auf eine Datei zugreifen zu können, müssen Sie zuerst eine Nummer für einen freien Lese-/Schreib-"Kanal" erhalten. Hier wird oftmals einfach eine 1 benutzt, dabei liefert FreeFile sehr einfach eine gültige Nummer. Dann müssen Sie nur noch festlegen, wie Sie auf die Datei zugreifen wollen.
Ab jetzt können Sie die Datei-Inhalte zeilenweise in eine Variable (hier strDatei) einlesen, etwa bis ein bestimmter Text gefunden wurde:
Dim lngKanal As Long
lngKanal = FreeFile()
Open "C:DatenTest.txt" For Input As #lngKanal
Do Until EOF(lngKanal)
If InStr(strZeile, "Summe") > 0 Then
Exit Do
Close lngKanal
Textdateien schreiben ID 451 Top-Tipp!
Das Schreiben von Daten in eine Textdatei funktioniert ähnlich wie im Tipp "Textdateien lesen", indem Sie zuerst eine freie Kanal-Nummer zum Schreiben von der Funktion FreeFile erhalten. Damit können Sie einen neuen Kanal zum Schreiben in einen Dateinamen öffnen.
Falls die Datei bereits existierte, wird sie komplett überschrieben. Das können Sie verhindern, indem Sie statt Output die Einstellung Append wählen. Dann erfolgt das Hinzufügen der Zeilen am Ende der bereits vorhandenen Datei:
Dim intZeile As Integer
lngKanal = FreeFile()
Open "C:DatenTestSchreiben.txt" For Output As #lngKanal
Print #lngKanal, "Hier geht es los"
Print #lngKanal, "" 'für eine Leerzeile
For intZeile = 1 To 5
Print #lngKanal, ""
Print #lngKanal, "Das ist die letzte Zeile"
Close lngKanal
Bevor Sie, wie beispielsweise im Tipp "Textdateien schreiben" eine vorhandene Datei versehentlich löschen, sollten Sie vorher prüfen, ob diese bereits existiert. Das lässt sich mit einer kleinen Funktion schnell und einfach bewerkstelligen:
Wenn die Datei (inklusive Pfadangabe natürlich) existiert, liefert die Dir-Funktion deren Namen zurück, so dass also die Länge des gelieferten Ergebnisses größer als 0 ist.
Ebenso können Sie mit einer solchen Funktion prüfen, ob ein Verzeichnis vorhanden ist, indem Sie den zweiten Parameter der Dir-Funktion passend einstellen:
Dabei wird der komplette Pfad gleichzeitig geprüft, es reicht also nicht, dass das Hauptverzeichnis vorhanden ist oder nur das Laufwerk stimmt.
Hinter dem Datum/Zeit-Datentyp verbirgt sich ein so genanntes Serielles Datum, also vor dem Komma die Anzahl der Tage seit einem Startdatum (und nach dem Komma die Anteile an den heutigen 24 Stunden). Das Startdatum ist der Jahreswechsel 1900, aber der erste Tag ist nicht Neujahr 1900!
Mit der folgenden Prozedur können Sie sich anzeigen lassen, an welchem Tag für VBA diese Zeitrechnung beginnt:
Sie werden es kaum glauben: die Zeitrechnung startet am 30.12.1899 und nicht Silvester 1899 oder Neujahr 1900! Das bedeutet, daß der 1.1.1900 schon der Tag 2 ist. Daher wird zum Beispiel auch im Tipp "Mehr als 24 Stunden anzeigen" für den Wert 3,99 als Ergebnis der 2.1.1900 angezeigt.
Während man sich hier schon fragt, wer sich diesen merkwürdigen Datumsstart am vorletzten Tag des Jahres ausgedacht hat (trinken die bei Microsoft schon vor Silvester?), ist bei Excel noch eine besonders gefährliche Besonderheit zu beachten: in Tabellen ist das Startdatum einen Tag später!
Geben Sie dort in eine beliebige Tabellenzelle =TEXT(1;"TT.MM.jjjj") als Formel ein und finden als Ergebnis 01.01.1900. Hier ist also tatsächlich Neujahr 1900 auch der erwartete Tag 1. Das funktioniert ebenso, wenn Sie sich statt der Zahl 1 mit =TEXT(A1;"TT.MM.jjjj") auf eine Zelladresse beziehen, welche ein Datum enthält.
Lesen Sie dieselbe Zelle per (Excel-)VBA aus, bedarf es nur einer geringfügigen Änderung am Code:
Obwohl die Zelle offensichtlich den Wert 01.01.1900 anzeigt, ermittelt VBA nun den 31.12.1899 als Inhalt! Das ist genauso falsch wie Ihre Vermutung, daß der Tag 2 der 2.1.1900 sei, denn dabei handelt es sich schon um den dritten Tag dieser Zeitrechnung.