[ 1 ] [ 2 ]

Ein klassischer VFP-Bericht automatisiert die Ausgabe von WinWord

Lisa Slater Nicholls

In „WinWord für VFP-Berichte automatisieren“ habe ich Ihnen ein einfaches Beispiel vergestellt, wie Sie mit OLE Automation einen Bericht in WinWord erstellen können. Dieses Beispiel generiert Resultate, die auf einem einzigen VFP-Datensatz basieren. Das Schreiben von WordBasic-Code, der Resultate mehrerer Datensätze auflistet, erfordert schon einige Genialität, vor allem, wenn die Anzahl der Datensätze im Resultat variiert. Die Behandlung von Code, der unter bestimmten Bedingungen die Datensätze in Gruppen aufteilt, die eigene Überschriften und Berechnungen erfordern, macht ebenfalls Probleme. In diesem Artikel behandle ich einige der Probleme, und im guten FoxPro-Stil zeige ich Ihnen den Umgang mit einer benutzerdefinierten Klasse, die den größten Teil der Arbeit erledigt.

Ich informiere Sie über verschiedene Tricks, die Sie im Umgang mit WordBasic kennen sollten, um Ihre Zeit nicht zu verschwenden, besonders wenn Sie bereits wissen, wie Sie mehrere Datensätze in Visual FoxPro behandeln müssen. Im Grunde benutzen Sie ein SCAN ... ENDSCAN, um durch Ihre Datensätze zu gehen, und Sie betten SCAN WHILEs ein, um die Daten zu gruppieren, auf die Sie in den inneren SCANs treffen. Oder nicht?

Die guten Seiten der FRX

Es ist schon lange her, seit Sie diese Art von Bericht geschrieben haben. VFPs Berichts-Designer erledigt das Gruppieren der Daten und die Bewegungen des Satzzeigers für Sie – und diese Arbeit ist auch seine Aufgabe, besonders wenn wir entscheiden, daß eine FRX-Datei nicht in der Lage ist, unsere Resultate angemessen zu formatieren. Alles was wir brauchen ist eine Möglichkeit, die Ausgabe durch WinWord auszulösen, wie der Berichtsgenerator durch die Datensätze geht und sie gruppiert.

Die Ereignisse von VFPs Berichtsgenerator geben uns diese Möglichkeit. In FoxPro 2.x haben wir dafür vielleicht UDFs benutzt, aber die Ereignisse in VFP-Berichten sind einfacher zu kontrollieren. Sie müssen exakt auf jedes Ereignis reagieren und dabei auch auf andere Aktionen des Berichtsgenerators achten. Für WinWord-Text, der Daten satzweise anzeigt, lassen Sie WordBasic-Code durch die Ereignisse im Detailbereich ausführen. Für zusammenfassende Informationen, die einmal für jede Gruppe ausgeführt werden sollen, schreiben Sie die WordBasic-Anweisungen in den Gruppenkopf oder –fuß.

Das Ergebnis ist ein FRX, der keine eigene Ausgabe besitzt, wie Sie in Abbildung 1 sehen können. Die Ereignisse des FRX rufen verschiedene benutzerdefinierte Prozeduren auf, um eine Ausgabe in der von Ihnen gewünschten Form zu erzeugen.

Die Technik generisch machen

Über diese Technik können Sie glücklich sein, wenn Sie bloß einen Bericht zu erzeugen haben. Ich habe lediglich einige Gruppierungen in meinem leeren Bericht, FRXCLASS.FRX, benutzt. Dafür habe ich die höchste Anzahl Gruppierungen, die ich vermutlich jemals in einem Bericht benutzen werde, in das FRX gesetzt; wenn Sie es wünschen, können Sie auch mehr benutzen. Für jeden Bericht, der weniger Gruppierungen erfordert, gibt der Gruppenausdruck lediglich .T. zurück – und solche Gruppierungen werden nicht ausgeführt. Dementsprechend behalten Ereignisse, die ich nicht immer benötige (z.B. das OnEntry und OnExit eines Titels in einem Bericht, der keinen Titel benötigt), ihre Prozeduren für alle Berichte. Wird in einem bestimmten Bericht kein Titel benötigt, RETURNen diese Prozeduren lediglich ein .T. und keinen Inhalt.

Wie Sie wahrscheinlich bemerkt haben werden, habe ich die Ereignisprozeduren Page und Column in FRXCLASS.FRX ausgelassen. Warum habe ich nicht wie bei anderen Ereignissen leere Prozeduren hinzugefügt, wenn diese Ereignisse doch immer benötigt werden?

Anders als andere Gruppen des Berichtsgenerators sind die Adressierungen von Page und Column Teile des Physikalischen Layout. Ich will den Berichtsgenerator nicht entscheiden lassen, wann eine Seite beginnt oder endet oder wann eine Spalte umbrochen werden muß. Die unintelligente Behandlung der physikalischen Seitengröße ist eines der größten Mankos des Berichtsgenerators von Visual FoxPro. Ich möchte die Seitenbehandlung auf den Teil meines Programms einschränken, in dem ich mit der Textverarbeitung, einer anderen Anwendung oder einem Gerät kommuniziere.

Ableitung von Klassen mit dieser Technik

Vermutlich haben Sie bereits ausgeknobelt, daß es nicht notwendig ist, diese UDFs für jeden WinWord-Bericht, den Sie erstellen, neu zu schreiben. Sie könnten beispielsweise ein Rahmenprogramm haben, das FRXCLASS.FRX aufruft, und das in etwa folgendermaßen aussieht:

oWord = CREATEOBJECT(Word.Basic)

REPORT FORM FrxClass

PROC DetailEntry

<Jede Menge Anweisungen>

ENDPROC

 

<Andere Prozeduren>

Sie könnten diesen Rahmen für jeden Bericht neu erstellen. Wenn Sie aber feststellen, daß Sie Code erneut schreiben, sollten Sie daran denken, den Code in eine Klasse zu schreiben, um ihn einfacher wiederverwenden zu können. Auch wenn dieser Code einfach zu schreiben ist, ermöglicht es doch die Erstellung von Klassen vielen Berichten, von anderen zu erben.

Zum Beispiel könnten Sie vorhaben, daß sich alle Berichte eines bestimmten Kunden bestimmte Stilelemente teilen, die Sie in einem Makro in der Eigenschaft SummaryExit plaziert haben. Wir werden das im nächsten Beispiel durchführen. Sie haben vor, das Makro ohne Cut-and-Paste mehrfach zu verwenden. Sie haben auch vor, das Schreiben leerer Prozeduren für alle Ereignisse und Gruppierungen, die Sie in dem speziellen Bericht nicht verwenden wollen, zu vermeiden. Damit vermeiden Sie auch Fehlermeldungen, daß eine Prozedur nicht gefunden wurde, wenn FRXCLASS.FRX auf die Namen dieser Prozeduren trifft.

Abbildung 1         FrxClass ist ein scheinbar leerer Bericht, der aber die Möglichkeiten für eine unbegrenzte Ausgabe enthält.

Damit haben Sie ein Problem. Die Berichte von VFP sind keine Objekte. Wie wollen Sie davon also eine Klasse ableiten und die Methoden wiederverwenden? Vielleicht sehen Sie die Antwort bereits in Abbildung 1: Jedes Ereignis ruft statt einer UDF eine getrennte Methode eines Berichtsverwaltungsobjekts auf. Der Code, der Ihren Bericht FrxClass ausführt, sieht dann ungefähr so aus:

oFRX = CREATEOBJECT(<MeinBericht>)

REPORT FORM FrxClass

DEFINE CLASS ThisReport AS <FRXMgrClass>

      PROC DetailEntry

      *  <ganz viel Code für die Details>

      ENDPROC

ENDDEFINE

Nicht benötigte Methoden werden von der Elternklasse geerbt, so daß Sie sich keine Gedanken über die Wiederholung irgendwelcher Methoden machen müssen.

*Auszüge aus JUGGLER1.PRG

 

LPARAMETERS tcAlias, tcScope, tnSession, tcTarget, tcDot

* Fügen Sie jeden Parameter, den Sie benötigen hinzu,

* einschließend neuer Features für die Initialisierung,

* die Sie Ihrer abgeleiteten Klasse übergeben wollen.

 

* die Klasse bekanntmachen:

SET PROC TO Frx2Doc.PRG ADDITIVE

 

IF EMPTY(tcDot) OR TYPE("tcDot") # "C"

   tcDot = "JUGGLERS.DOT"

ENDIF  

 

* Objektreferenz erstellen:

PUBLIC oFRX

oFRX = CREATEOBJECT("Juggler1", ;

                     tcAlias, tcScope, ;

                     tnSession, tcTarget, tcDot)

  

IF TYPE("oFRX") = "O" AND NOT ISNULL(oFRX)

   oFRX.Run()

ENDIF

 

RELEASE PROC FRX2DOC

CLEAR CLASS Juggler1

CLEAR CLASS Frx2Doc

CLEAR CLASS FrxClass

RETURN

 

DEFINE CLASS Juggler1 AS Frx2DOC

   cOldAlias = ALIAS()

   lSourceTableUsed = USED("Jugglers")

 

   PROC DEOpenTables(toDE)

      * Die Erstellung der Detail- und

      * Gruppencursor, die die Ergebnisse

      * enthalten, habe ich hier weggelassen

   ENDPROC

 

   PROC TitleEntry

         THIS.oWordRef.InsertPara

      THIS.oWordRef.FormatStyle("Heading 1")     

      THIS.oWordRef.CenterPara

      THIS.oWordRef.Insert("Current Standings")

   ENDPROC

 

PROC GroupLevel1

   RETURN Detail.Name

  

   PROC GroupHeader1Entry

      * Ein Zeichen ausrücken

      THIS.oWordRef.CharRight(0)

      THIS.oWordRef.InsertPara

      THIS.oWordRef.FormatStyle("Heading 2")

      THIS.oWordRef.Insert(Detail.Name)

      THIS.oWordRef.InsertPara         

      THIS.oWordRef.TableInsertTable(,3,1)

      THIS.oWordRef.TableSelectRow

      THIS.oWordRef.FormatStyle("Heading 3")     

         THIS.oWordRef.TableColumnWidth('2"')     

      THIS.oWordRef.StartofRow      

      THIS.oWordRef.Insert("Trick")

      THIS.oWordRef.NextCell

      THIS.oWordRef.Insert("Date")

      * Weiter mit den Einträgen für die

      * Gruppendaten

ENDPROC

  

PROC DetailEntry

      * Aufruf der Thermometer-Methode

      * wenn Sie wollen, einmal pro Gruppe

      THIS.UpdateMessage()

      * Weggelassen: Bewegen durch die

      * Detaildaten, Einfügen der Daten

ENDPROC

  

PROC GroupFooter1Entry

* Weggelassen: Formatierung der Fußzeile

* Das Einfügen der Informationen

* endet wie folgt

THIS.oWordRef.Insert(TRANS(Group1.SumPoints,"@( 999.99"))

THIS.oWordRef.CharRight(0)

* Ende der Tabelle

THIS.oWordRef.LineDown

ENDPROC

  

PROC DECloseTables(toDE)

*     Hier weggelassen: Schließen der Tabellen,

      *  zurück zum Original-Alias

   ENDPROC     

  

PROC SummaryExit

THIS.oWordRef.FilePrintSetup("")

#DEFINE PRT_COLOR        9

      #DEFINE PRTCOLOR_COLOR   2

      IF PRTINFO(PRT_COLOR) = PRTCOLOR_COLOR

         THIS.oWordRef.ToolsMacro("UseRedForNegativeNumbers",1)

      ELSE

         THIS.oWordRef.ToolsMacro("UseBoldForNegativeNumbers",1)

      ENDIF

   ENDPROC

 

ENDDEFINE

Listing A zeigt Ihnen einen Auszug aus JUGGLER1.PRG. Der Code wird zur Erstellung komplexer Berichte verwendet und benötigt FRXCLASS.FRX und sein Managerobjekt Juggler1. Den fertig erstellten Bericht sehen Sie in Abbildung 2. Ich werde Ihnen im nächsten Abschnitt die Klassenhierarchie erläutern, auf der das Managerobjekt basiert. Lassen Sie uns aber erst einen Blick auf die Funktionalität von Frx2Doc, die Elternklasse von Juggler1, werfen.

Tricks in der Vorlage

Das Beispielprogramm JUGGLER1.PRG erhält zahlreiche Parameter, die alle von Frx2Doc initialisiert werden. Das einzige Argument, das direkt einen Wert bekommt, ist die Angabe, welche Formatvorlage für das Word-Dokument verwendet werden soll. Ich wollte die Bedeutung der von WinWords .DOT-Datei für den Vorgang des Berichts betonen. Dem ausgegebenen Dokument sollte eine Formatvorlage zugeordnet sein. In diesem Fall ist es JUGGLERS.DOT, der Briefkopf der Vereinigung. Sie sollten aber auch in der Lage sein, die Vorlage in den unterschiedlichen Instanzen zu überschreiben – daher übergebe ich die Datei dem PRG als Parameter.

Die Formatvorlage enthält eine ganze Anzahl Makros und Stilelemente, die das Dokument brauchen könnte. Warum ist es dann aber nicht gefährlich, die Vorlage zu überschreiben? Word besitzt ein Vererbungsschema für Formatvorlagen, das sich wie eine echte objektorientierte Klassenhierarchie verhält. Alle Formatvorlagen erben die Einstellungen der NORMAL.DOT, der globalen Vorlage. Die Vorlagen können auf anderen DOTs basieren und erben die Einstellungen ihrer Eltern.

Andere Formatvorlagen können in WinWord als AddIns geladen werden und zusätzliche Stilelemente und Makros anbieten. Diese Tatsache unterliegt einem sehr mächtigen Teil meiner VFP-WinWord-Strategie: Die Makros, die ich in JUGGLER1 benutze, kommen von FRX2DOC.DOT, die ich separat von der Juggler-Vorlagen-Hierarchie lade. FRX2DOC.DOT enthält alle Stilelemente, Makros und andere Features, auf denen der Code von Frx2Doc basiert. Dadurch steht es dem Anwender frei, die Vorlage des Briefkopfes zu gestalten und zu ändern.

Zur Sicherheit verwende ich noch eine andere Formatvorlage, die in diesen Beispielen nicht direkt verwendet wird: FRXBASE.DOT. Sie ist komplett leer, und wird von Frx2Doc benutzt, um eine aktuelle Vorlage anzugeben, für den Fall, daß dies weder von der abgeleiteten Klasse noch vom Berichtsprogramm erledigt wird. Im Praxiseinsatz mache ich die FRXBASE.DOT den Anwendern zugänglich, damit diese alle ihre Vorlagen für Berichte mit wenigen globalen Änderungen von einer Quelle ableiten können. Ich habe schreibgeschützte Kopie in meiner APP, die bei Bedarf auf die Platte kopiert werden kann. Auch die FRX2DOC.DOT ist in der APP gespeichert und wird, bevor sie vom Frx2Doc-Prozeß geladen wird, auf die Festplatte kopiert.

Die Behandlung der Daten

Die Klasse Juggler1 benutzt eine Methode der Datenumgebung des Berichts, DEOpenTables(), um dem Bericht die erforderlichen Daten liefern zu können. Alle Ereignisse der FRX, die mit der Datenumgebung zusammenarbeiten, haben entsprechende Frx2Doc-Methoden, die eine Referenz auf die Datenumgebung als Argument erhalten. Im Code von Juggler1 mache ich mit diesem Code nichts; ich benutze lediglich die Ereignisse DEOpenTables() und DECloseTables() als einen passenden Platz, um SELECTs und anderen Code für die Behandlung der Daten für diesen Bericht unterzubringen. Ich gehe davon aus, daß diese Ereignisse zu den richtigen Zeiten eintreten.

Haben Sie bemerkt, daß FRXCLASS.FRX keine private Datensitzung benutzt? Für einen Bericht, der mehrfach verwendet werden soll, halte ich das Verbleiben in der aktuellen Datensitzung für sinnvoll. Sie könnten die FRX aber auch in einem komplexen Abfrage-Dialog einsetzen und den Dialog die Views oder Cursor erstellen lassen, aus denen Sie dann die Daten für Ihren Bericht entnehmen. Diesem Dialog können sie, falls notwendig, eine Referenz auf die entsprechende Datensitzung übergeben.

Dieser Bericht ist nach den Mitgliedern der Code Jugglers gruppiert. Vergleichen Sie dazu in Listing A die Prozedur GroupLevel1. Die Daten in JUGGLER1.PRG kommen von zwei SELECT-Anweisungen, die im Listing nicht enthalten sind. Eine Anweisung erstellt die Detaildatensätze, die andere bildet Untergruppen für jedes Mitglied. Statt eines zweiten Cursor habe ich einfach eine Variable verwendet, die ich in einem der Detail-Ereignisse aufrechne und die ich auf Null setze, wenn ich in eine neue Gruppe komme. Ich glaube, daß diese Methode ein wenig schneller ist, aber vielleicht haben Sie ja andere Prioritäten.

Abbildung 1 Wir haben eine etwas komplexere Ausgabe als WinWord-Dokument erzeugt, die mehrere Datensätze in einer Tabelle enthält.

[ 1 ] [ 2 ]