Reports: Nicht ganz so limitiert, wie man denkt

In Xojo gibt es mindestens drei Arten, etwas zu Papier zu bringen – alle davon finden Sie in den aus der IDE unter „Examples“ vorliegenden Beispielen unter „Printing & Reporting“:

  • Man kann ein Graphics-Objekt initialisieren und es mit den Graphics-Befehlen „bemalen“ – wozu auch DrawString gehört, also die Textausgabe als Grafikelement. Das ist zweifelsohne die flexibelste Methode, aber auch die anstrengendste – Sie müssen ja alles von Hand erledigen.
  • Bequemere Textausgabe erreicht man mit dem StyledTextPrinter, der einen ganzen Textblock ausgeben kann. Damit ist auch recht einfache Mehrspaltigkeit möglich.
  • Und schließlich gibt es noch die Reports, bei denen sich Platzhalter für Ausgabeelemente auf einem Formular platzieren lassen. Ideal für Bürokram wie Rechnungen und Angebote sowie jede Form von Übersichten, aber leider auch sehr eingeschränkt: Es gibt zunächst nur Header und Footer, die auf jeder Seite wiederholt werden, sowie den Body-Bereich, in dem Einzelposten etwa von Datensätzen ausgegeben werden, und der beliebig aufgebläht werden kann. Weitere Abschnitte lassen sich zwar hinzufügen – diese haben dann wieder Header und Footer –, etwa um Kundennamen auszugeben und dann die Auswertung nach Kunden. Ein Beispiel dafür unter „GasReport“, bei dem eine Übersicht der Entwicklung von Gaspreisen nach Jahren aufgeschlüsselt erfolgt:

GasReport

Man wird also schnell zum Gedanken verführt, es wäre nur möglich, einmalige Daten – etwa Namen, Adresse etc. – im PageHeader-Bereich auszugeben, während für sich wiederholende Daten der Body verfügbar ist. Der PageHeader hat den Nachteil, dass er auf jeder Seite neu erscheint. Und das ist bei mehrseitigen Reports dann doch ein bisschen zu viel des Guten. Will man mehr, so wird gesagt, muss man, wenn man die Lösung nicht völlig selbst erschaffen möchte, zu einem fortgeschritteneren Reports-Tool greifen. Davon gibt es einige, nur sind sie zumeist nicht ganz billig.

Jetzt die gute Nachricht: Das stimmt gar nicht! So limitiert sind selbst die einfachen Xojo-Reports nicht. Man muss nur die Möglichkeiten des Reports.Dataset-Interfaces nutzen und ein bisschen die Group-Sections, die in offizieller Beschreibung zur Ankündigung eines neuen Abschnitts vorhanden sind, zweckentfremden. Das ist gar nicht mal so schwer und bringt zudem den Vorteil, auch komplexe Datenbankabfragen, für die man sich sonst die Synapsen verwirbeln müsste, zerlegen zu können und ganz nach Belieben mit weiterem Code anzureichern, bevor das zugehörige Report-Feld beschrieben wird.

Da Ihr Report sicher ganz anders aussehen wird als meiner und ich zudem selbst momentan damit beschäftigt bin, einige Auftragsarbeiten zu erfüllen, wird dieser Beitrag ein kleines bisschen akademischer als üblich: Ich beschreibe als die Grundlagen und überlasse das Austüfteln Ihnen. Ok?

Vorweg schon mal: Das – hier noch nicht wirklich designte Ergebnis – sieht dann in meinem Fall so aus:

DaisyControl 1.0.0b1 · Benutzer- UB

DaisyControl 1.0.0b1 · Benutzer- UB 2

Der Report …

Dazu brauchte es zunächst einmal einen Report:

Report

Wie auch im Video-Tutorial zu Drucken & Reports empfohlen, setze ich als Vorgabetext der Report Fields den Feldnamen ein. Das macht es schneller, wenn man den entsprechenden Code ergänzt.

Der PageHeader trägt nur die Informationen, die wirklich auf jeder neuen Seite stehen sollen – also den Namen, das Ausgabedatum und ein paar weitere Felder. Und im PageFooter steht nur das Feld für die Seitenzahl – ein PageNumberLabel.

Mit beherztem Klick auf Add Group Section – das erste Symbol in der Werkzeugleiste des Report Editors – habe ich weitere Abschnitte hinzugefügt – Anschrift, Kommunikation, Interesse (das Ganze ist eine Immobilienmakler-Anwendung, die ich gerade entwickle).

Report Editor controls

Die entsprechenden Group Footers, die unter dem Abschnitt Body auftauchen, brauche ich nicht. Also einfach im Inspector deren Height auf 0 gesetzt.

… und sein Dienstleister

Die Daten stammen aus einer SQLite-Datenbank, die wie üblich jede Menge Tabellen relational verknüpft. Zentrum ist die Tabelle Person, und eine Person kann nun mehrere verknüpfte Telefonnummern, Mailadressen (jeweils eigene Tabellen) und und referenzierten – und wie in diesem Fall auch beliebig viele (hier recht sinnlose) Notizen.

Die Notizen sind das einzige Element, das im Body-Bereich ausgegeben wird. Den Rest habe ich auf plausible Durchschnittswerte voreingestellt. Ich glaube aber, dass sich durch weitere Unterteilung der Abschnitte hier noch mehr Flexibilität erreichen lässt – also nicht unbedingt drei Telefonnummernzeilen verfügbar sein müssen, sondern nur so viele, wie wirklich benötigt werden. Falls Sie diese Anleitung weiterverfolgen: Probieren Sie es gerne mal aus und lassen Sie mich am Ergebnis teilhaben! Oder warten Sie noch ein Weilchen auf den Folgebeitrag …

Nun, jedenfalls: Wenn ich diese Daten pur aus der Datenbank extrahieren würde, wäre ich wegen der komplexen SQL-Abfragen vermutlich irgendwann im Spätsommer fertig. Ich mache es mir lieber einfach und erstelle eine neue Klasse: ReportServer. Der erhält das Interface Reports.Dataset und verfügt deshalb dann über fast alle der unten angegebenen Methoden.

Report Server.png

In seinem Constructor erhält er die ID des Personendatensatzes und erzeugt daraus das Recordset des gewünschten Kontakts sowie ein Recordset für die zugehörigen Notizen. Lassen Sie sich von den ungewohnten Befehlen nicht verwirren: Ich habe mir ein kleines Modul zusammengestrickt, das mir das wiederholte Tippen von „Select * From … Where …“ etc. und ähnliches erleichtert:

ContactRS = MainDB.SelectAllWhere(TabPerson, FieldID+SQLModule.equals+id.ToText)
Notizrs = MainDB.SelectAllWhereOrdered(TabNotiz, FieldPersonlink+SQLModule.equals+id.ToText, fieldkeepattop+SQLModule.kNot+SQLModule.null+SQLModule.kAnd+ _
fieldkeepattop+SQLModule.kNot+SQLModule.Like+SQLModule.kfalse.Singlequoted+SQLModule.kAnd+fieldkeepattop+SQLModule.equalsNot+SQLModule.zero+SQLModule.DESC+kComma+fieldErstellDatum, false)

ContactRS wird also einfach, wie gesagt, der Datensatz des Kontaktes mit der dem Constructor übergebenen ID, und NotizRS ein nach Datum und einem Flag „KeepAtTop“ geordneter Recordset der Notizen zu diesem Kontakt.

RecordPosition können Sie ignorieren; das ist noch ein Relikt aus Test-Tagen.

Beendet werden soll der Report, wenn ich alle Notizen ausgewertet habe. Ergo

Public Function EOF() as Boolean
 // Part of the Reports.Dataset interface.
  return notizrs.eof
End Function

Ausgegeben werden in meinem Fall grundsätzlich Strings bzw. (Sie wissen ja, meine Vorliebe fürs Xojo-Framework), Datentypen der Art Text:

Public Function Type(fieldName as string) as integer
 // Part of the Reports.Dataset interface.
 return 5
End Function

Die Methode Field(idx As Integer) kann komplett leer bleiben. Ich benutze ja keine Listbox zur Ansteuerung des Reports, deshalb erfolgen alle Report-Abfragen über den Feldnamen.

Der ganze Zauber findet in der Methode Field(Name As String) statt. Diese beginnt, wie nich anders zu erwarten, mit

// Part of the Reports.Dataset interface.

Select case name

und einige der Cases greife ich hier mal exemplarisch auf:

case "RepFullname"
 return trim(AnredeArtForID (contactrs.Field(FieldAnredeLink).Int64Value)+ _
 " "+ContactRS.Field(fieldVorname).StringValue+" "+ _ 
 ContactRS.Field(FieldNachname).StringValue)

Es gibt in der Datenbank kein Feld für den Namen mit Anrede. Stattdessen besitzt ContactRS einen Int64 für die Anredeart (Herr, Frau etc.), und eine Funktion AnredeArtForID liefert mir die entsprechende Anrede. Vorname und Nachname sind allerdings Bestandteil von ContactRS – ich bastele mir für dieses Feld einfach eine vollständige Anrede zusammen – im Beispielfall „Herr Ulrich Bogun“.

case "RepStraße"
 return contactrs.Field(FieldStraße).StringValue

Einfache Felder wie Bestandteile der Adresse geben direkt dieses Recordset-Field heraus (auf Kundenwunsch gibt es nur eine Adresse, nicht mehrere mögliche).

Bei den Telefonnummern und Mail-Adressen erzeuge ich mir eben schnell den Datensatz aus den entsprechenden Tabellen – hier mal zum Telefon:

case "RepTel1", "RepTel2", "RepTel3"
 dim rs as recordset = MainDB.SelectAllWhereOrdered _
 (TabTelefon, FieldPersonlink+SQLModule.equals+ _
 ContactRS.Field(FieldID).StringValue.ToText, FieldHauptnummer, false)
 if rs.eof then return ""
 if name <> "RepTel1" then 
  if rs.RecordCount > 1 then
   rs.MoveNext
  else
   return ""
  End if
 end if
 if name = "RepTel3" then 
  if rs.RecordCount > 2 then
   rs.MoveNext
  else
   return ""
  End if
 end if
 return rs.Field(FieldTelefon).StringValue+" ("+ _
  Telefonartforid(rs.Field(FieldTelefonArtLink).Int64Value)+")"+ _
  if (rs.Field(FieldHauptnummer).BooleanValue, " (Hauptnr.)", "")

Das ginge wahrscheinlich noch eleganter, aber es funktioniert für meine Ansprüche fix genug: Egal ob Feld Tel1, 2 oder 3 abgefragt wird: Zunächst erzeuge ich aus der Tabelle TabTelefon den entsprechenden Recordset. Soll der zweite oder dritte Telefoneintrag ausgegeben werden, blättere ich, so möglich, eben ein oder zwei Mal weiter. Geht das nicht, weil so viele Datensätze gar nicht drin sind, gebe ich einen leeren String aus. Im Erfolgsfall – der letzte Code-Abschnitt, wird wieder ein String aus dem Datensatz mit der Telefonier extrahiert, an den ich noch den Vermerk der Telefonart (privat, geschäftlich, …) anhänge sowie die Information, ob dies der Hauptanschluss ist, also der, unter dem der Kontakt bevorzugt erreicht werden soll.

Körperarbeit

Im Body werden ja die Notizen ausgegeben. In meinem Fall ist es so, dass es verschiedene Notiztypen gibt und ich gar nicht jeden ausgeben will. Tun wir, um Sie nich weiter mit meinen Ausertungsmethoden zu verwirren, aber mal so, als ob das nicht der Fall wäre. Dann sähe alles so aus:

case "Repdate"
dim notizdate as date = notizrs.Field(fieldErstellDatum).DateValue
 return notizdate.ShortDate

fürs Datum und

case "RepInfo"
Return notizartrs.Field(FieldArt).StringValue.ToText

für die eigentliche Notiz.

Bleibt nur noch

Public Function NextRecord() as Boolean
 // Part of the Reports.Dataset interface.
 NotizRS.MoveNext
End Function

Auch hier ist in realitas die Auswertung komplexer, aber ich habe es nicht als nötig gefunden, einen Ergebniswert zurückzuliefern. Die Abfrage des Reports liefert durch die EOF-Methode (siehe oben) diesen ja schon zuverlässig.

In diesem Sinn: Gutes Reporten auch ohne teure Report-Tools! (Wobei diese freilich viel mehr Komfort ermöglichen und ihr Geld durchaus wert sind. In vielen Fällen, wie Sie sehen, braucht man das aber gar nicht zu investieren. Und kann die Ressourcen besser in Verschönerung des Reports stecken.

Schreiben Sie mir gerne, wenn noch Unklarheiten bestehen.

Noch ein kleiner Hinweis

Der Sinn der Punktmatrix im Reports-Editor erschließt sich mir nicht. Die Felder rasten nicht darauf ein – man muss doch immer wieder den Inspector verwenden, um Felder genau auszurichten.

 

 

 

 

 

 

 

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s