Erzeugen Sie Grafiken mit Visio und Visual Basic

Veröffentlicht: 08. Sep 2001 | Aktualisiert: 18. Jun 2004

Von Ken Spencer

Die grafische Darstellung von Daten kann man oft auch an Visio delegieren, wodurch eine ansprechende Optik schnell und bequem realisiert werden kann.

Auf dieser Seite

Grafikausgabe per Programm
Die Arbeit mit Visio
Fazit

Diesen Artikel können Sie hier lesen dank freundlicher Unterstützung der Zeitschrift:

sys2

Grafikausgabe per Programm

Microsofts Visio war schon seit jeher gut per Programm ansteuerbar. Da ich sehr viele Anfragen erhalte, die sich um die Ansteuerung von Visio drehen, möchte ich dieses Thema einmal eingehend behandeln.

In vielen Anwendungen ist es sinnvoll, den zu beschreibenden Sachverhalt grafisch darzustellen. Wenn Sie zum Beispiel Empfänge ausrichten, müssen Sie sich vermutlich bei der einen oder anderen Gelegenheit Gedanken über die Sitzordnung machen. Eine entsprechende Karte können Sie zum Beispiel erstellen, indem Sie die darzustellenden Informationen im Listenformat erfassen und die Karte mit einem Werkeug wie Visio zeichnen. Besser aber wäre es, sie von Visio automatisch generieren zu lassen.

Die letztere Variante ist natürlich vorzuziehen. Als Beispiel für diesen Vorgang möchte ich nun die Erstellung solcher Sitzordnungskarten genauer beschreiben. Das Beispielprogramm habe ich ursprünglich in Visual Basic geschrieben, und zwar bereits 1995. Damals habe ich es in meinem Buch "Client/Server Programming with Microsoft Visual Basic" (Microsoft Press, 1995) benutzt. Ich dachte mir, es könnte interessant werden, diese Anwendung nun mit Visual Basic 6.0 und Visio 2000 zu implementieren.

Die Anwendung greift auf Daten zurück, die in einer Access-Datenbank liegen (Visitor2000.mdb, zusammen mit der Anwendung auf CD). In dieser Datenbank gibt es die beiden Tabellen Visitmaster und VisitorDemographics. Die Visitmaster-Tabelle enthält Informationen über die Aktivitäten, also Picknicks, Konferenzen und so weiter. Die VisitorDemographics-Tabelle ist über das Feld VisitName mit der Visitmaster-Tabelle verknüpft und enthält Informationen darüber, wer als Teilnehmer für das betreffende Ereignis vorgesehen ist. Außerdem wird in der VisitorDemographics-Tabelle das Geschlecht des Teilnehmers erfasst, der Abteilungsname und der Umstand, ob der Teilnehmer Wein zum Essen wünscht oder nicht.

Wenden wir uns dem Code zu. Ein Teil der Anwendung ist natürlich damit beschäftigt, die erforderlichen Informationen aus der Access-Datenbank auszulesen. Der Originalcode wurde mit RDO geschrieben (Remote Data Objects). Also habe ich nun den RDO-Code gelöscht und durch ADO-Code (ActiveX Data Objects) ersetzt, um die Dinge zu beschleunigen. Allerdings möchte ich an dieser Stelle nicht weiter auf den Datenbankcode eingehen, weil es ganz normales ADO ist und wohl jeder Leser schon weiß, wie man mit ADO umgeht.

18visio

B1 Auswahl des Ereignisses

Bild B1 zeigt die Schnittstelle der Anwendung. Die drei Befehlsschaltflächen im oberen linken Teil des Formulars erledigen die Arbeit mit Visio. Der Anwender braucht nur aus einer Auswahlliste das Ereignis auszuwählen, das ihn interessiert, und dann die Schaltfläche Set the Table anzuklicken. Dann wird Visio initialisiert und übernimmt das Zeichnen der Karte. Sobald Visio fertig ist, kann der Anwender das Ergebnis ausdrucken oder als Grafikdatei speichern, indem er die entsprechende Schaltfläche drückt.

Sobald der Anwender die Schaltfläche Set the Table anklickt, wird die SetTheTable-Funktion aufgerufen. Sie sorgt für die Erstellung der Grafik in Visio. Der Name des Teilnehmers wird von der cboVisitorList an die Prozedur übergeben. Sobald die Funktion fertig ist, sorgt die Funktion EnableCmdButtons dafür, dass alle drei Schaltflächen im Steuerelemente-Array freigeschaltet werden.

Bild B2 zeigt, wie das Ergebnis in Visio aussehen sollte. Die resultierende Visio-Grafik zeigt einen Tisch, die Anordnung der Stühle und die Verteilung der Sitzplätze. Bei jedem besetzten Stuhl ist der Name des Teilnehmers vermerkt, die Abteilung und seine Entscheidung, was den Wein anbetrifft. Außerdem sitzen die Teilnehmer immer neben Angehörigen des anderen Geschlechts, erkennbar an den kleinen Bildchen auf den Stühlen.

18visio2

B2 Die Sitzordnung in der Visio-Darstellung.

Die Arbeit mit Visio

Den Hauptteil der Arbeit übernehmen die Methoden im Modul modTableSetting. Ich habe die Attribute für die Anwendung als Objekte definiert und im Deklarationsabschnitt untergebracht. Dadurch sind sie in der Wartungsphase der Anwendung leichter zu finden.

Ich möchte nun nicht auf alle Einzelheiten eingehen, aber mir scheint es sinnvoll zu sein, zumindest die wichtigsten Definitionen zu beschreiben. Im Quelltext finden Sie zu den Objekten, die für Visio benutzt werden, entsprechende Kommentarzeilen mit kurzen Beschreibungen.

Der VisitorType ist ein anwenderdefinierter Typ, der die Informationen aus einem Teilnehmerdatensatz aufnimmt. Die Anwendung speichert den Namen, die Abteilung und die Entscheidung bezüglich des Weins:

Public Type VisitorType 
    Name As String 
    Department As String 
    Wine As String 
End Type

Die Arrays MaleVisitors und FemaleVisitors enthalten Elemente dieses Typs. Da die Anwendung mit diesen beiden Arrays arbeitet, muss sie die einzelnen Datensätze natürlich beim Einlesen aus der Datenbank entsprechend zuordnen. Immerhin wird durch diesen zusätzlichen Aufwand die spätere Zuordnung der Teilnehmer bei der Erstellung der Grafik vereinfacht (in der Funktion SetTheTable). Die nächsten beiden Zeilen definieren die Arrays MaleVisitors und FemaleVisitors. In beiden Arrays sind die Elemente vom Typ VisitorType:

  Dim MaleVisitors(1 To 9) As VisitorType 
  Dim FemaleVisitors(1 To 9) As VisitorType

Die nächste Zeile definiert zwei Zähler, in denen die Zahl der Teilnehmer erfasst wird.

 Dim CounterMale As Integer, CounterFemale As Integer

SetTheTable ist die wichtigste Funktion der Anwendung. Sie erledigt den wesentlichen Teil der Arbeit oder sorgt zumindest für deren Durchführung. Die folgenden drei Befehle legen einige Variablen an, die in dieser Funktion benutzt werden:

Sub SetTheTable(VisitName As String) 
  Dim sCurrentSex As String 
  Dim sCurrentName As String, sCurrentDepartment As String 
  Dim sWine As String

Die folgenden Konstanten werden zur Berechnung der SmartShapes-Positionen bei 0 oder 45 Grad benutzt. Die Multiplikation mit 12 dient zur Umrechnung der Werte auf Inch.

Const Degree0 = 3 * 12 
  Const Degree45 = 2.5 * 12

Die nächsten beiden Zeilen vervollständigen die Definitionen für diese Funktion. Sie definieren eine Variable für die Ereignisbeschreibung und eine weitere für den Anwendungspfad:

Dim VisitDescription As String 
  Dim sPath As String

Nun folgen einige Zeilen, mit denen die Positionen der verschiedenen Objekte in der Zeichnung festgelegt werden. Zuerst geht es um die Mitte des Tisches:

YCenter = 8.5 * 12 
  XCenter = 11 * 12

Anschließend bringen die Variablen XPos und YPos jedes Objekt an seinen vorgesehenen Platz. Für den Anfang erhalten die Variablen XPos und YPos ihre Standardwerte:

XPos = XCenter 
  YPos = YCenter + Degree45

Mit dem frmSetTable.labStatus-Steuerelement als Status-Etikett beschreibe ich die Aktionen des ActiveX-Servers während des Vorgangs Set the Table.

frmSetTable.labStatus = "Loading table from database"

Im Code werden Ihnen die eingestreuten DoEvents-Anweisungen auffallen. Sie sollen der Anwendung die Aktualisierung des Status-Etiketts ermöglichen, während die Dinge im Programm ihren Gang gehen. Falls Sie nach einer Textänderung DoEvents vergessen, kann es geschehen, dass sich die Änderung erst dann auf dem Bildschirm bemerkbar macht, wenn der betreffende Vorgang abgeschlossen ist.

Anschließend wird die Funktion LoadVisitorTables mit VisitName als Argument aufgerufen.

LoadVisitorTables VisitName

Das veranlasst LoadVisitorTables, die Gästeliste aus der Datenbank in die zwei Arrays zu laden.
Anschließend wird die Funktion GetDescription aufgerufen. Sie liest die Beschreibung des Ereignisses aus der Datenbank.

VisitDescription = GetDescription(VisitName) 
frmSetTable.labStatus = "Starting Visio"

An dieser Stelle starte ich Visio mit der Methode StartVisio. Dann setze ich die Fehlerbearbeitung zurück.

  On Error GoTo 0 
  frmSetTable.labStatus = "Creating new document in Visio"

Im nächsten Schritt wird die Variable sPath auf den aktuellen Pfad der Anwendung gesetzt, an den sich der Name der Visio-Vorlage anschließt. Ich benutze folgenden Angabe:

  sPath = App.Path & "\TableSetting.vst"

Die folgende Zeile eröffnet mit der Add-Methode eine neue Zeichnung. Diese Zeichnung beruht auf der Vorlage TableSetting.vst.

Set objTableDiagram = Visio.Documents.Add(sPath).Pages(1)

Die nächste Zeile legt eine Referenz auf die Schablone an, die in der Zeichnung benutzt werden soll.

Set objTableSettingStencil = Visio.Documents _ 
    ("Table Setting Stencil.vss")

Nach diesen Vorbereitungen setzt der Code objDocument auf das ActiveDocument-Property von Visio, um die neue Zeichnung zu repräsentieren.

Set objDocument = Visio.ActiveDocument 
frmSetTable.labStatus = "Adding table to new document"

Das objDocument wird am Ende der Funktion nicht entsorgt, damit es noch in anderen Teilen der Anwendung Verwendung finden kann.
Der folgende Befehl ist in meinem Beispiel auskommentiert. In einer Produktionsanwendung würde ich das Kommentarzeichen löschen, um die Visio-Schnittstelle beim Positionieren der Objekte abzuschalten. Dadurch wird das Programm nämlich bedeutend schneller, weil Visio dann bis zum Ende der Funktion keine Ausgaben auf dem Bildschirm mehr vornimmt.

'Visio.ScreenUpdating = False

Anschließend beginnt der Code mit der Positionierung der Objekte auf der Zeichnung. Zuerst wird der Tisch in die Mitte gerückt. Das Property stTemp enthält den Namen der Vorlage für meinen Tisch. Die nächste Anweisung legt eine Objektreferenz auf diese Vorlage an (in der Sammlung Masters).

stTemp = """Round Table 60""" 
Set shMaster = objTableSettingStencil.Masters(stTemp) 
Die Drop-Methode legt das Objekt auf die Zeichnung ab: 
Set objTableRound60 = objTableDiagram.Drop(shMaster, _ 
    XCenter, YCenter) 
frmSetTable.labStatus = "Adding title to new document"

Der nächste Codeabschnitt legt den Titel für die Zeichnung fest. Hierzu werden sieben Fuß zum YCenter-Wert addiert (in Zoll umgerechnet), damit der Titel oberhalb der Zeichnung erscheint. Das Text-Property wird in der letzten Anweisung auf den gewünschten Text gesetzt.

stTemp = "Title" 
Set shMaster = objTableSettingStencil.Masters(stTemp) 
Set objTitle = objTableDiagram.Drop(shMaster, XCenter, _ 
    YCenter + (7 * 12)) 
objTitle.Text = "Table Setting for " & VisitDescription

Die folgenden Zeilen initialisieren verschiedene Variablen.

iTableCount = 1 
sCurrentSex = "F" 
CounterMale = 1 
CounterFemale = 1

Eine While-Schleife läuft über alle am Tisch verfügbaren Plätze.

While iTableCount <= 8 
    sCurrentName = ""

Eine Select Case-Anweisung hilft bei der Berechnung der Positionen. Die Werte der X- und Y-Koordinaten hängen von der Lage des Sitzplatzes am Tisch ab. Hier dienen die Degree-Konstanten zur Positionierung der Stühle (Listing L1).

L1 Positionsbestimmung

Select Case iTableCount 
    Case 1: 
        XPos = XCenter 
        YPos = YCenter + Degree0 
    Case 2: 
        XPos = XCenter + Degree45 
        YPos = YCenter + Degree45 
    Case 3: 
        XPos = XCenter + Degree0 
        YPos = YCenter 
    Case 4: 
        XPos = XCenter + Degree45 
        YPos = YCenter - Degree45 
    Case 5: 
        XPos = XCenter 
        YPos = YCenter - Degree0 
    Case 6: 
        XPos = XCenter - Degree45 
        YPos = YCenter - Degree45 
    Case 7: 
        XPos = XCenter - Degree0 
        YPos = YCenter 
    Case 8: 
        XPos = XCenter - Degree45 
        YPos = YCenter + Degree45 
End Select 
frmSetTable.labStatus = "Adding individual - number: " & iTableCount

Eine If-Anweisung bestimmt anhand der Variablen sCurrentSex das Geschlecht des Gastes und setzt dann den Richtigen an den freien Platz (Listing L2).

L2 Mann oder Frau?

If sCurrentSex = "M" And CounterMale <= 9 Then 
    sCurrentName = MaleVisitors(CounterMale).Name 
    sCurrentDepartment = _ 
        MaleVisitors(CounterMale).Department 
    sWine = MaleVisitors(CounterMale).Wine 
    stSex = "Men" 
    CounterMale = CounterMale + 1 
    sCurrentSex = "F" 
Else 
  If CounterFemale <= 9 Then 
    sCurrentName = FemaleVisitors(CounterFemale).Name 
    sCurrentDepartment = _ 
        FemaleVisitors(CounterFemale).Department 
    sWine = FemaleVisitors(CounterFemale).Wine 
    stSex = "Women" 
    CounterFemale = CounterFemale + 1 
    sCurrentSex = "M" 
  End If 
End If

Dann lege ich für jeden Gast eine Referenz auf das dem Geschlecht entsprechende Symbol an. Zuvor wurde das Property stSex auf Men oder Women gesetzt. Wie Sie wohl schon ahnen, heißen auch die beiden Bildchen Men und Women. Wie in den vorigen Anweisungen werden zur Positionierung des Symbols und zur Beschriftung die Properties Drop und Text benutzt.

If Len(sCurrentName) > 0 Then 
    Set shMaster = objTableSettingStencil.Masters(stSex) 
    Set Shape = objTableDiagram.Drop(shMaster, _ 
        XPos, YPos) 
    stLabel = sCurrentName & " - " & sCurrentDepartment 
    stLabel = stLabel & " (" & sWine & ")" 
    Shape.Text = stLabel

Die nächsten Zeilen setzen den Text auf Fettdruck, mit Ausnahme der Anzeige "Wine".

    IndexCounter = Shape.Shapes.Count 
    Set objTextShape = Shape.Shapes(IndexCounter) 
    Set objChars = objTextShape.Characters 
    objChars.end = InStr(objTextShape.Text, "(") - 1 
    objChars.CharProps(visCharacterStyle) = visBold 
    iTableCount = iTableCount + 1 
Else 
    iTableCount = 10        ' verlasse die Schleife 
End If 
Wend

Den letzten Zeilen der Funktion bleiben die Aufräumarbeiten überlassen:

   frmSetTable.labStatus = "Finished" 
    Visio.ScreenUpdating = True 
End Sub

Wie schon erwähnt, erfolgt der Start von Visio mit der Methode StartVisio. Die Kapselung einer Programmfunktion (wie zum Beispiel den Start eines ActiveX-Servers) in einer eigenen Methode ist eine gute Programmierpraxis. Durch die Abtrennung dieses Codes können Sie die erforderlichen Fehlerüberprüfungen durchführen und brauchen keine anderen Methoden mit Codezeilen zu überschwemmen, die eigentlich nicht dort hingehören.

Sub StartVisio() 
On Error Resume Next

Sofern Visio bereits läuft, sorgt die nächste Anweisung für die erforderliche Visio-Objektreferenz. Falls Visio noch nicht läuft, tritt ein Fehler auf, der zur Weiterführung des Programms mit der nächsten Anweisung führt. Die If-Anweisung prüft den Fehler, indem sie den Err-Wert überprüft, Visio mit CreateObject startet und für die Objektreferenz sorgt.

Set Visio = GetObject(, "Visio.Application") 
    If Err Then 
        Set Visio = CreateObject("Visio.Application") 
    End If 
End Sub

Die Ereignisroutine cmdSetTable_Click des Formulars ist für die drei Schaltflächen zuständig, die für die Zusammenarbeit mit Visio vorgesehen sind. Es war bereits die Rede davon, wie die Schaltfläche Set the Table funktioniert. Werfen wir einen kurzen Blick auf die Speicherung und den Druck der Dokumente, die mit Visio erzeugt werden. Wenn der Anwender die Schaltfläche Save the Table anklickt, legt der Code den Pfad für das neue Dokument fest und ruft dann die SaveAs-Methode des ActiveDocument-Objekts auf:

sFileName = App.Path & "\" & txtFileName 
objDocument.SaveAs sFileName

Klickt der Anwender die Schaltfläche Print the Table an, wählt der Code die auszudruckende Seite aus (die erste Seite natürlich) und ruft dann die Print-Methode der Page-Referenz auf, die von der Pages-Sammlung geliefert wurde.

Set objPage = objDocument.Pages(1) 
junk = objPage.Print

Damit wären die wichtigsten Aspekte des Codes besprochen. Wenn ein Anwender mit diesem Programm arbeitet, kann er ihm die Arbeit überlassen, insbesondere die Zeichenarbeit. Außerdem kann er nach der Erstellung der Zeichnung nach Visio wechseln und das Ergebnis überarbeiten. Insgesamt wird so die grafische Darstellung von Daten aus einer Datenbank relativ einfach und flexibel.

Fazit

Einer der interessanten Aspekte dieser Anwendung ist das Objektmodell von Visio. Wie erwähnt, wurde die Anwendung bereits 1995 geschrieben, und zwar mit einer ganz anderen Version von Visio. Ich brauchte keine einzige Codezeile zu ändern, damit sie mit Visio 2000 funktioniert. Das dürfte wohl für die Stabilität des Objektmodells von Visio sprechen. Der Code funktioniert unter anderem auch deswegen noch, weil die Verbindung zu Visio mit später Bindung erfolgt. Ich weiß zwar, dass die späte Bindung langsam ist, aber immerhin geht es hier um einen Client und nicht um eine Server-Anwendung mit einer großen Zahl von Nutzern. Die reine Laufgeschwindigkeit ist also nicht der entscheidende Punkt.

Ich habe die Objektreferenzen für Visio überprüft, die sich in Visual Basic benutzen lassen. Ich stieß auf 14 Objekte, die dem Entwickler eine Menge Optionen zur Lösung von grafischen Problemen mit Visio geben.

Bevor Sie mit dem Beispielcode arbeiten können, müssen Sie noch die SetTheTable-Funktion so abändern, dass sie mit allen Einträgen (den Gästen) aus der Datenbank arbeiten kann. In der aktuellen Form funktioniert der Code nur mit einer Tabelle und acht Gästen oder weniger.