[ 1 ] [ 2 ]

Spezielle Features dieser Unterklasse für die Berichtsverwaltung

Geschwindigkeit ist wichtig. Sie sollten immer nach der schnellstmöglichen Methode für die Kommunikation mit Word suchen. Im Code von GroupHeader1Entry, den sie in Listing A sehen, habe ich eine Tabelle erstellt, die die Daten der aktuellen Gruppe enthält.

Ich habe den Code für die Erstellung der Tabelle bewußt in mehrere Zeilen aufgeteilt, so daß Sie in der Lage sein sollten, zu erkennen, wie die Tabelle aussieht. Beachten sie besonders, daß ich die Spaltenbreite durch einen String festlege und dabei die Maßeinheit mit angebe (THIS.oWordRef. TableColumnWith(‘2“‘)). Sie können und sollten die Einheit immer mit angeben, da Sie nicht wissen, welches Maßsystem in WinWord voreingestellt ist.

Auf jeden Fall vermindern OLE-Anweisungen immer die Geschwindigkeit. Wenn ich Code schreibe, der anschließend in der Praxis eingesetzt werden soll, könnte ich eine spezielle Formatvorlage erstellen, die ein Beispiel dieser Tabelle als Autotext enthält. Dann brauche ich die sie nur noch an die richtige Position einzufügen, wenn ich mit einer Gruppe beginne.

Auch mit solchen Verbesserungen bleibt die Kommunikation zwischen Komponenten recht behäbig. Sie könnten nun dem Anwender mitteilen, daß nichts schiefgegangen ist und die Verarbeitung läuft. Ich habe der Klassenhierarchie meiner Klasse FRXClass die Methode UpdateMessage() hinzugefügt, die dem Anwender in ähnlicher Form wie die Fortschrittsanzeige von WinWord mitteilt, daß seine Aktion bearbeitet wird. Sie sehen sie in Abbildung 3. In der Klasse Juggler1 habe ich UpdateMessage() nur im Detailbereich aufgerufen, da die Tabelle nur wenige Datensätze enthält; aber in größeren Tabellen kann es sinnvoll sein, sie für jede Gruppe aufzurufen.

Abbildung 3         Die Methode UpdateMessage() teilt dem Anwender mit, daß das Dokument erstellt wird.

Da Juggler1 einen Standardbriefkopf der Organisation verwendet, habe ich das Ereignis TitleEntry() benutzt, um diesem Bericht einige Formatanweisungen für diesen Bericht mitzugeben. Dieser Abschnitt kann nahezu endlos erweitert werden oder Sie speichern berichtsspezifische Informationen in einer berichtsspezifischen Formatvorlage.

Sie werden bald feststellen, daß Sie die WordBasic-Anweisungen sehr schnell schreiben können, daher ist es nicht notwendig, sie noch länger zu behandeln. Der Code, den ich in das Ereignis SummaryExit() geschrieben habe, erfordert aber doch noch einige Aufmerksamkeit. In diesem Ereignis kann ich alle Arten von globalen Formatierungen unterbringen, die ich nicht in jedem Detailbereich erneut plazieren will. Zum Beispiel formatiert der Code von SummaryExit() negative Zahlen entweder rot oder fett (je nach den Fähigkeiten des Druckers). Dafür werden Makros verwendet, die ich in FRX2DOC.DOT gespeichert habe.

Als Erstes stellt SummaryExit() Word auf den Standarddrucker ein. Sie können dem Anwender auch mit Visual FoxPros Funktion APRINTERS() eine Liste aller verfügbaren Drucker anzeigen lassen und ihn wählen lassen. Als nächstes stellen Sie mit VFPs Funktion PRTINFO() fest, ob der Drucker Farbe drucken kann, soweit VFP und Windows das feststellen können. Haben Sie APRINTERS() eingesetzt und WinWord benutzt nicht den Standarddrucker, müssen Sie PRTINFO() den optionalen zweiten Parameter mit der Druckerinformation übergeben. Anschließend können Sie entscheiden, welches Makro eingesetzt werden soll und übergeben dessen Namen WordBasics Funktion ToolsMacro(). Ich habe eines der zwei Makros, die sich im Übrigen nicht stark unterscheiden, in Listing B dargestellt.

Sub MAIN

StartOfDocument

EditFindClearFormatting

EditFind .Find = "( ", .Direction = 0, .Wrap = 0

While EditFindFount()

      ExtendSelection(")")

      FormatFont .Color = 6

      Cancel

      CharRight

      EditFind .Find = "( ", .Direction = 0, .Wrap = 0

Wend

End Sub

Jenseits von Word

Sie haben nun gesehen, wie die Klasse Juggler1 auf ihrer Elternklasse, Frx2Doc, aufbaut. Frx2Doc ist eine abstrakte Klasse, die für FRXCLASS.FRX die Dokumentenstellung verwaltet. Vielleicht glauben Sie nun, das einzige komplexe wäre der Code für die generelle Behandlung von WinWord, den ich in Frx2Doc geschrieben habe. Wenn Sie eine Klassenhierarchie entwerfen und erstellen, werden Sie häufig feststellen, daß Sie das eine oder andere erneut entwerfen, bis Sie die meisten Funktionalitäten der Basisklasse in höheren Stufen der Klassenhierarchie separiert haben, die dann die unterschiedlichen Aufgaben in zusätzlichen abgeleiteten Klassen ausführen. Auch Frx2Doc war da keine Ausnahme. In der jetzigen Fassung basiert Frx2Doc auf FrxClass, einer Custom-Klasse, die FRXCLASS.FRX steuert, ohne selber eine eigene Ausgabe zu besitzen.

DEFINE CLASS Frx2Doc AS FRXClass

* überschreibt die Vorgabewerte von FRXClass:

cTarget = THIS.cFRXFileDir + "\FRX2Doc.DOC"

cTargetExt = "DOC"

Name = "FRX2DOC"

cInitMessage = "Contacting Word and initializing..."

* FRX2Doc-spezifische Eigenschaften:

cDotExt = "DOT"

oWordRef = .NULL.

cAddInFRXDOT = THIS.cFRXFileDir + "\FRX2Doc.DOT"

cDot = THIS.cFRXFileDir + "\FRXBase.DOT"

  

FUNC Init(tcAlias, tcScope, tnSession, tcTarget, tcDot)

      IF NOT FRXClass::Init(tcAlias, tcScope, tnSession, tcTarget)

      RETURN .F.

      ENDIF

      WAIT WINDOW NOWAIT THIS.cInitMessage

      THIS.cDot = THIS.GetFileName(THIS.cDot,tcDot,THIS.cDotExt)

      THIS.oWordRef = CREATEOBJECT("Word.Basic")

      * Testen der Word-Referenz,

    * Ausführen und Testen anderer erforderlicher

    * Setup-Bedingungen:

    lcConditionsFailMessage = ;

      THIS.Setup2(tcAlias,tcScope,tnSession, ;

            THIS.cTarget, THIS.cDot)

      IF NOT EMPTY(lcConditionsFailMessage)

      =MESSAGEBOX(lcConditionsFailMessage,16, ;

        THIS.Name+" kann nicht ausgeführt werden")

         THIS.lError = .T.

      ENDIF   

      IF NOT THIS.lError

         THIS.oWordRef.AddAddIn(THIS.cAddInFRXDOT,1)

         THIS.oWordRef.FileNew(THIS.cDot)

        THIS.oWordRef.FileSaveAs(THIS.cTarget)

      ENDIF

      WAIT CLEAR

RETURN ! THIS.lError

  

PROCEDURE Setup2(tcAlias, tcScope, tnSession, tcTarget, tcDot)

      IF TYPE("THIS.oWordRef") # "O" OR ISNULL(THIS.oWordRef)

         RETURN "Kann nicht auf Word zugreifen!"

      ENDIF

      * Hier weggelassen: Setzen und Testen zusätzlicher

      * Frx2Doc-spezifischer Bedingungen, die im

    * Artikel beschrieben werden

      * Gibt im Fehlerfall eine Meldung zurück.

RETURN ""

 

PROC Destroy

      FRXClass::Destroy()

      THIS.oWordRef = .NULL.

ENDPROC

 

PROC Run

      LOCAL lnDocNumber

      FRXClass::Run()

      * Als erstes stellen sicher, daß Ihr Dokument aktiv ist.

      lnDocNumber = THIS.GetDocNumber(THIS.cTarget)

      IF lnDocNumber > 0

         THIS.oWordRef.WindowList(lnDocNumber)

         THIS.oWordRef.FileSave( )

         THIS.oWordRef.FileClose(1)

      ENDIF

ENDPROC

  

FUNC IsDocOpen(tcTargetName)

      LOCAL lnCount, liIndex, llFound

      lnCount = THIS.oWordRef.CountWindows()

      IF lnCount > 0

         FOR liIndex = 1 TO lnCount

            IF UPPER(tcTargetName) == ;

               UPPER(THIS.oWordRef.FileNameFromWindow(liIndex))

               llFound = .T.

               EXIT

            ENDIF

         ENDFOR

      ENDIF     

RETURN llFound

 

FUNC GetDocNumber(tcTarget)

      LOCAL lnCount, liIndex, lnFound

      lnCount = THIS.oWordRef.CountWindows()

      lnFound = 0

      IF lnCount > 0

         FOR liIndex = 1 TO lnCount

            IF UPPER(tcTarget) == ;

               UPPER(THIS.oWordRef.FileNameFromWindow(liIndex))

               lnFound = liIndex

               EXIT

            ENDIF

         ENDFOR

      ENDIF     

RETURN lnFound

  

* Alle Methoden, die im Ereigniscode der FRX benötigt werden

* bleiben in der abstrakten Klasse Frx2Doc leer,

* Sie können sie für verschiedene Berichte individuell füllen

ENDDEFINE

Listing C:              Auszug aus FRXCLASS.PRG

DEFINE CLASS FRXClass AS Custom

      cFRXFileDir = SET("DIRECTORY")

      cTarget = THIS.cFRXFileDir + "\FrxClass.PRN"

      cTargetExt = "PRN"

      cVFPReport = "FRXClass.FRX"

      cScope = ""

      nOldSelect = 0

      nOldSession = 0

      lError = .F.

      Name = "FRXClass"

      cRunMessage = "Erstelle Dokument..."

      cInitMessage = "Initialisiere Dokument..."

 

      PROC Init(tcAlias, tcScope, tnSession, tcTarget)

         WAIT WINDOW NOWAIT THIS.cInitMessage

         LOCAL lcConditionsFailMessage

         THIS.cTarget = THIS.GetFileName(THIS.cTarget,tcTarget,THIS.cTargetExt)

         * Ausführen und Testen der notwendigen Setup-Bedingungen:

         lcConditionsFailMessage = ;

            THIS.Setup(tcAlias, tcScope, tnSession, THIS.cTarget)

         IF NOT EMPTY(lcConditionsFailMessage)

            =MESSAGEBOX(lcConditionsFailMessage,16, ;

               THIS.Name+" kann nicht ausgeführt werden")

            THIS.lError = .T.

         ENDIF  

         WAIT CLEAR

      RETURN ! THIS.lError

 

      PROC Setup(tcAlias, tcScope, tnSession, tcTarget)

         IF NOT _WINDOWS

            RETURN "Dieser Code ist nicht plattformübergreifend!"

            * Zum Beispiel werden kein Pfade auf dem Mac behandelt...

         ENDIF

         * Hier ausgelassen: Zusätzliche Fehlerbehandlung,

         * Setzen von Datensitzung, Alias und Bereich

      RETURN ""

  

      PROC Destroy

         WAIT CLEAR

         IF NOT THIS.lError

            =MESSAGEBOX(THIS.Name +" erstellt Ihren Bericht als"+;

               CHR(13)+THIS.cTarget+".",0, ;

               THIS.Name+" Finished!")

         ENDIF                    

         *  Hier ausgelassen: zurücksetzen von Datensitzung und Alias

         RELEASE oFRX

      ENDPROC

 

      PROC Run

         LOCAL lcScopeString

         WAIT WINDOW NOWAIT THIS.cRunMessage

         lcScopeString = THIS.cScope  

         REPORT FORM (THIS.cVFPReport) ;

            &lcScopeString ;

            NOCONSOLE ;

            NAME oFRXDE

         RELEASE THIS                 

      RETURN

     

      FUNC GetFileName(tcDefaultFileName, ;

         tcDesiredFileName, ;

         tcExtension)

         * Ausgelassen: Setzen eines gültigen Dateinamens und

         * eines Pfades

         * Automation Server benötigen einen vollqualifizierten Pfad

      RETURN lcReturn

 

      PROC Error(nError, cMethod, nLine)

         THIS.lError = .T.

         * Hier nur rudimentär...

         WAIT WINDOW cMethod+CHR(13)+STR(nError)+;

            CHR(13)+MESSAGE( )+CHR(13)+;

            MESSAGE(1)

      ENDPROC

  

      PROC UpdateMessage

         * Hier ausgelassen: Rückmeldung an den

         * Anwender, Beschreibung im Artikel

      ENDPROC                        

 

      * Gruppierungs-Bedingungen für jede Stufe der Gruppierung:

      FUNC GroupLevel1

      RETURN .T.

      * Hier ausgelassen: zusätzliche Gruppierungsbedingungen

 

      * Leere Methoden

      PROC TitleEntry()

      ENDPROC

      PROC TitleExit()

      ENDPROC

      *  Hier ausgelassen: Funktionen für zusätzliche Gruppierungen

      FUNC DEInit(toDE)

      RETURN .T.

      *  Hier ausgelassen: Zusätzliche DE-Methoden  

ENDDEFINE

Listing D -            Auszug aus FRXCLASS.PRG

Die Listings C und D zeigen Ihnen Aufzüge der Definitionen von Frx2Doc und FrxClass. Die Klasse Frx2Doc erhält mehrere Parameter, die ersten vier werden von der Superklasse FrxClass vorgeschrieben, die fünfte ist WinWord- und Frx2Doc-spezifisch. Die Parameter enthalten folgende Angaben:

  • den Alias zum SELECTen, der die treibende Kraft für die Bewegung des Datensatzzeigers durch das FRX ist,
  • den Bereich, in dem das FRX sich in der SELECTierten Tabelle bewegen soll,
  • die Datensitzung, in der der Alias geöffnet ist, auf den zugegriffen wird,
  • das zu erstellende Dokument für die Ausgabe,
  • die .DOT-Datei, die benutzt werden soll.

Das Programm, das eine von Frx2Doc abgeleitete Klasse instantiiert, sollte wie dieses aussehen:

PUBLIC oFRX

oFRX = CREATEOBJECT(<von Frx2Doc abgeleitete Klasse>)

      tcAlias, tcScope, ;

      tnSession, ;

      tcTarget, tcDot, ;

      <weitere Parameter>

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

      oFRX.Run()

ENDIF

Beachten Sie, daß oFRX ein unbedingt erforderlicher Variablenname ist, da diese Referenz für alle Ereignisaufrufe in der FRX benötigt wird. Das Objekt oFRX räumt auf und gibt in seiner Methode Run() selbst frei.

Ihre Methode Run() könnte, bevor sie etwas anderes macht, versuchen, Word mit dem Dokument in der Seitenansicht zu öffnen. Sie könnte auch QUICKVIEW, den Dateibetrachter, der mit Win95 ausgeliefert wird, starten, um dem Anwender die Möglichkeit zu geben, sich das Dokument anzusehen. Wie ich bereits früher beschrieben habe, könnte Run() es dem Anwender auch ermöglichen, das Dokument in einem beliebigen Dateityp zu speichern. Sie können diese Vorschläge noch beliebig erweitern Sie finden genauere Erläuterungen zu diesem Vorgehen, dem Gebrauch und Entwurf der Klassen Frx2Doc und FrxClass in den vollständigen Source, die die Klassendefinitionen enthalten (Listing C und D: FRX2DOC.PRG).

Wenn Sie sich die generische Natur von FrxClass ansehen, werden Sie leicht verstehen, wie das entsprechende Berichtsformular andere Ausgabeformate steuern kann. Stellen Sie sich zum Beispiel vor, es wird eine Präsentation von PowerPoint mit zwei Tabellen in einer 1:n-Beziehung gesteuert, von denen die Mastertabelle Überschriften oder Titel enthält und die Detailtabelle einzelne Punkte oder die Darstellungen selber enthält.

Sie starten die OLE-Sitzung mit dem bekannten CREATEOBJECT() und benutzen dann das Objektmodell von PowerPoint, fügen der Sammlung der Präsentationen ein Mitglied hinzu und dem neuen Präsentationsobjekt Folien hinzu. Der Code in der Gruppe könnte zum Beispiel so aussehen:

oSlide = oPresent.Slides.Add(<number>,<layout>)

oSlide.Objects.Title.Text = ALLTRIM(<Feldname>)

*     Erstellen einer Referenz auf den „Körper der Folie

oBody = oSlide.Objects.Objects.Placeholders(1)

Der Code für den Detailbereich geht weiter mit dem füllen der Objektreferenz oBody mit zusammengesetzten Zeichenketten, die die Punkte, die Sie auf der Folie darstellen wollen, benennen. Sie werden durch Zeilenumbrüche voneinander getrennt. Aufbauend auf den Layoutinformationen, die Sie ausgewählt haben, werden diese Punkte automatisch formatiert. Die Formatanweisungen können sie aber ändern und Grafiken, eigene Hintergründe usw. „während des laufenden Betriebs“, ganz wie es Ihre Daten erfordern hinzufügen.

Jenseits von OLE

Nachdem Sie nun einmal gesehen haben, wie OLE Automation arbeitet, ist der Code, den Sie für den jeweiligen Server schreiben, einfach und vorhersehbar. Je offener das Objektmodell des Server, desto einfacher und vorhersehbarer ist der Code. Daher werde ich auch keinen extra Artikel schreiben, in dem ich Ihnen zeige, wie Sie den Code einer Klasse für PowerPoint schreiben, die Sie von FrxClass ableiten. Außerdem steht Ihnen mit Frx2Doc eine gut entwickelte von FrxClass abgeleitete Klasse zur Verfügung. Diese Beispiele sollten Ihnen auch helfen, mit jedem anderen OLE Automation Server zu arbeiten.

Allerdings definiert OLE Automation noch nicht die Grenzen von Nicht-VFP-Ausgabemöglichkeiten oder die Möglichkeiten von FrxClass. In der nächsten Folge dieses Serie, werden wir mit FrxClass HTML-formatierten Text generieren, mit dem Sie WWW-Dokumente erstellen können. Sie werden sehen, daß die gleiche „Technologie des leeren Berichts“, mit der wir hier ein WinWord-Dokument erstellt haben, auch in ein vollständig anderes Ziel ausgeben kann.

[ 1 ] [ 2 ]