Reporting mit dBASE
Alternativen zum internen Reportgenerator
von Lutz Conrad

NACH Datenerfassung und Bearbeitung im PC möchte man die Daten zu Papier bringen und per Fax oder EMail versenden. Hierzu stellt dBASE eine eigene Reportengine als Two-Way-Tool zur Verfügung. Abgesehen davon, daß man tatsächlich fast jeden Report damit programmieren kann, stehen Aufwand und Ergebnis in keinerlei Verhältnis. Der Designer ist schlicht nur dazu zu verwenden, einen groben Entwurf des Reports herzustellen.

Die wesentlichsten Nachteile aus meiner Sicht sind:

Auf der dBKON 2002 wurde Combit List&Label 8 vorgestellt. Bei aller anfänglichen Skepsis habe ich einige interessante Alternativen zum dBASE-Reportgenerator gesehen. Insbesondere hatte ich Bedenken, ob sich eine datenbankunabhängige Software wirklich so einfach in dBASE einbinden ließe. Den Entschluß, den Umstieg zu versuchen, habe ich zu keiner Zeit bereut. Innerhalb kürzester Zeit wurden die bisherigen Reports meiner Anwendungen auf List&Label angepaßt. Neuentwicklungen erfolgen nur noch mittels List&Label.

Entscheidend für meinen Wechsel waren folgende Aspekte:

Aber ganz ohne Programmieraufwand geht es nicht! Es muß eine Schnittstelle zwischen dBASE und List&Label definiert werden, über die folgende Vermittlung realisiert wird: Hierzu wurde die benutzerdefinierte Klasse LLReport.CRP angelegt. Die Dateierweiterung ist hier nur aus Kompatibilitätsgründen gewählt, die Klasse selbst ist nicht von Report, sondern von Object abgeleitet. Im Objektinspektor wird unter baseClassName der Eintrag LLReport angezeigt.

Die Klasse LLReport

Die Klasse LLReport bildet die Schnittstelle von dBASE zu combit List&Label.

Syntax:
 
 
[<oRef>] = new LLReport()
   

oRef

Eine Variable oder Eigenschaft, in der eine Referenz auf ein neu erstelltes Report-Objekt gespeichert wird.

Die Klasse LLReport verfügt über folgende Eigenschaften und Methoden:

Eigenschaften
 
  Eigenschaft   Standard   Verwendung/Hinweise
  baseClassName   LLReport  
  directory   SET("DIRECTORY")   Verzeichnis, das beim Aufruf des Reports aktiv ist
  LL   Objekt   List&Label Objekt
  LL.Application   Name der EXE   Ist eine Variable _app.oInfo.ListAndLabel vom Typ "C" definiert, wird diese als Standard verwendet
  LL.AutoFaxVar   ""   Name des Feldes bzw. der Variablen mit Faxnummer des Empfängers
  LL.AutoMailVar   ""    Name des Feldes bzw. der Variablen mit Email-Adresse des Empfängers
  LL.CanSelFont   false   Flag, ob Schriftartauswahl für RTF-Objekte in Etiketten
  LL.CanShowPreview  true   Flag, ob Vorschau (wenn gewählt) sofort angezeigt wird
  LL.ChartMode   0   0 Standard, 1 erweitert (Spalten), 2 erweitert (Objekte)
  LL.Chartfields   ARRAY   Chartfelder für erweiterten Modus (derzeit nicht funktional, es wird nur der Standardmodus unterstützt)
  LL.ChooseDest   true   Flag, ob Zielauswahl möglich
  LL.Copies   0   Anzahl der Kopien, 0 mit Auswahl
  LL.CopyCountField   ""   Name des Feldes, mit dem die Anzahl der zu druckenden Etiketten festgelegt wird
  LL.CopyCountValue   1   Standardvorgabe, falls CopyCountField leer oder nicht vorhanden
  LL.DataObjects   NULL   Objectarray mit weiteren Datenzugriffsobjekten (rowsets)
  LL.Debug   false   Debugmodus
  LL.DefDir   ""   Verzeichnis mit List&Label Definitionen (Vorlagen)1
  LL.DeletePreview   true   Temporäre Vorschaudateien nach Ausgabe löschen
  LL.Design   false   Flag für Designermodus
  LL.Destination   3   Ausgabeziel, dBASE-Äquivalent (Vorschau)
  LL.Destinations   ""   Mögliche Ausgabeziele, Leerstring für alle
  LL.EMail   ""   EMail-Empfänger
  LL.EMailBCC   ""   EMail-ZusatzEmpfänger
  LL.EMailBody   "Dies ist der gewünschte Report"   EMail-Text
  LL.EMailCC   false   EMail-Zusatzempfänger
  LL.EmailSubjectl   0   EMail-Betreff
  LL.Error   0   Fehlernummer
  LL.Extension   ".LST"   Unterstützt werden:
  Listenprojekte: ".LST","
  Karteikartenprojekte ".CRD",".BRF","PLN"
  Etikettenprojekte ".LBL",".ETI",".BAR"
  LL.ExportDir   ""   Hauptverzeichnis für Exportdateien2
  LL.ExportFilename   "Export"   Vorgabename für Exportdatei
  LL.Fax   ""   Faxnummer des Empfängers
  LL.FileCanAdd   false   Flag zum Hinzufügen von Dateien
  LL.FileCanSelect   false   Flag zum Auswählen von Dateien
  LL.Filemask   ""   Maske für Dateinamen bei Auswahlmöglichkeit
  LL.Filename   ""   Name der List&Label Vorlage
  LL.Firstpage   0   Startseite, 0 mit Auswahl
  LL.IntCopies   1   Anzahl der Etiketten mit gleichen Daten (nur für Etikettenprojekte)
  LL.LizenzInfo   ""   Lizenzschlüssel (Diese Variable muß vom Anwender gesetzt werden!)
  LL.MemoFieldsAsRTF   true   Memofelder in RTF-Objekten abbilden
  LL.NoDialogs   false   Keine Benutzeraktion, nur Standards oder Vorgaben verwenden
  LL.ObjInput   ARRAY   für Erweiterung vorgesehen, derzeit nicht funktional
  LL.PageCount   0  Wenn beim Start des Reports =-1, erfolgt eine Ermittlung der Seitenanzahl . Diese steht dann in der Variablen C_PageCount zur Verfügung. Nur für Listenprojekte mit Seite X von Y.
  LL.Params   ARRAY   Array mit benutzerdefinierten Konstanten (L&L Variablen)
  LL.Params[1]   ARRAY   {"Lizenznummer",<Lizenznummer>}
  Ist die Variable _app.oLizenz.Lizenz vom Typ "C", wird diese für <Lizenznummer> verwendet, andernfalls der Text "**** Diese Software ist nicht lizenziert ****"
  LL.Params[2]   ARRAY   {"Lizenznehmer",<Lizenznehmer>}
  Ist die Variable _app.oLizenz.Lizenznehmer vom Typ "C", wird diese für <Lizenznehmer> verwendet, andernfalls der Text "Testversion "
  LL.Params[3]   ARRAY   {"Zielfaxnummer",""}
  LL.PDFEncrypt   "0"   #"0" -> PDF-Datei wird verschlüsselt
  LL.PDFEncryptCopy   "0"   #"0" -> Inhalte der PDF-Datei können in die Zwischenablage kopiert werden3
  LL.PDFEncryptEdit   "0"   #"0" -> Inhalte der PDF-Datei können bearbeitet werden3
  LL.PDFEncryptLevel   "0"   Verschlüsselungstiefe ("0" 40 bit, sonst 128 bit)3
  LL.PDFEncryptPrint   "1"   #"0" -> PDF-Datei kann gedruckt werden3
  LL.PDFFontMode   "5"   Schriftarteneinbindung in PDF-Dokumente (s.a. L&L-Doku)
  LL.PreviewDir   ""   Verzeichnis für Vorschaudaten2
  LL.PrintersDir   ""   Verzeichnis mit Druckerdefinitionen2
  LL.Projektart   LL_PROJEKT_LIST   wird automatisch in Abhängigkeit vom Projekt gesetzt
  LL.RealTime   false   Zeitangaben immer aktualisieren
  LL.ResetList   false   Seitenzählung zurücksetzen (nur Listenprojekte)
  LL.ResetPage   true   Seitenzählung zurücksetzen (nur Karteikartenprojekte)
  LL.ResetUser   false   Variable (Konstanten) nach jedem Datensatz neu lesen
  LL.SerienEmail   false   für Erweiterung vorgesehen, derzeit nicht funktional
  LL.SerienFax   false   für Erweiterung vorgesehen, derzeit nicht funktional
  LL.SetOffset   false   Dialog zur Feinpositionierung anzeigen
  LL.ShowEmailDialog   true   Dialog zum EMail-Versand anzeigen
  LL.SortVariables   false   Variable im Designer alphabetisch sortieren
  LL.Special   ""   Zusatzverzeichnis mit benutzerdefinierten Anpassungen einzelner Reports
  LL.SuppPageBreak   true   Seitenumbruch in Text-Objekten unterstützen
  LL.Tablenames   false   Feldnamen mit Tabellennamen anzeigen
  LL.UserBreak   false   Flag, ob durch Benutzer abgebrochen
  LL.Version     interne LL-Versionsnummer
  LL.VarsAndFields   false   Flag, ob bei Listenprojekten neben Feldern auch Variablen im Funktionsdialog des Designers angezeigt werden sollen
  LL.Zoomfaktor   100   Zoomfaktor beim Öffnen der Vorschau
  output   3   Ausgabeziel
  ParentWindow   0  Fensterhandle des aufrufenden Fensters, falls nicht verfügbar, wird _app.frameWin.hWnd verwendet
  scaleFontBold   false   Standardauszeichnung für RTF-Text
  scaleFontName   "Arial"   Standardfont für RTF-Text
  scaleFontSize   10   Standardschriftgrad für RTF-Text
  streamsource1   OBJECT   Hauptdatensatzbereich (Query oder Array4)
  Tools

  OBJECT

  Interne Funktionsbibliothek

Methoden
 
     
  Init() Definition der Standards, diese Methode muß aus dem Report mittels SUPER::Init() nach der Definition der Datensatzbereiche (des Hauptdatensatzbereichs) aber vor allen weiteren Zuweisungen aufgerufen werden.
Rückgabewert: keiner
  render() Startet den Report
Rückgabewert: bei Fehler oder Abbruch false, sonst true
     

Alle weiteren Methoden dienen der internen Verarbeitung und sollten daher durch den Programmierer nicht direkt aufgerufen werden.

Anmerkungen

  1. Wird der Pfad zur Hauptdatenbank der Anwendung in _app.DatabasePath gespeichert, so werden die Reportvorlagen standardmäßig im Verzeichnis <Datenbankverzeichnis>\LLDef gesucht.

    Ist mit LL.Special ein Unterverzeichnis mit speziellen Anpassungen referenziert, so wird zunächst in diesem Verzeichnis gesucht, ist der Report dort nicht vorhanden, wird die Vorlage aus dem o.g. Verzeichnis verwendet.

    Ist _app.DatabasePath nicht definiert, so wird der Report im Anwendungsverzeichnis gesucht.

  2. Das Basisverzeichnis wird durch eine Variable _app.oUser.UserDir vom Typ C oder durch das Verzeichnis der EXE definiert. Ausgehend von diesem Verzeichnis wird, falls nicht explizit anders angegeben, folgende Struktur angelegt:
     
         
      <Benutzerverzeichnis>\<Anwendung>\Printers benutzerspezifische Druckerdefinitionen
      <Benutzerverzeichnis>\<Anwendung>\Previews Vorschauverzeichnis
      <Benutzerverzeichnis>\<Anwendung>\Exports  Exportierte Listen, je Format ein Unterverzeichnis
         

  3. Nur wirksam, wenn LL.PDFEncrypt # "0"

  4. Wird als Datenquelle ein Array verwendet, ist  folgender Code in die Init() Methode der Reportklasse aufzunehmen:
     
     
     this.Streamsource1.rowset = new OBJECT()
     this.Streamsource1.rowset.handle=-1
     this.Streamsource1.rowset.fields=new ARRAY()
     this.Streamsource1.rowset.fields.add({<Spaltenname 1>, …, <Spaltenname n>})
     this.Streamsource1.rowset.fields.add({<Typ 1>, …, <Typ n>})
     this.Streamsource1.rowset.fields.add({<Länge 1>, …, <Länge n>})
     this.Streamsource1.rowset.fields.add({<Dezimalstellen 1>, …, <Dezimalstellen n>})
     this.Streamsource1.rowset.fields.add({<Inhalt Zeile 1 Spalte1, …, <Inhalt Zeile1, Spalte n})
     .....
     this.Streamsource1.rowset.fields.add({<Inhalt Zeile m Spalte1, …, <Inhalt Zeile m, Spalte n})
       

    Die Schnittstelle ruft bei jeder Navigation im Array eine Methode SelectData() auf. Diese kann bei Bedarf überschrieben werden, um so eine Navigation in verbundenen Datensatzbereichen zu erzwingen.

Datentypzuordnungen

Die Vermittlung der in Tabellen enthaltenen Datenfelder erfolgt ohne weiteren Programmieraufwand. Dabei realisiert die Schnittstelle folgende automatische Datentypzuordnungen:
 
  dBASE   List&Label
  Character   Character
  Logical   Logical
  Memo   RTF[|<nSize>][|cFontname]
  Als Standardschrift wird die mittels scaleFontName, scaleFontSize und scaleFontBold zugewiesene Schriftart verwendet. HTML-Tags werden bei der Konvertierung berücksichtigt.
  Numeric,AutoInc,Float, Long, Double   Numeric
  Date   Date
  Timestamp   Character (es wird DTTOC() verwendet)
  OLE, Binary

  keine Vermittlung

Bei der Konvertierung von HTML.formatiertem Text in das RTF-Format besteht zusätzlich die Möglichkeit, mit dem Tag <pagebreak> bzw <umbruch> einen sofortigen Seitenumbruch zu erzwingen.

Kalkulationsfelder können wie gewohnt bei der Definition der Datensatzbereiche angelegt werden. Zusätzlich besteht jedoch die Möglichkeit, diese in einem Array an den Hauptdatensatzbereich anzufügen. In letzterem Falle muß im Init() folgende Syntax verwendet werden:
 
 
this.streamsource1.rowset.calcFields=new ARRAY()
this.streamsource1.rowset.calcfields.add({<Wert>,<Name>,<Typ>,<Länge>,<Dezimalstellen>})
   

Konstanten müssen an List&Label über das Array LL.Params vermittelt werden. Dieses Array enthält je zu übermittelnden Wert ein Sub-Array mit folgendem Aufbau:
 
 
{<Name>,<Wert>[,<Typ>[,<Länge>[,Dezimalstellen]]]}
   

Als Typ sind die o.g. dBASE-Spezifikationen zu verwenden. Zusätzlich können die Typen GRAPHIC und RTF[|nSize][|cName] verwendet werden. Beide Formate erwarten als Wert eine Zeichenkette. Beim Format GRAPHIC ist dies ein Verweis auf eine Bilddatei. Ist der Typ nicht angegeben, wird automatisch CHARACTER vorausgesetzt, Länge und Dezimalstellen werden bei Bedarf anhand des Datentyps zugeordnet.

Verwendung

Der Aufruf aus einem Programm erfolgt stets über:
 
 
LOCAL r
SET PROCEDURE TO <Reportdatei> ADDITIVE
r = new <Reportklasse>
r.Init(([<Parameter 1>[, …Parameter n]])
r.render()
   

Es wird empfohlen, für jede Anwendung eine zwischengeschaltete Report-Basisklasse zu verwenden und die einzelnen Reports nicht direkt von LLReport sondern von dieser Klasse abzuleiten.

Beispiel:
 
 
CLASS AppReport OF LLReport FROM LLReport.CRO CUSTOM

   FUNCTION Init(lDirekt,lDesign)
      SUPER::Init()
      IF NOT EMPTY(lDirekt)
         this.output = 1  //Direktdruck ohne Vorschau
      ENDIF
      IF NOT EMPTY(lDesign)
         this.LL.Design = true  //Report im Designmodus aufrufen
      ENDIF
      this.LL.Lizenzinfo = <Persönlicher Lizenzschlüssel, den Sie von combit erhalten>
      RETURN

ENDCLASS

   

Die Reportklasse selbst hat dann folgenden grundsätzlichen Aufbau
 
  CLASS SampleReport OF AppReport FROM AppReport.CRP
   [<Definiere Datenzugriffsobjekte>]

   Function Init([<Parameter 1>[, …Parameter n]],lDirekt,lDesign)
      [<Definiere Besonderheiten der Datenzugriffsobjekte in Abhängigkeit von Parametern (Index, Filter...)>]
      SUPER::Init(lDirekt,lDesign)
      this.LL.Filename = <Reportname>

      // Weitere Konstanten definieren, falls erforderlich
      this.LL.Params.add({<Name 1>,<Wert>[,<Typ>[,<Länge>[,Dezimalstellen]]]})
      ...
     this.LL.Params.add({<Name n>,<Wert>[,<Typ>[,<Länge>[,Dezimalstellen]]]})

      // Weitere Datensatzbereiche anmelden, falls erforderlich
      this.LL.DataObjects.add(<Weiterer Datensatzbereich 1>)
      ...
      this.LL.DataObjects.add(<Weiterer Datensatzbereich n>)

     // Kalkulationsfelder definieren, falls erforderlich
      this.streamsource1.rowset.CalcFields=new ARRAY()
      this.streamsource1.rowset.CalcFields.add({<Wert>,<Name 1>,<Typ>,<Länge>,<Dezimalstellen>}
      ...
      this.streamsource1.rowset.CalcFields.add({<Wert>,<Name n>,<Typ>,<Länge>,<Dezimalstellen>}
      // Setzen weiterer LL-Properties
      retuen

   Function Sourcefile_onNavigate
      /*
      Wird nur benötigt, wenn mit CalcFields-Array gearbeitet wird. In diesem Falle werden hier die Werte des Arrays gesetzt. Dies ist z.B. dann von Vorteil, wenn Berechnungen in externen Modulen durchgeführt werden sollen, um das Ergebnis in den Report zu übernehmen, z.B..
   this.Calcfields[1][1]=_app.Tools.GetTagesart((this.fields["Datum"].value))
     */
      return

ENDCLASS

   

Tip: Obwohl die in der Datei LLReport.CRO enthaltene Klasse nicht von der dBASE Stock-Class Report abgeleitet ist, wird im Dateinamen die Erweiterung CRO verwendet. Damit wird eine entsprechende Zuordnung im Projektexplorer erreicht. Es wird empfohlen, diese Verfahrensweise beizubehalten, also benutzerdefinierte Reportdateien mit der Endung *.CRP und die in der untersten Hierarchieebene befindlichen Reports mit der Endung *.REP zu versehen.

Verwendung der Beispiele

Zur Verwendung der Beispiele wird ein BDE-Alias namens LLSample benötigt. Dieser wird beim ersten Aufruf des Beispiels automatisch angelegt und verweist auf das Verzeichnis DATA der Beispielstruktur.

Zwischen die eigentlichen Reports und die Basisklasse LLReport wird ein benutzerdefinierter Report geschoben, in dem allgemeingültige Vorgaben für alle Reports des Projekts definiert werden. Hier erfolgt die Zuweisung des combit-Lizenzschlüssels. Weitere Festlegungen dienen zur Gewährleistung eines einheitlichen Erscheinungsbildes aller Reports des Projekts. U.a. werden definiert:

Die Beispielanwendung selbst besteht aus dem Formular Beispiele.WFM und den von dort aufgerufenen Reports. Alle Dateien (außer LLReport) liegen im Quelltext vor und können als Muster für eigene Entwicklungen verwendet werden.

Für eine Trial-Version können Sie einen beliebigen Lizenzschlüssel verwenden.

Im Auswahldialog finden Sie den Aufruf für folgende Programmbeispiele:

Für alle Reports wurden sämtliche verfügbaren Exportmodule bereitgestellt. Beim Anklicken der Schaltfläche Report mit der linken Maustaste wird ein Auswahldialog für das Ausgabeziel geöffnet. Beim Klick auf die Schaltfläche Report mit der rechten Maustaste wird der Report sofort zum Drucker gesandt.

Wird mit der rechten Maustaste bei gleichzeitig gedrückter STRG-Taste geklickt, öffnet sich der List & Label-Designer (vorausgesetzt List&Label ist in Vollversion installiert oder auf dem Rechner befindet sich die List&Label-Professional-Runtime).

In diesem Dialog legen Sie das Ausgabeziel für den Report fest.


Artikelliste (Beispiel von combit)

Bei Etikettenprojekten haben Sie neben der Auswahl des Ausgabeziels außerdem die Möglichkeit festzulegen, bei welcher Position mit dem Etikettendruck begonnen wird. Somit können auch bereits verwendete Druckvorlagen vollständig genutzt werden.


Etiketten (Beispiel von combit)

Adreßliste

Nach Auswahl der gewünschten Option wird die komplette Liste gedruckt. Im Report wird hier nur eine Tabelle genutzt.

Geburtstagsliste mit Beschränkung auf einen Zeitraum.

Karteikarte

Für die im Tabellenobjekt ausgewählten Datensätze werden Karteikarten gedruckt, eine Mehrfachauswahl ist möglich. Das Umschalten der Sortierfolge im Tabellenobjekt erfolgt durch Doppelklick auf den Spaltenkopf. Rote Farbe weist auf die aktuelle Sortierung hin, grüne Farbe auf eine mögliche Sortierung.

Als Projektart wird ein Karteikartenprojekt verwendet. Die Datenbindung erfolgt auch in diesem Beispiel über eine einzelne Tabelle.

Karteikarten für die ausgewählten Personen

Brief

Nach Eingabe eines Brieftextes wird ein Serienbrief für alle Adressen erstellt. Die Faxnummer wird direkt übergeben, so daß auch ein Serienfax möglich ist (Tobit-Faxware-Client).


Serienbrief

Rechnung

Im oberen Tabellenobjekt sind die Rechnungen aufgeführt, im unteren die dazugehörigen Rechnungspositionen, die entsprechend Navigation aktualisiert werden. Zum Ausdruck der Rechnungen können auch hier mehrere Datensätze markiert werden.

Im Report werden hier mehrere Datensatzbereiche verwendet. Die Haupttabelle enthält die Rechnungsangaben, die Rechnungspositionen kommen aus einer Detailtabelle. Adreßangaben werden bei der Navigation in der Haupttabelle verknüpft.

Gescanntes Formular

In diesem Beispiel wird davon ausgegangen, dass der Ausdruck im Zusammenhang mit einem eingescannten Formular ausgeführt wird.
Wenn direkt in ein vorhandenes (vorgedrucktes) Formular gedruckt werden soll, kann abhängig von den Druckerparametern eine Feinjustierung des Reports erforderlich werden. Verwenden Sie hierzu die entsprechende Option, die eine Positionierung auf 0,01 mm ermöglicht.

Diagramme

Das Beispiel Adressen nach Orten vermittelt anhand eines Tortendiagramms die Verteilung der Kunden auf Wohnorte.

Mit dem Beispiel Umsatz nach Region werden die Umsätze hinsichtlich Region und Rechnungsmonat in einem 3-D Balkendiagramm dargestellt.

Am Beispiel Umsatz nach Monat wird in einem Liniendiagramm der Verlauf der Umsatzentwicklung aufgezeigt. Für jede Region wird hierfür eine andersfarbige Linie verwendet.

Um die beigefügten Beispiele herunterzuladen,
bitte hier klicken (283 Kb als Zip-Datei).