Berichte ohne Datenherkunft

Lies diesen Artikel und viele weitere mit einem kostenlosen, einwöchigen Testzugang.

Haben Sie schon einmal einen Access-Bericht missbraucht Um beispielsweise ein Kalenderblatt zu erstellen oder ein Sudoku zu drucken Dann haben Sie sicher auf eine entsprechende Datenherkunft zurückgegriffen, denn ohne diese lassen Berichte sich nur bedingt mit Informationen füllen. Dieser Beitrag zeigt, wie Sie auch ohne Daten eine Menge mit Berichten anstellen. Dabei sollen zunächst der Inhalt einer Textdatei und später der eines Moduls in Berichtsform abgebildet werden – Letzteres mit entsprechender Färbung von Kommentarzeilen und Schlüsselwörtern!

Normalerweise läuft die Berichtserstellung etwa so ab: Sie überlegen sich den Aufbau des Berichts, stellen dann die benötigten Tabellen in einer Abfrage zusammen (oder auch in mehreren, wenn Unterberichte zum Einsatz kommen) und positionieren dann die an die Datenherkunft gebundenen Steuerelemente im Bericht.

Was aber, wenn die auszugebenden Daten gar nicht in Tabellenform vorliegen Vielleicht möchten Sie ja einfach mal eine Textdatei in Berichtsform abbilden oder auch ein Codemodul (wobei dies den Charme hätte, dass man bestimmte Code-Elemente farbig darstellen könnte – der VBA-Editor bietet diese Möglichkeit nicht).

Zunächst kümmern wir uns um die Anzeige einer einfachen Textdatei in einem Bericht. In diesem Fall haben wir einfach den Inhalt einer Prozedur in eine Textdatei geschrieben. Diese soll nun wie in Bild 1 in einem Bericht dargestellt werden.

pic001.png

Bild 1: Dieser Bericht druckt den Inhalt einer Textdatei.

Zur einfachen Auswahl der Textdatei finden Sie in der Beispielanwendung ein kleines Formular mit einem Textfeld zur Anzeige des Dateinamens und einer Schaltfläche zum Öffnen eines Dateiauswahl-Dialogs (s. Bild 2). Die Schaltfläche mit der Beschriftung Datei in Bericht anzeigen erledigt nichts weiter als das Öffnen des Berichts rptTextdatei in der Vorschauansicht. Dabei übergibt die durch die Schaltfläche ausgelöste Prozedur den Namen der anzuzeigenden Textdatei als Öffnungsparameter an den Bericht (da dies erst ab Access 2002 möglich ist, prüft der Bericht beim Öffnen, ob OpenArgs einen Wert enthält, und fragt gegebenenfalls direkt den Inhalt des Textfelds mit dem Dateinamen ab).

pic002.png

Bild 2: Die Auswahl der auszugebenden Textdatei erfolgt komfortabel per Formular.

Bericht aus Datei füllen

Damit bewegen wir uns nun aus der Komfortzone der bekannten Techniken heraus und betrachten die Vorgehensweise, einen Bericht zeilenweise mit dem Inhalt einer Textdatei zu füllen.

Die Daten sollen in einem Bericht namens rptTextdatei erscheinen. Diesen legen Sie neu an und stellen die Größe seiner Bereiche etwa wie in Bild 3 ein.

pic009.png

Bild 3: Entwurf des Berichts zur Ausgabe von Textdateien

Danach benötigen Sie zwei Variablen, die Sie gleich oben im Klassenmodul des Berichts unterbringen. Das Klassenmodul fügen Sie am einfachsten zum Bericht hinzu, indem Sie die Berichtseigenschaft Enthält Modul auf Ja einstellen. Die Variablen speichern den Namen der Textdatei sowie die aktuell bearbeitete Zeile der Textdatei:

Dim strTextdatei As String
Dim strTextzeile As String

Im Prinzip durchlaufen wir dann die Textdatei auf die gewohnte Weise, also etwa mit folgenden Anweisungen:

Open strTextdatei For Input As #1
Do While Not EOF(1)
    Line Input #1, strTextzeile
    ''was mit dem Text machen
Loop
Close #1

Allein die Aufteilung und die Realisierung der Schleife sieht anders aus. Beginnen wir mit dem Öffnen des Berichts und somit auch der Textdatei. Beim Öffnen des Berichts soll das Ereignis Beim Öffnen ausgelöst werden, weshalb Sie dem gleichnamigen Ereignis den Wert [Ereignisprozedur] zuweisen und nach einem Klick auf die Schaltfläche rechts von der Eigenschaft den automatisch angelegten Prozedurrumpf wie folgt ergänzen:

Private Sub Report_Open(Cancel As Integer)
    If Not IsNull(Me.OpenArgs) Then
        strTextdatei = Me.OpenArgs
    Else
        If IstFormularGeoeffnet("frmDateiDrucken") Then
            strTextdatei = Forms!frmDateidrucken!txtDatei
        Else
            MsgBox "Keine Datei!"
            Cancel = True
        End If
    End If
    Open strTextdatei For Input As #1
End Sub

Diese Prozedur prüft, ob das Öffnungsargument einen Dateinamen enthält. Falls nein, wird dieser direkt aus dem Textfeld txtDatei des aufrufenden Formulars ermittelt. Ist dieses gar nicht geöffnet, bricht die Anzeige des Berichts an dieser Stelle mit einer kurzen Meldung ab. Ist jedoch eine Textdatei angegeben, wird ihr Name in die Variable strTextdatei eingetragen. Die letzte Zeile öffnet die Datei schließlich für den lesenden Zugriff. Nun prüft der Bericht, ob er irgendwelche Daten in den Detailbereich schreiben soll. Auch wenn die Datenherkunft des Berichts wie in diesem Fall leer ist, löst er dabei trotzdem zumindest einmalig die beiden Ereignisse Beim Formatieren und Beim Drucken des Detailbereichs aus. Wenn Sie keine außergewöhnlichen Anweisungen in den entsprechenden Ereignisprozeduren hinterlegen, geschieht nichts und der Bericht wird ohne Daten im Detailbereich angezeigt.

Das gilt es zu verhindern: Wir wollen dafür sorgen, dass der Bericht im Detailbereich die Zeilen unserer Textdatei anzeigt. Dazu gibt es nun zwei Möglichkeiten:

  • Wir zeigen alle Zeilen in einem entsprechend großen Detailbereich an.
  • Der Detailbereich soll mehrmals gefüllt werden, und zwar mit jeweils einer Zeile der Textdatei.

Die erste Variante wäre einfach, denn dies können wir durch einen schlichten Aufruf der Print-Methode des Berichts erledigen. Wir müssen einfach nur den Detailbereich so groß wie möglich machen, damit dieser (mit ein wenig Glück) die komplette Textdatei fasst.

Aber was geschieht, wenn die Datei nicht auf eine Berichtsseite passt Der Rest wird schlicht abgeschnitten. Dem können wir nur entgegenwirken, indem wir berechnen, wie viele Zeilen in einen Detailbereich passen, und den Inhalt der Textdatei darauf aufteilen. Das scheint kompliziert zu sein, also schauen wir uns zunächst die zweite Variante an.

Wenn wir jede Zeile in einen Detailbereich schreiben, gibt es kaum Probleme – zumindest, wenn sich der Bericht beim Hinzufügen von Inhalten mit der Print-Methode genau so verhält wie sonst: Er sollte einen Detailbereich, der nicht mehr auf die aktuelle Seite passt, einfach auf die folgende Seite verschieben.

Bleibt das Problem, dass wir nicht wissen, wie wir den Bericht dazu bewegen, auch ohne Datenquelle mehr als einen Detailbereich anzuzeigen.

Hier kommen zwei Eigenschaften des Berichts zum Tragen. Diese können Sie jeweils im Beim Formatieren-Ereignis der einzelnen Berichtsereignisse unterbringen:

  • MoveLayout: Diese Eigenschaft legt fest, ob der Bericht zum nächsten zu druckenden Bereich wechselt. Das bedeutet in der Regel, dass er nach der Ausgabe eines Bereichs eine Zeile weiter unten am linken Rand mit dem folgenden Bereich fortfährt.
  • NextRecord: Diese Eigenschaft legt fest, ob der nächste Datensatz der Datenherkunft zum Füllen des aktuellen Bereichs herangezogen werden soll – abhängig davon, ob es überhaupt noch einen weiteren Datensatz gibt.

Diese beiden Eigenschaften machen wir uns zunutze: Mit Me.MoveLayout = True sorgen wir dafür, dass die folgende Zeile der Textdatei links unter der aktuellen Zeile ausgegeben wird. Dazu muss es aber erst einmal kommen: Der Bericht druckt nämlich den Detailbereich nur einmal, wenn die Datenherkunft keine weiteren Daten enthält. Und in unserem Fall enthält diese ja gar keine Daten – nach dem ersten Durchlauf wäre also eigentlich Schluss.

Dieser hier unerwünschten Arbeitsverweigerung des Berichts können wir durch Setzen der Eigenschaft Me.NextRecord auf False vorbeugen: Dann versucht der Bericht nämlich nicht, zum nicht vorhandenen zweiten Datensatz zu springen, sondern druckt den aktuellen Bereich nochmals.

Im Detail profitieren wir davon in den beiden Ereignisprozeduren Beim Formatieren und Beim Drucken. In Beim Formatieren lesen wir jeweils eine Zeile der Textdatei ein. Dort stellen wir auch die Höhe des Bereichs auf einen passenden Wert ein. Schließlich erhält die Eigenschaft MoveLayout den Wert True, wenn noch die letzte Zeile eingelesen wurde, und False, wenn dies die letzte Zeile war. Sprich: Enthält die Textdatei noch ungedruckte Zeilen, mache noch einen Detailbereich auf und fahre fort.

Die Eigenschaft NextRecord bestücken wir mit dem umgekehrten Wert der Eigenschaft MoveLayout: Wenn noch Zeilen vorliegen, soll der Bericht nicht zum nächsten Datensatz springen. Dort würde er ja bemerken, dass eigentlich gar keine Daten vorliegen, und die Ausgabe gegebenenfalls mit dem übrigen Bereich wie etwa dem Seitenfuß beenden:

Private Sub Detailbereich_Format(Cancel As _
    Integer, FormatCount As Integer)
    Line Input #1, strTextzeile
    Me.Detailbereich.Height = 200
    Me.MoveLayout = Not EOF(1)
    Me.NextRecord = Not Me.MoveLayout
End Sub

Im Ereignis Beim Drucken erfolgt schließlich die eigentliche Ausgabe der aktuellen Zeile der Textdatei. Vorher stellen wir jedoch noch einige Texteigenschaften ein und geben dann mit der Print-Methode des Berichts den Inhalt der Textzeile in den Bericht aus:

Private Sub Detailbereich_Print(Cancel As Integer, PrintCount As Integer)
    Me.FontSize = 8
    Me.FontBold = False
    Me.FontName = "Courier New"
    Me.Print strTextzeile
End Sub

Diese beiden Prozeduren werden entsprechend der Zeilenzahl der Textdatei wiederholt. Probieren Sie dies später ruhig auch mit einer Textdatei aus, die mehr als eine Berichtsseite lang ist – der Bericht wird die Zeilen, die nicht auf die erste Seite passen, auf den folgenden Seiten ausgeben. Schließlich soll der Bericht im Seitenkopfbereich den Namen der Datei anzeigen. Das ist leicht: Die Ereignisprozedur Seitenkopfbereich_Print braucht dazu nur die Schrifteigenschaften nach Wunsch einzustellen und den Dateinamen mit der Print-Methode auszugeben:

Private Sub Seitenkopfbereich_Print(Cancel As Integer, PrintCount As Integer)
    Me.FontSize = 10
    Me.FontBold = True
    Me.Print strTextdatei
End Sub

Schließlich soll beim Schließen des Berichts noch die Textdatei geschlossen werden:

Private Sub Report_Close()
    Close #1
End Sub

Offene Baustellen

Das hat doch schon was, oder Textdateien einfach mal so in einem Access-Bericht anzeigen Immerhin lassen sich diese nach Gusto mit zusätzlichen Informationen ausstatten, zum Beispiel mit einer Zeilenzahl, mit Seitenzahlen et cetera. Alles Dinge, die der geneigte Access-Entwickler schneller programmiert, als er die entsprechenden Konfigurationsmöglichkeiten im Texteditor seiner Wahl gefunden hat.

Leider hat die Lösung noch eine kleine Schwachstelle: Wenn nämlich eine Zeile länger als die Berichtsbreite ist, wird sie einfach abgeschnitten. Die Länge der Zeile können wir in Abhängigkeit von Schriftgröße et cetera mit der Funktion TextWidth des Berichts ermitteln. Aber wie genau nutzen wir das Wissen um die Breite der Textzeile Wir könnten ungefähr festlegen, auf wie viele Zeilen wir den Text aufteilen müssen, damit er nicht abgeschnitten wird.

Aber wie schneiden wir die Textzeilen auseinander Am einfachsten wird es wohl sein, zunächst nur überlange Textzeilen mit einer kleinen Spezialbehandlung zu versehen. Dabei müssen wir zunächst prüfen, wann eine Zeile nicht mehr in die Spaltenbreite des Berichts passt. In diesem Falle muss der Text umbrochen, die Höhe des Detailbereichs angepasst und erst dann der Text in den Bericht gedruckt werden.

Textzeilen umbrechen

Die Version des Berichts mit der Funktion zum Umbrechen überlanger Textzeilen finden Sie in der Beispieldatenbank unter dem Namen rptTextdateiMitZeilenumbruch.

Den Hauptteil der änderungen finden Sie in der Ereignisprozedur Beim Formatieren (s. Listing 1). Hier werden wie gehabt zunächst der Inhalt der aktuellen Textzeile ermittelt und die Schrifteigenschaften festgelegt. Dann kommt frischer Code: Die TextWidth-Funktion des Berichtsobjekts ermittelt die Breite des aktuell in der Variablen strTextzeile enthaltenen Textes. Ist diese breiter als die Berichtsspalte, unterzieht die Prozedur den Text einer Sonderbehandlung in der Funktion NarrowText. Diese haben wir bereits im Beitrag Zeichenketten zerlegen (www.access-im-unternehmen.de/560) vorgestellt. Die Funktion erwartet einen Text und die maximale Zeichenanzahl pro Zeile. Die Prozedur liefert einen an den Leerzeichen umbrochenen Text zurück. Der Aufruf

Listing 1: Formatieren des Detailbereichs und optimieren des Textes im Hinblick auf die Spaltenbreite

Private Sub Detailbereich_Format(Cancel As Integer, FormatCount As Integer)
    Dim intFontWeight As Integer
    Dim intZeilen As Integer
    Dim lngX As Long
    Dim lngY As Long
    Line Input #1, strTextzeile
    Me.FontSize = 8
    Me.FontBold = False
    Me.FontName = "Courier New"
    If Me.TextWidth(strTextzeile) > Me.Width Then
        strTextzeile = NarrowText(strTextzeile, 80, intZeilen)
    End If
    Me.Detailbereich.Height = Me.TextHeight(strTextzeile)
    Me.MoveLayout = Not EOF(1)
    Me.NextRecord = Not Me.MoveLayout
End Sub
Debug.Print NarrowText("bla bla bla bla bla", 12)

wird bei Angabe einer maximalen Zeichenanzahl von 12 etwa folgenden Text liefern:

bla bla bla
bla bla

Nach dem Umbrechen des Textes mit einer Breite von maximal 80 Zeichen ermittelt die Funktion TextHeight die Höhe des Textes, die gleich im Anschluss der Eigenschaft Height des Detailbereichs zugewiesen wird. Nun folgen die übrigen Schritte, also das Prüfen auf die letzte Zeile der Textdatei sowie das Drucken der aktuellen Zeile in den Bericht.

Begrenzte Print-Kapazität

Wenn Sie ein wenig mit dem Bericht experimentieren und dabei zufällig eine recht lange Zeile abdrucken möchten, stoßen Sie auf einen Fehler: Die Print-Anweisung kann nur eine begrenzte Anzahl Zeichen im Bericht ausgeben. Dummerweise ist diese nicht fix vorgegeben, sondern hängt anscheinend von weiteren Faktoren wie der Schriftgröße ab. Wir fangen dies ab, indem wir die Beim Drucken-Ereignisprozedur des Berichts wie in Listing 2 erweitern.

Listing 2: Wenn der Text zu lang für die Print-Methode ist, zeigt diese Prozedur eine Fehlermeldung an.

Private Sub Detailbereich_Print(Cancel As Integer, PrintCount As Integer)
    On Error Resume Next
    Me.Print strTextzeile
    If Not Err.Number = 0 Then
        MsgBox "Die folgende Zeile kann nicht gedruckt werden, da sie zu lang ist:" _
            & vbCrLf & vbCrLf & "''" & strTextzeile & "''"
    End If
End Sub

Die dort im Fehlerfall ausgelöste MsgBox-Anweisung liefert eine Meldung wie in Bild 4.

pic003.png

Bild 4: Hinweismeldung beim Versuch, einen überlangen Text abzudrucken

Sollten Sie Dokumente mit überlangen Zeilen wie in diesem Beitrag per Bericht ausgeben wollen, können Sie diese Zeilen theoretisch auf mehrere Detailbereiche aufteilen. Aus Platzgründen gehen wir auf die dazu notwendigen Anpassungen an dieser Stelle nicht ein.

Wechsel von der Vorschau zur Druckansicht

In der jetzigen Form können Sie den Bericht nicht von der Vorschauansicht aus drucken. Der Bericht wird zum Drucken erneut gerendert, was ein erneutes Auslösen der Ereignisprozeduren zur Folge hat. Der Ablauf beim Öffnen, Einlesen und Schließen der Datei müsste speziell an das aufeinanderfolgende Rendern der Vorschau- und der Druckvariante angepasst werden.

Code-Dokumentation per Bericht

Natürlich können Sie nicht nur Texte aus Dokumenten, sondern beliebige Texte in Berichten ausgeben – Hauptsache, diese liegen in Zeilenform vor. Als Nächstes nehmen wir uns die Module beziehungsweise den Code einer Anwendung vor. Wir wollen zunächst grundsätzlich die Module und den enthaltenen Code strukturiert ausgeben und dann Elemente wie Schlüsselwörter oder Kommentare einfärben.

Die Lösung soll eines oder alle Module ausgeben. Um einzelne Module für die Ausgabe auszuwählen, sollen diese in einem Formular per Kombinationsfeld auswählbar sein. Für den Zugriff auf die Module und später auf deren Inhalte benötigen Sie einen Verweis auf die Bibliothek Microsoft Visual Basic for Applications Extensibility 5.3 (s. Bild 5).

pic004.png

Bild 5: Verweis auf die Bibliothek für den Zugriff auf das VBA-Projekt und die enthaltenen Module

Schauen wir uns den ersten Anlauf für das Modul des Berichts selbst an. Praktischerweise zeigt der Screenshot mit dem Ergebnis aus Bild 6 gleich den Code, der diesen hervorgerufen hat.

pic005.png

Bild 6: Ausgabe eines kompletten Code-Moduls per Bericht

Es gibt kaum Unterschiede zu der Variante des Berichts, der den Inhalt einer Textdatei anzeigte. Hier benötigen wir je eine Objektvariable für die aktive VB-Komponente und das enthaltene CodeModule-Objekt sowie eine Zählervariable namens intLine, welche die aktuell bearbeitete Zeilennummer speichert. Diese wird im Beim Formatieren-Ereignis des Detailbereichs jeweils um eins erhöht. Die folgenden Zeilen brechen wieder zu lange Textzeilen um und passen die Höhe des Detailbereichs an. Schließlich erhalten die Eigenschaften MoveLayout und NextRecordset des Berichtsobjekts wieder die Werte, die dazu führen, dass solange Detailbereiche mit Codezeilen gefüllt werden, bis das Ende des Moduls erreicht wurde. Für die Ausgabe ist wiederum die Print-Methode in der Ereignisprozedur Beim Drucken verantwortlich.

Ausbaufähig

Mit dieser Basisversion können wir uns an den Ausbau machen. Aktuell zeigt der Bericht nur den Inhalt des Moduls an. Damit wir uns mit jemandem über den Code unterhalten können, wären Zeilennummern hilfreich. Für den Beginn geben wir uns mit durchlaufenden Zeilennummern zufrieden, die Vielfache von Zehn sind und im Format 0000 angezeigt werden – also etwa so wie in Bild 7. Dazu erweitern Sie die Prozedur Detailbereich_Print der vorherigen Version um einige Zeilen (s. Listing 3).

Listing 3: Erweiterung des Beim Drucken-Ereignisses zur Ausgabe von Zeilennummern

Private Sub Detailbereich_Print(Cancel As Integer, PrintCount As Integer)
    If bolZeilennummern Then
        Me.Print Format(intLine * 10, "0000")
        Me.CurrentX = 600
    Else
        Me.CurrentX = 0
    End If
    Me.CurrentY = 0
    Me.Print strTextzeile
End Sub

pic006.png

Bild 7: Modulcode mit Zeilennummern

Hier kommen erstmals die beiden Eigenschaften CurrentX und CurrentY ins Spiel. Diese legen fest, in welcher Position die nächste Ausgabe etwa mit der Print-Methode landet. Die Prozedur prüft den Wert der Variablen bolZeilennummern, der an geeigneter Stelle eingestellt werden muss (in diesem Fall schlicht im Ereignis Beim Öffnen des Formulars).

Hat dieser den Wert True, sorgt eine erste Print-Anweisung für die Ausgabe der Zeilenzahl, die auf dem Wert der Zählervariablen intLine basiert. Die CurrentX-Eigenschaft verschiebt die X-Position für den folgenden Aufruf von Print entsprechend nach rechts. Weil nach dem ersten Print-Befehl der in CurrentY gespeicherte Wert entsprechend der Zeilenhöhe des ausgegebenen Textes eingestellt wird, würde der eigentliche, mit der folgenden Print-Anweisung ausgegebene Text um eine Zeile nach unten verschoben. Damit dies nicht geschieht, stellen wir CurrentY wieder auf 0. Erst danach wird der eigentliche Code der Zeile ausgegeben.

Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...

Testzugang

eine Woche kostenlosen Zugriff auf diesen und mehr als 1.000 weitere Artikel

diesen und alle anderen Artikel mit dem Jahresabo

Schreibe einen Kommentar