Die meisten Beispiele für Office-Programmierung sind in Visual Basic abgefasst. Das liegt daran, dass die Programmiersprache Visual Basic in allen Office-Anwendungen (Word, Excel. Outlook, PowerPoint, ...) eingebaut ist. Dieses eingebaute „Office-Basic“ wird Visual Basic for Applications (VBA) genannt. VBA ist allerdings am Ende seiner Lebenszeit angekommen und wird nicht mehr weiterentwickelt. Neue Projekte sollte man deshalb lieber mit Visual Studio und einer .NET Sprache seiner Wahl aufsetzen. Da .NET relativ neu ist, gibt es in Verbindung mit Office-Programmierung momentan noch viel mehr Informationen, Beispiele und Artikel zu VBA, als zu C#. Deshalb kommt man häufig mit Beispielen in VBA in Kontakt. Dieser Artikel soll helfen, VBA-Code besser zu verstehen und zeigen, wie man ihn in C# übersetzt. Mit Visual Basic ist hier VBA und Visual Basic 6 gleichermaßen gemeint, aber nicht Visual Basic.NET. Der Einfachheit halber spreche ich in den folgenden Abschnitten immer von VBA. Natürlich erhebt dieser Artikel keinen Anspruch auf Vollständigkeit!
Zwischen VBA und C# bestehen einige Unterschiede, die sich bei der Übersetzung als Stopperfallen erweisen können.
Beispiele für Syntaxunterschiede
Die VBA Syntax orientiert sich stark an der menschlichen Sprache. Die meisten Sprachkonstrukte sind sind Wörter. Bei C# kommen häufiger eher kryptische Zeichen zum Einsatz, statt Wörter. Hier ein Beispiel:
VBA
If myObject Is Nothing Then
Set myObject=New SomeComponent.SomeClass
End If
If (myObject==null)
{
myObject=new SomeComponent.SomeClass();
}
In C# werden Blöcke mit geschweiften Klammern begrenzt und in VBA mit entsprechenen Schlüsselwörtern. Dabei gibt es nicht immer ein logisches Schema, wie „If“ und „End If“. Das kann man besonders bei Schleifen-Strukturen sehen. Eine For-Schleife wird mit dem Schlüsselwort Next geschlossen und eine While-Schleife z.B. mit Wend.
VBA
Dim i as Integer
For i=1 To 10
' Aktuellen Zählerwert in Messagebox darstellen
MsgBox i
Next
i=1
While i<11
' Aktuellen Zählerwert in Messagebox darstellen
MsgBox i
i=i+1
Wend
for(Int16 i=1;i<11;i++)
{
// Aktuellen Zählerwert in Messagebox darstellen
MessageBox.Show(i.ToString());
}
i=1;
while (i<11)
{
// Aktuellen Zählerwert in Messagebox darstellen
MessageBox.Show(i.ToString());
i++;
}
**Verhalten von Operatoren **
Unterschiede gibt es auch bei verschiedenen Operatoren. VBA verwendet z.B. den Gleich-Opertor = für Zuweisungen und Werteprüfungen gleichermaßen. Bei C# muss für Werteprüfungen ein doppeltes Gleich == verwendet werden, da es sonst als Zuweisung angesehen wird. Der Operator für die Negierung ist in VBA das Schlüsselwort „Not“ und in C# ein Ausrufezeichen !. Der Zustand einer nicht zugewiesenen Objektvariable wird in VBA mit „Nothing“ und in C# mit „null“ ausgedrückt. Um auf Nothing zu prüfen, darf in VBA nicht, wie vielleicht jetzt vermutet, der Gleich-Operator = verwendet werden, sondern das Schlüsselwort „Is“, da es sich um einen Objektvergleich handelt. Inkrement (z.B. i++) oder etwas vergleichbares gibt es in VBA überhaupt nicht (Stattdessen wird z.B. i=i+1 verwendet).
Groß- und Kleinschreibung
Ein weiterer grundsätzlicher Unterschied der beiden Sprachen ist die Interpretation von Groß- und Kleinschreibung. VBA macht da keinen Unterschied. „FOR I=1 TO 10“ ist das selbe wie “For i=1 To 10” oder “for i=1 to 10”. VBA ist also nicht “Case Sensitive“. Bei C# ist das anders. Alles muss peinlichst genau so geschrieben werden, wie es definiert wurde. „For(int16 i=0;i<11;i++)“ würde z.B. an zwei Stellen zu einem Fehler führen: „For“ muss immer klein, also „for“ geschrieben werden und der Datentyp heißt „Int16“ und nicht „int16“.
Variablendeklaration
Variablen werden in VBA mit den Schlüsselwort „Dim“ (Für Dimensionieren) erzeugt. Der gewünschte Datentyp wird dabei mit dem Schlüsselwort „As“ hinter den Variablennamen gehängt. Im Gegensatz zu C# muss man Variabeln nicht zwingend vor der ersten Verwendung erzeugen. Man kann einfach eine Variable verwenden. Bei der ersten Verwendung wird die Variable dann automatisch angelegt. Der Datentyp ist dabei immer Variant. Variant ist ein Datentyp, der beliebige Daten enthalten kann. Auch die Definition mehrerer Variablen in einer Zeile führt in VBA zu anderen Ergebnissen, als in C#. So erzeugt „Dim a As Integer,b,c“ in VBA nicht etwa drei Integer-variablen mit den Namen a,b und c, sondern eine Integer Variable mit dem Namen a und zwei Variant-Variablen mit den Namen b und c. Wenn einer Objektvariable (z.B. vom Typ Word.Document) in VBA etwas zugewiesen werden soll, muss dies mit dem Schlüsselwort „Set“ getan werden. Primitive Typen wie Integer, String, Double etc. werden hingegen mit „Let“ zugeweisen. Das Schlüsselwort „Let“ kann aber auch komplett weggelassen werden („Let a=1“ ist das selbe wie „a=1“). VBA unterstützt keine Zuweisung eines Wertes bei der Variablendefinition. Hier ein paar Beispiele zum besseren Verstandnis:
VBA
' 32-Bit Integer Variable erzeugen und zuweisen
Dim objectCount As Long
objectCount=23
' Aktuelles Word-Dokument einer neuen Objektvariable zuweisen
Dim myWordDoc As Word.Document
Set myWordDoc=myWordApp.ActiveDocument
' Neue Variant-Variable erzeugen (Wenn kein Typ angegeben ist, wird Variant verwendet)
Dim unspecifiedContent
// 32- Bit Integer Variable erzeugen und zuweisen
int objectCount=23;
// Aktuelles Word-Dokument einer neuen Objektvariable zuweisen
Word.Document myWordDoc=myWordApp.ActiveDocument;
// Variable erzeugen, die Werte eines VBA-Variant aufnehmen kann
object unspecifiedContent;
Zugriffsmodifizierer
Zugriffsmodifizierer (Private, Public, etc.) haben in VBA teilweise andere Namen, als in C#. Hier eine Liste:
Private (private) Nur innerhalb der eigenen Klasse sichtbar
Public (public) Auch für externe Klassen sichtbar
Friend (internal) Nur innerhalb des eigenen Namensraums sichtbar
(protected) Nur für abgeleitete Klassen sichtbar
Bei VBA gibt es keine Implementierungsvererbung (aber Schnittstellenvererbung). Deshalb gibt es auch keinen Zugriffsmodifizierer „Protected“.
Deklaration von Prozeduren, Funktionen und Eigenschaften
Für die Definition von Prozeduren, Funktionen und Eigenschaften gibt es in VBA spezielle Schlüsselwörter. In C# entscheidet der vorangestellte Datentyp für den Rückgabewert und die Klammen für Parameter. Bei Eigenschaften muss in VBA im Zuweisungs-Teil zwischen primitiven Datentypen und Objekten unterschieden werden. Primitive Datentypen werden mit „Property Let“ zugewiesen und Objekte mit „Property Set“ Hier ein paar Beispiele:
VBA
' Eine Prozedur (Also ohne Rückgabewert)
Public Sub DoSomething(ByVal someParameter As String)
MsgBox “Hallo Welt: “ + someParameter
End Sub
' Eine Funktion (Der Datentyp des Rückgabewerts steht hinten)
Public Function Calculate(ByVal amount1 As Currency,ByVal amount2 As Currency) As Currency
' Rückgabe erfolgt über den Funktionsnamen, anstatt über return, wie bei C#
Calculate=amount1 + amount2
End Function
' Eine Eigenschaft, die einen primitiven Datentyp zurückgibt
Public Property Get Amount() As Currency
Amount=totalAmount
End Property
' Eine Eigenschaft die einen primitiven Datentyp festlegt
Public Property Let Amount(ByVal value As Currency)
totalAmount=value
End Property
' Eine Eigenschaft, die ein Objekt zurückgibt
Public Property Get CurrentDocument() As Document
Set CurrentDocument=currentWordDoc
End Property
' Eine Eigenschaft, die ein Objekt festlegt
Public Property Set CurrentDocument(ByVal wordDocument)
Set currentWordDoc=wordDocument
End Property
// Eine Prozedur (Also ohne Rückgabewert)
public void DoSomething(string someParameter)
{
MessageBox.Show(“Hallo Welt: “ + someParameter);
}
// Eine Funktion (Der Datentyp des Rückgabewerts steht vorne)
public decimal Calculate(decimal amount1, decimal amount2)
{
return amount1 + amount2;
}
// Eine Eigenschaft, die einen primitiven Datentyp zurückgibt oder festlegt
public decimal Amount
{
get {return totalAmount;}
set {totalAmount=value;}
}
// Eine Eigenschaft, die ein Objekt zurückgibt oder festlegt
public Document CurrentDocument
{
get {return currentWordDoc;}
set {currentWordDoc=value;}
}
Parameterübergabe “By Value” und “By Reference”
Bei den VBA Funktionen im obigen Beispiel fällt sofort das Schlüsselwort “ByVal” auf, welches vor jedem Parameter steht. „ByVal“ steht für „By Value“ (zu deutsch: Bei Wert). Das bedeutet, dass der Wert, welcher dem Parameter übergeben wird in eine neue Stelle im Hauptspeicher kopiert wird und die Funktion mit der Kopie arbeitet. Wenn der Inhalt der Variable geändert wird, ändert dies nur den Inhalt der Kopie, nicht aber den Originalwert. Das ist auch die Standardvorgehensweise der Parameterübergabe in C#. Wenn man diese Wörtchen „ByVal“ in VBA weg lässt, sieht die Sache ganz anders aus. In dem Fall verwendet die Funktion eine Referenz auf den Originalwert im Hauptspeicher. Das ist sehr gefährlich, da eine Änderung der Parameter-Variable innerhalb der Funktion automatisch den Originalwert verändert. Leider ist dies das Standardverhalten in VBA. Dieser Art der Parameterübergabe nennt man „By Reference“ (zu deutsch: Bei Referenz), was man in VBA auch durch das Sychlüsselwort „ByRef“ ausdrücken kann. In C# muss das Schlüsselwort „ref“ verwendet werden, um eine Parameterübergabe „By Reference“ zu erzwingen. Auch beim Aufruf einer solchen Funktion in C# muss „ref“ angegeben werden. VBA und C# haben in Sachen Parameterübergabe ein genau umgekehrtes Standardverhalten. Das wird oft nicht bedacht und führt beim Übersetzen von VBA-Code in C# zu Fehlern, die für ungeübte oft nur schwer auszumachen sind (Plötzlich haben z.B. Variablen seltsame andere Wert, die man nie zugewiesen hat).
VBA
Public Sub TestByRef()
' Neue Nummern-Variable erzeugen und Eins zuweisen
Dim myNum As Long
myNum=1
' ShowNextValue aufrufen (Achtung! Parameterübergabe erfolgt By Reference!)
ShowNextValue myNum
' ShowNextValue hat den Wert von myNum nun um Eins erhöht!
' Als Beweis, den geänderten Wert in einer Messagebox anzeigen
MsgBox „Nach Ausführung von ShowNextValue: „ + CStr(myNum)
End Sub
Public Sub ShowNextValue(number As Long)
' Nummer um 1 erhöhen
number=number + 1
' Erhöhte Nummer in einer Messagebox anzeigen
MsgBox „ShowNextValue: „ + CStr(number)
End Sub
public void TestByRef()
{
// Neue Nummern-Variable erzeugen und Eins zuweisen
int myNum=1;
// ShowNextValue aufrufen (Achtung! Parameterübergabe erfolgt By Reference!)
ShowNextValue(ref myNum);
// ShowNextValue hat den Wert von myNum nun um Eins erhöht!
// Als Beweis, den geänderten Wert in einer Messagebox anzeigen
MessageBox.Show(„Nach Ausführung von ShowNextValue: „ + myNum.ToString());
}
public void ShowNextValue(ref int)
{
// Nummer um 1 erhöhen
number++;
// Erhöhte Nummer in einer Messagebox anzeigen
MessageBox.Show(„ShowNextValue: „ + number.ToString());
}
Behandlung von optionalen Parametern
Ein weiterer Stolperstein sind optionale Parameter in VBA. Optionale Parameter können beim Aufruf einer Funktion einfach weggelassen werden. Die entsprechende VBA-Funktion verwendet dann meistens einen definierten Standardwert oder fragt ab, ob der Parameter angegeben wurde. In der Dokumentation von VBA-Funktionen werden optionale Parameter in eckigen Klammen dargestellt. In C# steht man nun vor einem Problem, da man niemals einfach Parameter weglassen kann. Es gibt einfach keine optionalen Parameter in C#. Man erreicht ein ähnliches Verhalten mit überladenen Funktionen. Der erste Gedanke (Einfach allen Parametern null zu übergeben, die man nicht angeben möchte) funktioniert nicht. Man muss explizit sagen, dass man einen Parameter nicht angeben möchte. Dies geht durch Übergabe von System.Reflection.Missing.Value oder Type.Missing an den betroffenen Parameter. Oft kommt einem dabei wieder „By Reference“ in die Quere. Temporäre Variablen kann man nicht per Referenz übergeben. Deshalb muss man eine Variable erstellen, dieser den Missing-Wert zuweisen und diese Variable an die unerwünschten Parameter übergeben. Folgendes Beispiel zeigt, wie in Word ein neues leeres Dokument erstellt wird. In VBA kann man dies ohne weitere Angaben tun (Da alle Parameter der Documents.Add-Methode optional sind) , C# verlangt dagegen zwingend die Übergabe von vier Parametern (Ich habe das Objektmodell von Word 2000 verwendet; Bei anderen Word-Versionen kann die Parameterzahl variieren).
VBA
' Neues Word-Dokument erstellen
Dim myNewDoc As Word.Document
Set myNewDoc=myWordApp.Documents.Add
// Variable für den “Missing” Wert anlegen
object missing=Type.Missing;
// Neues Word-Dokument erstellen
Word.Document myNew=myWordApp.Documents.Add(ref missing, ref missing, ref missing, ref missing);
Datentypen
Byte (byte)
Integer (short, Int16)
Long (int, Int32)
Single (single)
Double (double)
Boolean (bool)
String (string)
Object (object)
Variant (object)
Variant (*) (decimal)
Currency (decimal Manuelle Umwandung mit System.Runtime.InteropServices.CurrencyWrapper nötig)
* Decimal existiert in VBA nur als spezieller Untertyp von Variant und muss mit dem Schlüsselwort „CDec“ zugewiesen werden.
Wenn man auf COM-Komponenten und -Anwendungen (wie z.B. Word, Excel, ...) von seinen C#-Klassen aus zugreifen will, sollte man auf jeden Fall den Namensraum „System.Runtime.InteropServices“ importieren. Dieser Namensraum enthält nützliche Tools, für den Umgang mit Objekten aus der COM-Welt.
Frühe und Späte Bindung
VBA unterstützt Frühe und Späte Bindung für den Zugriff auf Objekte. Um mit Früher Bindung auf ein Objekt zuzugreifen, muss man dessen Klassenschnittstelle kennen (Das bedeutet, dass man seinem Projekt einen Verweis auf die entsprechende Bibliothek zugefügt haben muss). Aus praktischer Sicht arbeitet man immer dann mit Früher Bindung, wenn Visual Studio Intellisense für die aufzurufenden Klasen und Methoden anbietet. In den meisten Fällen wird immer mit Früher Bindung gearbeitet.
Bei Später Bindung werden Funktionen aufgerufen, deren Signatur zur Entwurfszeit nicht bekannt ist. Der Compiler kann deshalb nicht feststellen, ob das betroffene Objekt über eine Methode mit dem aufgerufenen Namen und den übergebenen Parametern existiert. Die Bindung an das Objekt erfolgt also erst zur Laufzeit. Deshalb wird dieses Verfahren Späte Bindung genannt.
Späte Bindung kommt bei Office Programmierung mit VBA oft zum Einsatz. Um z.B. die Elemente eines Outlook-Ordners zu lesen, verwendet man die Items-Auflistung des MAPIFolder-Objekts (des entsprechenden Ordners). Die Items-Auflistung enthält die Item-Eigenschaft, die Zugriff auf die Elemente des Ordners über einen Index ermöglicht. Der Rückgabetyp der Item-Eigenschaft ist Object, da ein Outlook-Ordner verschiedene Elemente enthalten kann (E-Mails, Aufgaben, Termine, Kontakte, ...). Der Datentyp gibt keine Auskunft darüber, welches Objekt zurückgegeben wird. In VBA kann in solchen Fällen späte Bindung eingesetzt werden. Wenn es sich bei dem Outlook-Ordner z.B. um den Posteingang handelt, müssen die Elemente wohl MailItem-Objekt sein, da der Posteingang nur Nachrichten enthält ab er keine Kontakte, Termine etc. In VBA könnte der Zugriff auf den Betreff der ersten E-Mail im Posteingang folgendermaßen aussehen:
' Auf den Posteingang zugreifen
Dim inbox As Outlook.MAPIFolder
Set inbox = myOutlook.GetNamespace("MAPI").GetDefaultFolder(olFolderInbox)
' Betreff der ersten E-Mail anzeigen. Item(1) gibt Object zurück, deshalb wird Subject spätgebunden aufgerufen!
MsgBox inbox.Items.Item(1).Subject
C# unterstützt keine direkte Späte Bindung. Um auf die Subject-Eigenschaft zugreifen zu können, muss man den Rückgabewert vom Item(1) mittels Typumandlung in ein Outlook.Mailitem-Objekt umwandeln (casten). Das ist oft eines der zeitaufwändigsten Aufgaben, beim Übersetzen von VBA-Code in C#. Man muss herausfinden, in was man einen Rückgabewert umwandeln muss, damit man ihn weiterverarbeiten kann. Deshalb sollte man sich mit dem Objektmodell der jeweiligen Office-Anwendung vertraut machen, bevor man Lösungen damit entwickelt. Beispielcode per Copy & Paste übernehmen, ohne ihn zu verstehen, ist fatal. Die Lösung in C# könnte so aussehen:
// Auf den Posteingang zugreifen
Outlook.MAPIFolder inbox=myOutlook.GetNamespace("MAPI").GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
// Erste E-Mail abrufen. Item[1] gibt object zurück, deshalb muss nach MailItem gecastet werden!
Outlook.MailItem firstMail = (Outlook.MailItem)inbox.Items[1];
// Betreff anzeigen
MessageBox.Show(firstMail.Subject);
Alternativ kann man auch das Reflection-API (System.Reflection) einsetzen, um spätgebunden (komplett ohne COM-Verweise) mit Office unter C# zu arbeiten.
Ereignisse
Der Umgang mit Ereignissen unterscheidet sich zwischen VBA und C# ziemlich stark. In VBA müssen Objektvariablen mit dem speziellen Schlüsselwort "WithEvents" angelgt werden, damit man auf die Ereignisse des Objekts reagieren kann. Es gibt auch keine Möglichkeit, die Ereignisüberwachung zu unterbrechen oder zu beenden. Ereignisprozeduren werden bei VBA auch nicht explizit mit dem Ereignis verknüpft. Wenn die Signatur eine Prozedur (Variablenname + _ + Ereignisname) der Ereignisdefinition entspricht, wird sie automatisch als Ereignisprozedur angesehen und aufgerufen.
' Objektvariable mit aktivierter Ereignisüberwachung
Dim WithEvents objObserved
' Inizialisierungsmethode
Sub Class_Initialize()
' Objekt erzeugen
Set objObserved=New ObservableClass
End Sub
' Ereignisprozedur für das Ereignis "DemoEvent" des Objekts "objObserved".
Sub objObserved_DemoEvent(ByVal message As String)
' Zum Test ein Meldungsfenster anzeigen
MsgBox message
End Sub
Bei C# sieht das ganz anders aus. Die Signatur der Ereignisprozeduren wird durch Delegaten definiert. Ereignisüberwachung wird nicht beim anlegen der Objektvariable aktiviert, sondern explizit in einer beliebigen Stelle im Code. Man kann einem Ereignis auch mehrere Ereignisprozeduren zuweisen und die Zuweisungen auch nach belieben wieder entfernen. Das Beispiel von oben würde in C# etwa so aussehen:
public class EventClient
{
// Objekt, welches Ereignisse anbietet
private ObservableClass _observed;
/// <Summary>
/// Standardkonstruktor.
/// </Summary>
public EventClient()
{
// Neues Objekt erzeugen
_observed=new ObservableClass();
// Ereignisprozedur für "DemoEvent" registrieren
_observed.DemoEvent+=new DemoEventHandler(MyEventProcedure);
}
// Ereignisprozedur.
private void MyEventProcedure(object sender, DemoEventArgs args)
{
// Zum Test ein Meldungsfenster anzeigen
MessageBox.Show(args.Message);
}
}
Sehr lesenswert ist auch der folgende Artikel zum Thema VB<->C#: [http://www.microsoft.com/germany/msdn/library/office/ZehnCodeversionenFuerVBAVisualBasicNETUndCSharp.mspx?mfr=true]Zehn Codeversionen für VBA, Visual Basic .NET und C#](http://www.microsoft.com/germany/msdn/library/office/ZehnCodeversionenFuerVBAVisualBasicNETUndCSharp.mspx?mfr=true]Zehn%20Codeversionen%20f%C3%BCr%20VBA,%20Visual%20Basic%20.NET%20und%20C#)