Reports: Trigger, DataItems & mehr

22. Juli 2010 21:11

Dieser Artikel befindet sich im Aufbau. Er soll den häufigsten Wissenslücken unserer Mitglieder entgegen wirken. Der Artikel dient nicht dazu, die Reporterstellung lückenlos zu beschreiben.
Programmiergrundkenntnisse werden vorausgesetzt!

Einführender msdn-Artikel: Designing Reports for the Classic Client
Dieser Artikel erläutert nur die klassischen Reports und nicht die Reporting-Services-basierten Reports seit NAV 2009!


Springe zu


Warum Reports?
Viele Anwendungen, die von Reports ausgeführt werden, können ebenso durch andere Objekte (z.B. Codeunits) ausgeführt werden. Reports braucht ihr, wenn
  • ein Ausdruck generiert werden soll (logisch ;-))
  • der Benutzer die Möglichkeit haben soll, Daten zu filtern, einen Schlüssel oder Optionen zu wählen, ohne dass hierfür eine eigene Form erstellt werden muss. Das Zauberwort heißt Request-Form. Mit nur wenig Aufwand realisiert ihr die Filermöglichkeit für verschiedene Tabellen.
Keine technische Voraussetzung, aber eine Unterstützung ist der Report außerdem bei der
  • Strukturierung von Tabellendurchläufen. Ein Report-Aufbau kann dem Programmierer schnell visualisieren, welche Tabellen in welchen Abhängigkeiten durchlaufen werden. Daher werden Reports gern für Stapelverarbeitungen verwendet. Dazu später mehr.

DataItems
DataItems sind das Herzstück von Report-(und übrigens auch Dataport-)Objekten. Ein DataItem steht für genau eine zu durchlaufende Tabelle. Ein DataItem druckt nicht, das ist Aufgabe der Sections.
Ihr könnt DataItems direkt sehen, wenn ihr im Object Designer einen Report designt (Button Design oder Alt+D). Beispiel Report 101:
DataItemName
Customer<Customer>

"Customer" links ist die genaue Bezeichnung des Tabellennamens für Tabelle 18.

  • DataItems referenzieren auf Tabellen durch Anlage einer internen Variable
    In dem Augenblick, wo ein DataItem angelegt wird, wird intern eine (Report-)globale Record-Variable (Subtype = Tabellenname) erstellt. Der Variablenname entspricht dem Inhalt des Feldes Name. Intern nenne ich sie deswegen, weil sie nicht in den globalen Variablen aufgeführt wird. Wenn ihr im Design-Modus jedoch F5 aufruft, findet ihr in der ganz linken Spalte den DataItem-Namen wieder, so wie andere Variablen auch.
    Exkurs DataItem-Namen hat geschrieben:
    DataItemName
    Customer<Customer>Die interne Recordvariable verweist auf Tabelle Customer und ist unter F5 als Customer zu finden.
    CustomerMeinDebDie interne Recordvariable verweist auf Tabelle Customer und ist unter F5 als MeinDeb zu finden.
    CustomerMein DebDie interne Recordvariable verweist auf Tabelle Customer und ist unter F5 als Mein Deb zu finden. Übernehmt ihr aus F5 die Variable, wird sie "Mein Deb" geschrieben
    MeinDebCustomerFehler: Es gibt keine NAV-Tabelle, die MeinDeb heißt!

    Ihr könnt also in der rechten Spalte "Name" eine eigenen Variablennamen vergeben.
    • Normalerweise müsst ihr Name nicht aussfüllen: dann bleibt <Tabellenname> stehen.
    • Da Variablennamen jedoch niemals mehrfach vergeben werden dürfen, dürft ihr in einem Report nicht zwei DataItems mit identischen Einträgen in Spalte Name haben. Sonst erhaltet ihr folgende Fehlermeldung:
      Fehlermeldung hat geschrieben:Die Variable CUSTOMER ist mehrfach definiert.
      Ein mögliches Beispiel: 'File' existiert als Datentyp und ebenso als globale Variable.

      Wenn ihr also zwei DataItems auf Tabelle Debitor braucht, so tragt z.B. folgendes ein:
      DataItemName
      Customer<Customer>
      CustomerCustomer2
  • DataItems durchlaufen eine Tabelle ohne zusätzliche Programmierung
    Nun haben wir mit der DataItem-Anlage also dem Report mitgeteilt, dass er dieTabelle Customer durchlaufen werden soll. Was heißt das?
    Würden wir jetzt den Report speichern und ausführen, würde tatsächlich jeder Datensatz der Tabelle durchlaufen werden, ganz egal, ob gedruckt werden soll oder nicht! Dies geschieht ohne einzige Zeile Quelltext.
    Wir können nun auf diesen Durchlauf Einfluss nehmen, indem wir z.B.
    • Vor Durchlauf Schlüssel und Filter setzen
    • Für jeden gefundenen Datensatz etwas ausführen
    • Nach Durchlauf etwas ausführen
    Hierfür benötigen wir die DataItem-Trigger.

  • DataItems einrücken
    Ist DataItem 2 ggü. DataItem 1 um eine Stelle eingerückt, so wird für jeden gefundenen Datensatz in DataItem 1 das DataItem 2 durchlaufen - siehe auch Abschnitt Reihenfolge. Umgekehrt: Wurde in DataItem 1 kein einziger Datensatz gefunden, wird DataItem 2 kein einziges Mal durchlaufen.
    Achtung: DataItem 2 muss nun so gefiltert werden, dass nur solche Datensätze durchlaufen werden, die sich auf DataItem 1 beziehen.
    Beispiel mit "Sales Header" (DataItem 1) und "Sales Line" (DataItem 2, eingerückt): Belegart und Belegnr. der Zeilen müssen mit denen des aktuellen Kopfes übereinstimmen.

  • Integer als DataItem (NAV <= 2013 R2)
    Hinweis: Ab NAV 2015 braucht ihr keine Integer-DataItems mehr - siehe hier.
    In Standardreports begegnen sie einem öfters: DataItems, welche auf der Tabelle Integer basieren. Wofür soll das gut sein?
    Die virtuelle Tabelle Integer besteht aus genau einem Feld (nämlich "Number") und enthält alle in NAV verfügbaren negativen und positiven ganzen Zahlen. Filtert man diese Tabelle z.B. von 1 bis 5, so wird die Tabelle 5 mal durchlaufen. Diese Eigenschaft macht man sich zunutze:

    • Man möchte irgendetwas (nicht unbedingt Tabellendaten) genau einmal drucken: Tabelle integer auf den Wert 1 Filtern und eine Section dieses DataItems für den Druck nutzen

    • Kopien erstellen:
      • Einen Datensatz mehrfach hintereinander drucken:
        Ihr habt ein DataItem, z.B. Item. Dann erstellt ein neues DataItem integer:
        Code:
        Item
          integer
        Filtert integer von 1 bis zu eurer Wunschanzahl. Verschiebt den Inhalt der Section Item-Body nach integer-Body. Der Item-Body kann gelöscht werden.
      • Belegkopien:
        Siehe Report 206, DataItem CopyLoop

    • Temporäre Records drucken: Schaut es euch aus dem Standardreport 111, DataItem integer, Trigger OnAfterGetRecord ab. CustAmount ist hier ein temporärer Record.
      Code:
      IF Number = 1 THEN BEGIN           // 1. Durchlauf
        IF NOT CustAmount.FIND('-') THEN // (1)
          CurrReport.BREAK;              // (2) 
      END ELSE                           // 2. (bis n-ter) Durchlauf
        IF CustAmount.NEXT = 0 THEN      // (3)
          CurrReport.BREAK;              // (4).       

      Legende:
      (1) Suche ersten Datensatz von CustAmount
      (2) Wenn CustAmount leer: DataItem beenden. Wenn CustAmount nicht leer, dann wird hier gerade die Body-Section gedruckt
      (3) stehe noch auf dem 1. CustAmount-Datensatz. Gibt es einen nächsten? Wenn ja: drucken.
      (4)Wenn nein: DataItem beenden
    Achtung, Falle! Die Zählung fängt nicht automatisch bei 1 an, sondern nur, wenn ein entsprechender DataItem-Filter (1..) verwendet wird!

    Mehr Infos: Temporary Dataset Report

Trigger - auf msdn library
Trigger gibt es sowohl für den Report allgemein als auch für jedes DataItem einzeln. Sie unterteilen einen Report-Durchlauf in einzelne Phasen und erlauben uns, in diese Phasen durch Programmierung einzugreifen.
Oder auch: Alle Report-Trigger werden in einer bestimmten Reihenfolge nacheinander durchlaufen.

  • Um Report-Trigger zu sehen, klickt auf die erste leere Zeile unterhalb des letzten DataItems und klickt F9.
    Eine Auflistung und Erläuterung könnt ihr der msdn library entnehmen.

  • Um DataItem-Trigger zu sehen, klickt auf ein DataItem und dann F9.
    Am einfachsten erschließen sich DataItem-Trigger duch einen Vergleich mit herkömmlichen Quelltext.
    Folgender Code könnte aus einer Codeunit stammen:Dieser kann mit DataItem "Customer" folgendermaßen abgebildet werden:
    Code:
    Customer.SETCURRENTKEY(Name);
    Customer.SETFILTER(Name, 'G*');
    IF Customer.FINDSET THEN
      REPEAT
        VarCustName 
    := Customer.Name;
        Counter += 1;
      UNTIL Customer.NEXT = 0;
    MESSAGE('Habe %1 Debitoren gezählt!', Counter);
    Code:
    #--- OnPreDataItem#---#
    SETCURRENTKEY(Name);
    SETFILTER(Name, 'G*');

    #--- OnAfterGetRecord ---#
    VarCustName := Name;
    Counter += 1;

    #--- OnPostDataItem ---#
    MESSAGE('Habe %1 Debitoren gezählt!', Counter);

    Filter werden im OnPreDataItem-Trigger gesetzt.
    Das System setzt danach eine Datenbankabfrage ab.
    Für jeden gefundenen Datensatz tritt dann OnAfterGetRecord ein. Nur hier können die gefundenen Datensätze vom Programmierer gelesen werden.
    OnPostDataItem wird auch dann ausgeführt, wenn kein Datensatz gefunden wurde.

    In den DataItem-Triggern kann auf die Verwendung der Record-Variable verzichtet werden.
    Im DataItem Customer(!) gilt:
    Code:
    SETFILTER(Name, 'G*');
    Dies entspricht dem Styleguide.
    entspricht
    Code:
    Customer.SETFILTER(Name, 'G*');
    Macht den Quelltext etwas unübersichtlicher.

  • Trigger-Reihenfolge
    Report mit DataItems 1 und 2, wobei DataItem 2 ggü. 1 eingerückt ist.
    Reihenfolge hat geschrieben:
    1. OnInitReport
      • danach: Aufruf der Request-Form
    2. OnPreReport
    3. OnPreDataItem 1
    4. OnAfterGetRecord 1
      • OnPreDataItem 2
      • OnAfterGetRecord 2
      • OnPostDataItem 2
    5. OnPostDataItem 1
    6. OnPostReport

Request-Form
Für jedes DataItem erscheint auf der Request-Form eine eigene Karteikarte mit dem Tabellennamen. Soll für ein DataItem diese Karteikarte ausgeblendet werden, dann:
  • setzt keine ReqFilterFields
  • wählt einen Schlüssel im DataItemTableView

Sections

QUIT, SKIP, BREAK

Farben
Sämtliche Farbeinstellungen, die ihr im Reportdesigner zur Auswahl habt, können nicht gedruckt werden.
NAV unterstützt nur schwarze Schrift auf weißem Grund. Keine Graustaufen.
Unter Zurhilfenahme von Bitmaps, als Graphiken, sind immerhin bunte Hintergründe möglich.
Warum es dann die Einstellmöglichkeiten überhaupt gibt? Das fragen wir uns alle schon sehr, sehr lange ....
Siehe dazu auch: