List&Label: Kreuz- und Verbundtabellen

Moderator: Moderatoren

Antworten
Benutzeravatar
Tom
Der Entwickler von "Deep Thought"
Der Entwickler von "Deep Thought"
Beiträge: 9345
Registriert: Do, 22. Sep 2005 23:11
Wohnort: Berlin
Hat sich bedankt: 100 Mal
Danksagung erhalten: 359 Mal
Kontaktdaten:

List&Label: Kreuz- und Verbundtabellen

Beitrag von Tom »

Seit Version 11, wenn ich mich recht erinnere, bietet List&Label die Möglichkeit, mit Kreuz- und Verbundtabellen zu arbeiten. Damit sind zwei (eigentlich drei) Fälle gemeint: Tabellen, die völlig unabhängig voneinander in einem Formular folgen (und zwar beliebig viele) und Tabellen, die andere Tabellen als "Kinder" haben. Der dritte Fall sind Charts, die aber analog arbeiten, da Charts aus L&L-Sicht nichts anderes sind als Tabellen, die anders dargestellt werden.

Ein Beispiel für Fall 1 wären zum Beispiel zwei Listen mit allen Aufträgen und allen Angeboten, die je für einen Kunden erzeugt wurden, und zwar innerhalb einer Liste. Erst kommt die Liste mit den Aufträgen, dann die mit den Angeboten. Ein Beispiel für Fall 2 wäre eine Liste mit allen Aufträgen, die je Auftrag eine Tabelle mit allen Positionen enthält. Das hat man mit L&L bisher zwar auch hinbekommen, indem man in der Druckschleife Bedingungen (als Variable) gesetzt und dann wechselweise Auftragsdaten und -positionen übergeben hat, die jeweils innerhalb einer Tabelle in verschiedenen Datenzeilen saßen und auf die Darstellungsbedingungen reagierten, aber mit mehreren Tabellen geht das viel einfacher und auch eleganter. Und die Kunden verstehen es besser! Der gewaltige L&L-Designer lässt zwar vieles zu, aber auch bei uns im Haus sind schon Formulare entstanden, die zwar prächtig aussehen, aber kaum mehr zu durchschauen sind.

Ich habe mich jetzt aus aktuellem Anlass mit der Systematik beschäftigt, nachdem ich das lange vor mir hergeschoben habe. Dabei ist es total einfach. Und so geht es:

1. Schritt:

Üblicherweise haben List-Projekte eine Datenquelle, die alles mögliche sein kann, zum Beispiel ein Konglomerat aus Tabellendaten oder Arrays oder was auch immer. Bei Kreuz- und Verbundtabellen meldet man alle Tabellen an, die es im Projekt geben soll. Dabei ist wieder völlig wurscht, ob das echte Tabellen sind oder auch nur Arrays oder sonstwas. Das geschieht nach der Eröffnung des Jobs und dem Setzen der Optionen. Hier melde ich zwei Tabellen an, nämlich Angebote und Aufträge (Hinweis: Ich arbeite mit dem OEM-Zeichensatz, deshalb steht da oft ConvToAnsiCP()):

Code: Alles auswählen

LlDbAddTable(hJob,'','') // setzt alle Tabellen zurück
LlDbAddTable(hJob,ConvToAnsiCP('Auftraege'),'')
LlDbAddTable(hJob,ConvToAnsiCP('Angebote'),'')
LlDbAddTable kennt noch einen dritten Parameter (hier: Leerstring), den man für den im Designer angezeigten Namen der Tabelle nutzen kann. Ist dieser nicht gesetzt, wird die ID ("Auftraege", "Angebote") genutzt.

2. Schritt:

Vor dem Aufruf des Designers müssen für alle Tabellen alle Felder bekannt sein. Das geschieht ganz normal mit LlDefineField() bzw. LlDefineFieldExt(). Der Einfachheit halber und weil man im Formular ohnehin alles mit Daten machen kann, was man will, übergebe ich hier alle Daten als Strings. Für den Designer befülle ich die Felder mit ihren Namen:

Code: Alles auswählen

LlDefineFieldStart(hJob) // einmalig
DbSelectArea('Auftraege')
For i := 1 to Fcount()
    LlDefineFieldExt(hJob,'Auftraege.'+fieldname(i),fieldname(i),LL_TEXT,0)
Next
Das wird für alle Tabellen wiederholt (hier für zwei). Danach kennt L&L alle Tabellen und alle Felder.

3. Schritt:

Die Druckroutine muss jetzt leicht abgewandelt werden, weil das Formular entscheidet (!), welche Daten gerade benötigt werden. Das erlaubt es, die Tabellen im Formular beliebig anzuordnen, also erst Aufträge und dann Angebote oder umgekehrt. Ich nehme einfach mal an, dass in beiden Tabellen der Zeiger auf dem ersten Datensatz steht und dass z.B. ein Scope gesetzt ist - auf Kundennummer.

Code: Alles auswählen

lPrint := .T.
Do While lPrint
    cTableId := space(30)
    LlPrintDbGetCurrentTable(hJob, @cTableId,30,.F.)
    cTableId := trim(strtran(cTableId,chr(0),'')) // ACHTUNG: 0-terminierter String!
    Do Case
      Case cTableId = 'Auftraege'
      DbSelectArea('Auftraege')
      Do While !Eof()
        * jetzt wie oben (leere Daten) verfahren, aber natürlich mit Daten
       FelderDrucken(hJob) // siehe unten
       DbSkip(1)
     EndDo
     * Angebote
   EndCase
    nRet:=LlPrintFieldsEnd(hJob)
    Do While nRet=LL_WRN_REPEAT_DATA
      nRet:=LlPrintFieldsEnd(hJob)
    Enddo

    Ff !(nRet = LL_WRN_TABLECHANGE .or. nRet=LL_WRN_REPEAT_DATA)
      lPrint := .F.
    Endif
  Enddo
  * Druck abschließen, wie gehabt
Das ist auch eigentlich schon alles. Statt einfach alle Felder zu publizieren, fragt man über LlPrintDbGetCurrentTable() ab, welche Tabelle gerade aktuell ist, und dann publiziert man die Daten aus dieser Tabelle. Wenn diese nichts enthält, geschieht auch nichts, es ist also egal, ob der Kunde Angebote hatte oder nicht. Ob noch Daten für weitere Tabellen zu drucken sind, gibt der Rückgabecode LL_WRN_TABLECHANGE an, den man deshalb auch bequem zum Terminieren der Druckroutine benutzen kann.
Die kleine Prozedur "FelderDrucken(hJob)" löst den Druck der jeweiligen Zeile aus, wie auch im normalen Druck:

Code: Alles auswählen

Procedure FelderDrucken(hJob)
nRet:=LlPrintFields(hJob)
Do While nRet=LL_WRN_REPEAT_DATA
  LlPrint(hJob)
  nRet := LlPrintFields(hJob)
Enddo
RETURN
4. Schritt:

Im Formular steht jetzt eine neue Auswahl "Berichtscontainer" zur Verfügung. In diesem wählt man alle Tabellen, die gedruckt werden sollen, wobei ein Assistent bei der Auswahl der Felder hilft. Hier wird auch die Anordnung der Tabellen gewählt. Sie liegen im Designer "übereinander", werden aber in der gewählten Reihenfolge gedruckt. Man hat fantastische Layoutmöglichkeiten, und ich habe schon ziemlich geile Formulare damit gebaut. Wer Fragen dazu hat - feel free!
Herzlich,
Tom
Antworten