Home > Artikel > Ausgabe 8/2014 > Access-Berichte drucken per VBA

Access-Berichte drucken per VBA

  PDF ansehen

  Download PDF und Beispieldatenbank

Häufig unterschätzt man als Entwickler den Umfang von Daten, welche die Anwender der Datenbank zu Papier bringen möchten. Sollen diese Ausdrucke ansprechend gestaltet sein, so kommen Sie nicht umhin, dafür Berichte zu erstellen - der Ausdruck von Formularen sieht selten gut aus. Auch wenn Access über seine Oberfläche für den Druck alles mitbringt, stoßen Sie spätestens dann auf Probleme, wenn Sie auf die eingebauten Menüelemente verzichten und den Druck per VBA steuern wollen.

Beispieldatenbank

Die Beispiele dieses Artikels finden Sie in der Datenbank 1408_Drucken.mdb.

Drucken über Menüelemente

Öffnen Sie einen Bericht in der Seitenansicht, so präsentiert Ihnen Access ab Version 2007 ein Ribbon wie in Bild 1.

Ribbon bei Seitenansicht eines Berichts in Access 2010

Bild 1: Ribbon bei Seitenansicht eines Berichts in Access 2010

In früheren Versionen zeigt Access eine zusätzliche Menüleiste wie in Bild 2 an. Beide enthalten ein Symbol für das Drucken des Berichts. Die Funktionen unterscheiden sich indessen voneinander.

Menüleiste bei Seitenansicht eines Berichts in Access 2003

Bild 2: Menüleiste bei Seitenansicht eines Berichts in Access 2003

Während sich nach Klick auf das Drucken-Symbol im Ribbon ein Dialog zur Auswahl des gewünschten Druckers und weiterer Einstellungen öffnet (siehe Bild 3), führt dieselbe Aktion unter Access 2003 und früher unweigerlich dazu, dass der Bericht sofort auf den im System vorgegebenen Standarddrucker ausgegeben wird, oder, soweit angegeben, auf den im Berichtsentwurf über Seite einrichten eingestellten.

Dialog für die Druckerauswahl

Bild 3: Dialog für die Druckerauswahl

Dabei ist es Access egal, ob dieser Drucker gerade offline ist oder etwa über das Netzwerk nicht erreichbar. Normal landet der Druckauftrag dann in der Druckerwarteschlange und wird kommentarlos ausgeführt, nachdem der Drucker online geschaltet wurde. Das jedoch bemerken Ihre Anwender unter Umständen nicht sofort und führen die Drucken-Aktion mehrmals durch. Besser wäre es, wenn Access hier eine entsprechende Mitteilung über den Status des Druckers machen würde.

Aber auch das Drucken über den Auswahldialog hindert Access nicht daran, einen nicht verfügbaren Drucker anzusprechen. Das Ergebnis sähe dann etwa aus, wie in Bild 5. Dennoch wird der Druckauftrag, je nach Druckereinrichtung, auch hier für gewöhnlich in die Warteschlange gestellt, was zu verspäteter Ausführung des Auftrags führt und die Anwender abermals verwirrt.

Systemeinstellungen für den Druckauftrag-Spooler von Windows

Bild 4: Systemeinstellungen für den Druckauftrag-Spooler von Windows

Sollte Ihren von merkwürdigem Druckverhalten Ihrer Datenbank berichtet werden, so lohnt sich übrigens zur Analyse ein Blick in die Ereignisanzeige von Windows. Dort werden nämlich fehlgeschlagene Druckvorgänge mit Angabe von Gründen protokolliert.

Das muss allerdings unter Windows auch korrekt eingestellt sein. Sie können das überprüfen, indem sie die Eigenschaften des Druckservers inspizieren (siehe Bild 4). Sie gelangen dorthin über den Ordner Drucker und dessen Menü Datei|Server­eigenschaften....

Fehlermeldung beim Versuch, zu drucken

Bild 5: Fehlermeldung beim Versuch, zu drucken

Ohne Administratorrechte haben Sie nur passiven Zugriff auf dessen Einstellungen. Ab Windows Vista können Sie die Häkchen für die Spooler-Einstellungen nur setzen, wenn Sie die Druckverwaltung über das Startmenü|Verwaltung öffnen. Gehen Sie dort über das Kontextmenü des lokalen Druckservers auf Eigenschaften...|Erweitert.

Praktisch also wäre, wenn Ihre Datenbank den Druck nur dann ausführt, wenn der Zieldrucker wirklich erreichbar ist, oder über einen Dialog nur eben diese Drucker anböte. Wie eine solche Lösung, realisiert über VBA, aussehen könnte, zeigen wir Ihnen später.

Abgesehen von den angesprochenen Umständen haben Sie ihre Datenbank möglicherweise aber ohnehin mit eigenen Menüleisten oder Ribbon-Definitionen versehen und möchten gar nicht, dass die eingebauten Elemente in der Seitenansicht auftauchen.

Neben den durchaus nützlichen Elementen zur Seiteneinrichtung – Format, Orientierung, Seitenränder ­– enthält das Ribbon auch noch weitere Symbole für den Export des Berichts nach Excel, Word und ähnliche.

Es ist keine schlechte Idee, dem Anwender diese Funktionen vorzuenthalten, denn das Layout des Berichts unter Word oder Excel entspricht keinesfalls dem Original und führt regelmäßig zu Frustrationen.

Blenden Sie also das Ribbon oder die Menüleiste zur Seitenansicht besser aus und steuern das Drucken über eigene Oberflächenelemente, wie Buttons oder eigene Menüleisten, die VBA-Code auslösen, wie im Folgenden beschrieben.

Auch wenn Sie vom direkten Ausdruck von Formularen Abstand nehmen sollten – Datenblätter wären da eine Ausnahme –, sollen hier ein paar Anmerkungen dazu nicht fehlen. Öffnen Sie etwa das Formular frmKunden in der Beispieldatenbank.

Unter Access 2003 finden Sie in der dann erscheinenden Menüleiste ein Symbol zum Drucken. Analog zum Bericht erfolgt bei Klick eine Ausgabe auf den Standarddrucker. Den Druckerauswahl-Dialog bekommen Sie nur, wenn Sie über das Hauptmenü Datei|Drucken... gehen.

Ab Access 2007 gibt es das Drucken-Symbol im Ribbon bei geöffnetem Formular jedoch nicht mehr. Drucken können Sie das Formular unter Access 2010 etwa nur über den länglichen Umweg Datei|Drucken.

Im Backstage-Bereich sehen Sie dann die Elemente in Bild 6. Erst, wenn Sie den Eintrag zur Seitenansicht anklicken, öffnet sich das Formular in eben dieser. Im Ribbon finden sich nun die gleichen Elemente, wie beim Bericht. Die Seitenansicht kann in der Formularansicht nicht direkt aufgerufen werden.

Verschiedene Druckoptionen

Bild 6: Verschiedene Druckoptionen

Der Auswahl-Button zur Ansicht lässt unverständlicherweise nur die Entscheidung über Formular- oder Entwurfsansicht zu.

Druckeinstellungen im Bericht

In allen Versionen von Access kann für einen Bericht eingestellt werden, auf welchen Drucker dieser ausgegeben werden soll und wie dabei das Format und die Seiteneinstellungen aussehen sollen. All dies kann auch über VBA erfolgen, wobei dies vor Access 2003 noch sehr umständlich zu bewerkstelligen war.

Ab Access 2003 ist das Objektmodell weitgehend konsistent geblieben und bietet nun über die Printers-Auflistung und das Printer-Objekt komfortablen Zugriff auf die Druckfunktionen.

Manuell nimmt man die Einstellungen vor, indem man den Bericht entweder im Entwurf oder in der Seitenansicht lädt. Unter Access 2003 muss im Entwurf der Einstellungsdialog über das Hauptmenü|Seite einrichten... aufgerufen werden, in der Seitenansicht über die entsprechende Menüschaltfläche der Menüleiste Seitenansicht.

Ab Access 2007 ist der Aufruf selbsterklärend. Der Dialog an sich sieht in allen Access-Versionen gleich aus (siehe Bild 7). Nachdem die Einrichtung vorgenommen wurde, muss der Bericht noch mit den neuen Angaben gespeichert werden, was ebenfalls in der Seitenansicht möglich ist und nicht nur im Entwurf.

Dialog für die Seiteneinrichtung

Bild 7: Dialog für die Seiteneinrichtung

Neben den Einstellungen zu den Seitenrändern können Sie über den Reiter Seite das Ausgabeformat festlegen und außerdem dezidiert einen bestimmten Drucker angeben, auf den der Bericht standardmäßig gedruckt werden soll.

Diesen erreichen Sie über die Schaltfläche Drucker..., die den weiteren Dialog in Bild 8 öffnet. Das Festlegen eines bestimmten Druckers sollten Sie jedoch vermeiden, weil sonst unter Umständen jene Fehlfunktion wie oben dargestellt auftreten kann.

Weiterer Dialog für die Druckerauswahl

Bild 8: Weiterer Dialog für die Druckerauswahl

Belassen Sie es beim Standarddrucker, der gewöhnlich auf jedem System eingerichtet ist. Als Entwickler werden Sie wahrscheinlich ohnehin nicht über genau die Druckerumgebung verfügen, mit denen Ihre Anwender arbeiten.

Druckeinstellungen per VBA

Die Mühe, für jeden Bericht manuell Seiteneinstellungen einzurichten, brauchen Sie sich nicht unbedingt zu machen, da alles, was über den Dialog festgelegt wird, auch zur Laufzeit über VBA möglich ist.

Öffnen Sie die Beispieldatenbank und den Bericht rptKunden in der Seitenansicht. Wie im Entwurf festgelegt, zeigt sich dieser im Querformat. Starten Sie nun den VBA-Editor und setzen im Direktfenster die Anweisung SetOrientation ab.

Schon wechselt die Ansicht des Berichts ins Hochformat. SetOrientation ist eine Prozedur im Modul mdlDrucken der Datenbank und nur ein Beispiel für die Steuerung der Seitenansicht per VBA (siehe Listing 1).

Sub SetOrientation(Optional Orient As AcPrintOrientation = 1)

     Screen.ActiveReport.Printer.Orientation = Orient

End Sub

Listing 1: Einstellen des Berichtsformats per VBA

Die Sub-Prozedur erwartet eigentlich die Angabe der Orientierung als Parameter, doch dieser ist optional und auf 1 als Standardwert festgelegt, was der Konstante acPRORPortrait entspricht, also dem Hochformat. Wenn Sie die Anweisung SetOrientation 2 absetzen, wechselt der Bericht wieder ins Querformat.

Schlüsseln wir die einzige, aber lange Zeile der Prozedur einmal auf: Zunächst ermittelt der Ausdruck Screen.ActiveReport den aktuell in Access angezeigten Bericht. Ist kein Bericht geöffnet oder hat der Bericht nicht den Fokus, so hält die Prozedur mit einer Fehlermeldung an. Umgehen können Sie das einfach, indem Sie ein On Error Resume Next vor die Zeile stellen.

Dadurch, dass Sie keinen bestimmten Bericht angeben, sondern auf den aktuell angezeigten verweisen, wird die Prozedur universell verwendbar. Wollten Sie einen bestimmten Bericht ansprechen, so lautete die Syntax etwa

Reports!rptKunden.Printer ...

Mit dem Objekt Printer des Berichts, eine Eigenschaft, die übrigens auch Formulare haben, lässt sich die Seiteneinrichtung komplett steuern. Schauen Sie sich die Eigenschaften des Printer-Objekts im Objektkatalog des VBA-Editors für die Bibliothek Access an. Sie werden feststellen, dass alle Einstellungen, die Sie manuell über den Einrichtungsdialog vornehmen können, auch hier zu finden sind. So auch die Eigenschaft Orientation, die auf die Werte 1 oder 2 gesetzt werden kann.

Die Prozedur können Sie kopieren und modifizieren, um etwa festzulegen, wie groß die Seitenränder sein sollen (Printer-Eigenschaften LeftMargin, RightMargin), wieviele Kopien gedruckt werden sollen (Copies), welche Papiergröße im Drucker liegt (PaperSize), welcher Schacht zur Ausgabe dienen soll (PaperBin) und... welcher Drucker überhaupt angesprochen wird (DeviceName).

Letzteres ist ein String, der genau dem entspricht, was an Namen im Drucker-Ordner von Windows angezeigt wird.

Doch wie ermittelt man per VBA, wie die Drucker alle heißen? Auch hier lässt Access einen nicht im Regen stehen. Dessen Application.Objekt kennt die Auflistung Printers und hat übrigens ebenfalls ein Printer-Objekt.

Das ist jener Drucker, auf den die Anwendung standardmäßig drucken wird und im Allgemeinen identisch mit dem Windows-Standarddrucker. Setzen Sie im Direktfenster der Beispieldatenbank die Anweisung StandardDrucker ab.

Die gleichnamige Routine zeigt dann den Namen des Standarddruckers in einer MsgBox an:

MsgBox Application.Printer.DeviceName

Um die Namen aller verfügbaren Drucker zu ermitteln, bedienen Sie sich einer Funktion wie in Listing 2 aus dem Modul mdlDrucker. Sie enumeriert alle Elemente der Printers-Auflistung und gibt ein String-Array mit allen Namen zurück.

Function PrinterNames() As String()

     Dim oPrnt As Access.Printer

     Dim arrPrinterNames() As String

     Dim i As Long

     

     ReDim Preserve arrPrinterNames(Application.Printers.Count - 1)

     For Each oPrnt In Application.Printers

         Debug.Print oPrnt.DeviceName, oPrnt.Port

         arrPrinterNames(i) = oPrnt.DeviceName

         i = i + 1

     Next oPrnt

     PrinterNames = arrPrinterNames

End Function

Listing 2: Enumerieren aller Drucker des Systems

Einen Drucker der Auflistung können Sie entweder über eine Ordinalzahl ermitteln, oder über dessen Namen. Beide Methoden ergeben etwa dasselbe:

  Printers (2).DeviceName? Printers (" FRITZfax Drucker").DeviceName

Um diesen Drucker einem Bericht zuzuweisen, gehen Sie so vor:

Set Screen.ActiveReport.Printer = Printers(2)

Das geht nur so, denn falls Sie auf die Idee kämen, im existierenden Printer-Objekt des Berichts und dessen DeviceName-Eigenschaft einfach den Namen eines Druckers zu setzen, werden Sie feststellen, dass diese Eigenschaft schreibgeschützt ist. Sie müssen dem Bericht ein vorhandenes Printer-Objekt der Auflistung per Set zuweisen.

Druckereigenschaften auslesen

Im Access-Printer-Objekt suchen Sie Angaben zum Gerät selbst vergeblich. Das Objekt ist, anders, als der Name vermuten lässt, ein Abbild der Seiteneigenschaften beim Druck. Die Geräteeigenschaften bleiben außen vor. Mit Access-Bordmitteln allein lässt sich das nicht lösen. Zum Glück kann man auf aufwändige Windows-API-Routinen verzichten, wenn man das Windows Instrumentarium bemüht, die sogenannten WMI-Objekte. Das Hauptobjekt erhält man ohne Verweise über einen solchen Aufruf:

Set oWMI = GetObject( _

      "winmgmts:.\root\CIMV2")

Von diesem WMI-Objekt ausgehend kann man fast alles abfragen, was über die Systemsteuerung von Windows erhältlich ist. So auch die Druckereigenschaften:

oWMI.ExecQuery( _

         "SELECT * FROM Win32_Printer")

Die Routine WMIPrinters der Beispieldatenbank gibt über das WMI ebenfalls alle installierten Drucker in einem String-Array aus, fragt aber zusätzlich deren Eigenschaften ab, so dass die Rückgabe gefiltert werden kann.

So ist es etwa per Parameter der Funktion möglich, nur Netzwerkdrucker zurückgeben zu lassen, oder – voilà! – nur jene, die online sind! Eine Beschreibung des nicht ganz einfachen Codes der Routine kann wegen des Umfangs hier nicht gegeben werden. Kurz nur der Aufruf der Funktion: Drucker, die online sind, erhält man über

WMIPrinter (True, ePrntPropAny)

Netzwerkdrucker bekommt man über

WMIPrinter (True, ePrntPropNetwork )

Nur die lokale angeschlossenen über

WMIPrinter (True, ePrntPropLocal )

Diese Funktion macht sich der selbstgeschneiderte Drucker-Dialog im übernächsten Absatz zunutze.

Drucken per VBA

Das Einstellen der Seiteneigenschaften eines Berichts, sowie die Zuweisung eines Zieldruckers, ist die eine Sache, das Drucken des Berichts eine andere. Weder das Report-Objekt, noch das Printer-Objekt haben Methoden, um den Druck auszulösen. Zu diesem Zweck existieren stattdessen genau zwei VBA-Anweisungen von Access:

RunCommand acCmdPrint

und

DoCmd.PrintOut (...)

Die erste ist die einfachere, da sie weiter keine Parameter erwartet. Sie können Sie testen, indem Sie in der Beispieldatenbank den Kundenbericht öffnen und anschließend das Makro makPrintSimple doppelklicken. Dieses ruft lediglich die acCmdPrint-Anweisung auf. Warum per Makro und nicht über das Direktfenster von VBA sehen Sie dann am Ergebnis.

Nicht der Bericht wird gedruckt, sondern stattdessen ein Dialog wie in Bild 9 angezeigt. Grund dafür ist, dass sich die Print-Anweisung auf das aktive Objekt in Access bezieht.

Das falsche Datenbankobjekt hatte beim Drucken den Fokus

Bild 9: Das falsche Datenbankobjekt hatte beim Drucken den Fokus

Und das ist eben nicht der Bericht, sondern das eben doppelt angeklickte Makro. Access will also das Makro drucken. Damit sind wir auch schon beim Hauptproblem dieser Druckanweisung. Sie bezieht sich immer auf das im Datenbankfenster (Access 2003) oder im Navigationsbereich (Access 2007 und neuer) aktive Element.

Auch die zweite erwähnte Anweisung, DoCmd.PrintOut, hat damit zu kämpfen. Statt des Makros könnte das auch ein Formular sein, aus dem heraus sie etwa über eine Schaltfläche den Druck des Berichts anstoßen wollten. Die einzige Abhilfe ist, vor diesen Anweisungen den zu druckenden Bericht zu fokussieren. Etwa über diese Code-Zeile:

DoCmd.SelectObject acReport, "rptKunden"

Will man den Bericht nicht namentlich angeben, reicht auch diese Zeile:

    DoCmd.SelectObject acReport, _

Screen.ActiveReport.name

Damit wird der gerade offene Bericht abgefragt und anschließend selektiert. Dass das klappt, sehen Sie, wenn sie das Makro makPrintSimple2 doppelklicken.

Nun wird der Bericht tatsächlich gedruckt, nachdem Sie den erscheinenden Dialog zur Druckerauswahl bestätigt haben. Allerdings nur, wenn bei Ihnen genau der Lexmark-Drucker installiert ist, der für den Bericht vorgeben wurde.

Ansonsten erwartet Sie die Fehlermeldung aus Bild 4. Damit das Drucken auf einen anderen Drucker funktioniert, hätten Sie diesen erst im Dialog auswählen müssen. Derlei verwirrt Ihre Datenbankanwender eher, sodass es erstens ratsam wäre, den speziellen Drucker aus dem Bericht zu entfernen, und zweitens dem Bericht zur Laufzeit einen verfügbaren Drucker zuzuweisen.

Wie das geht sehen Sie an der Prozedur PrintExtended3 im Modul mdlDrucken der Beispieldatenbank, welche testweise über das Makro makPrintExtended3 aufgerufen werden kann (siehe Listing 3).

Function PrintExtended3()

     DoCmd.SelectObject acReport, Screen.ActiveReport.Name

     Set Screen.ActiveReport.Printer = _

Printers("Brother MFC-7820N USB Printer")

     DoCmd.PrintOut acPages, 1, 1, acHigh

End Function

Listing 3: Zuweisen eines Druckers an den Bericht

Nach dem Selektieren des Berichts wird ihm namentlich ein Drucker zugewiesen und anschließend die Seiten 1 bis 1 in hoher Druckqualität an ihn geschickt. Lassen Sie die Seitenzahlen weg, so werden alle Seiten gedruckt. Das empfiehlt sich auch. Denn wenn Sie falsche Seiten angeben – machen Sie den Test einfach einmal mit 2,2, obwohl der Beispielbericht nur eine Seite hat –, dann geht der Druckauftrag erst gar nicht an den Druck-Spooler, obwohl Access keinen Fehler meldet! Einfacher ist dann allerdings gleich der Aufruf mit diesem Parameter:

DoCmd.PrintOut acPrintAll

Die Seitenangaben machen auch deshalb selten Sinn, weil sie die Zahl der Berichtsseiten ohnehin nicht kennen werden. Sie hängt von zu vielen Parametern ab.

Im Beispiel wurde dem Bericht ein Druckername hartkodiert zugewiesen. In einer produktiven Umgebung entfiele das. Hier müssten Sie zunächst ermitteln, welche Drucker verfügbar sind und sich dafür etwa der beschriebenen Routinen PrinterNames() oder WMIPrinters() bedienen.

Soll der Anwender selbst entscheiden, welchen Drucker er benutzen will, Sie ihn aber nicht über die in Access eingebauten Druckerauswahldialoge leiten wollen, so nehmen Sie doch einfach unseren selbstgebauten Dialog auf Basis eines Access-Formulars!

Der kommt mit der Beispieldatenbank einmal in einer einfacheren Variante frmPrintDlg, die lediglich ein Kombinationsfeld zur Druckerauswahl anbietet, und einer erweiterten Fassung frmPrintDlg2, die zusätzlich Angaben zu Seitenzahlen und Kopien zulässt (siehe Bild 10).

Druckerauswahldialog im Eigenbau als Formular

Bild 10: Druckerauswahldialog im Eigenbau als Formular

Der Code des Formulars ist recht überschaubar, da er sich auf die Routine WMIPrinters beruft, um das Kombinationsfeld mit den Namen der verfügbaren Drucker zu befüllen.

Verfügbar heißt hier: wirklich online, da die anderen Drucker über den Aufrufparameter OnlyAvailable herausfiltert werden. Das Formular rufen Sie etwa über das Makro makPrintDlg auf, welches seinerseits die Prozedur GetPrinternameMsg anspricht (siehe Listing 4). Das Formular wird im Code als Dialog geöffnet.

Function GetPrinternameMsg()

     DoCmd.OpenForm "frmPrintDlg2", , , , , acDialog

     MsgBox Forms!frmPrintDlg2.Printername, _

         vbInformation, "Auswahl war:"

     DoCmd.Close acForm, "frmPrintDlg2"

End Function

Listing 4: Routine zum Aufruf des Dialogformulars

Damit hält der Code an dieser Stelle an, bis das Formular geschlossen oder auf unsichtbar gestellt wurde. Letzteres passiert bei Bestätigen der OK- oder Abbrechen-Buttons (Me.Visible = False).

Da das Formular dennoch weiterhin geladen ist, kann die Routine nun das Kombinationsfeld auslesen, das seine Auswahl in der Variablen PrinterName hinterlegt hat. Danach kann das Formular endgültig geschlossen werden.

Wie ein Bericht anhand des Selbstbaudialogs zum Drucker geschickt wird, finden Sie in der umfangreicheren Routine PrintViaDlg2 dokumentiert, aufrufbar über das Makro makPrintExtendedDlg2. Bedingung ist auch hier, dass ein Bericht geöffnet ist. Die Routine kombiniert die bisherigen Code-Beispiele (siehe Listing 5).

Function PrintViaDlg2()

     Dim sPrinter As String

     Dim startPg As Long, endPg As Long, bAll As Boolean

     Dim nCopies As Long

     DoCmd.OpenForm "frmPrintDlg2", , , , , acDialog, Screen.ActiveReport.Caption

     sPrinter = Forms!frmPrintDlg2.Printername

     If Len(sPrinter) = 0 Then Exit Function

     DoCmd.SelectObject acReport, Screen.ActiveReport.Name

     Set Screen.ActiveReport.Printer = Printers(sPrinter)

     nCopies = Nz(Forms!frmPrintDlg2!txtCopies.Value, 1)

     startPg = Nz(Forms!frmPrintDlg2!txtEnd.Value, 1)

     endPg = Nz(Forms!frmPrintDlg2!txtEnd.Value)

     bAll = Forms!frmPrintDlg2!chkAll.Value

     DoCmd.Close acForm, "frmPrintDlg2"

     If bAll Then

         DoCmd.PrintOut acPrintAll, , , acHigh

     Else

         DoCmd.PrintOut acPages, startPg, endPg, acHigh

     End If

End Function

Listing 5: Print-Dialog im Eigenbau