Lösungsskizze zu Libraries 4: Das Buchungsterminal


Ein paar Screenshots

Maske für Kundensuche

Das ist die Maske unmittelbar nach Start.

Eingabe eines Namensanfangs

Mit ENTER kann man ein Feld "betreten" und beschreiben. Ein zweites ENTER befördert ein wieder raus. Mit den Up/Down-Cursortasten bewegt man sich von Feld zu Feld.

Kunden-Suche

Mit Eingabe von CTRL-F zur Suche:

In dieser Version werden zu Anschauungszwecken (und bei der Entwicklung zu Testzwecken) die SQL's immer ausgegeben.

...und gefunden

Kunden werden angezeigt

Mit Bildauf-/Bildab-Taste wird durch die verschiedenen gefunden Kunden gescrollt.

Und zu den Buchungen...

Nach Abschicken dieses SQL's dauert's ein Weilchen...

...die auch gefunden werden

Anzeige der gefundenen Buchungen

Gleiches Prinzip: Mit Bildauf-/Bildab-Taste wird durch die verschiedenen gefunden Buchungen gescrollt.

Ändere Flugziel der Buchung: Wähle Flugziel aus

SQL dazu...

Untermenü: Wir wählen einen Flug aus

Ausgewählt: Andere Felder wie Uhrzeit, Wochentag, Flugzeugtyp wurden automatisch befüllt.

Esc-Taste - Wollen wir die Buchung speichern?

Mit einem Update-SQL...

Prüfung, wieviel Sitze im Flieger eigentlich gebucht sind.

Anschliessend landet man wieder in der Kundenmaske und kann sich ggf. weiteren Buchungen zuwenden.

Natürlich wurden noch einige notwendige Funktionen nicht implementiert. Alles was mit Preisen und Bezahlen zu tun hat, es können Buchungen noch nicht storniert werden usw. Aber es soll ja ein Übungsbeispiel bleiben...

Der Aufbau des Programms

Die Klasse tmask

Wenn man faul ist, dann versucht man, einmal geschriebenen Code für möglichst viele Zwecke zu "missbrauchen". Das ist der Grundgedanke der objektorientierten Programmierung. In diesem Fall benutze ich eine Klasse tmask für so ziemlich alles, was an Eingaben im Programm gemacht werden muss – für die Anzeige der Fundstellen, für die Eingabe der Daten und für die Darstellung der Untermenüs.

type tmask

  DATA1(20) AS STRING
  labels(20) AS STRING
  protect(20) AS INTEGER
  smenuflag(20) AS STRING
  id(20) AS STRING

  xrow(20) AS INTEGER
  xcol(20) AS INTEGER

  fieldlength(20) AS INTEGER
  title AS STRING
  NAME AS STRING

  current_pos AS INTEGER
  old_cpos AS INTEGER
  help_bar AS STRING
  book AS tbookf PTR
  submenumode AS INTEGER
  submenu_decided AS INTEGER

  'Initialisiere die Maske mit expliziteren Daten
  DECLARE SUB init(initdata() AS STRING, labels1() AS STRING, title1 AS string)
  'Initialisiere die Maske mit Lables, aber leeren Datenfeldern
  DECLARE SUB initempty(lables1() AS STRING, title1 AS STRING, NAME1 AS STRING, book1 AS tbookf PTR = NULL)
  'Initialisiere die Maske mit Inhalt von resulttab (das die letzten DB-Suchergebnisse enthaelt)
  'Diese Version nur aus tsearchcustomer heraus
  DECLARE SUB init_sc(resulttab AS tresulttab, irow AS integer)
  'Initialisiere die Maske mit Inhalt von resulttab (das die letzten DB-Suchergebnisse enthaelt)
  'Diese Version nur aus tbook heraus
  DECLARE SUB init_b(resulttab AS tresulttab, irow AS INTEGER, customerid AS integer)
  DECLARE CONSTRUCTOR
  DECLARE FUNCTION main AS INTEGER
  DECLARE SUB display

  DECLARE SUB INPUT1(ipos AS integer)
  DECLARE SUB input_book(ipos AS integer)
  DECLARE SUB set_cursor(ipos AS integer)

END type

tmask geht von einer zweispaltigen Eingabemaske mit bis zu 20 Eingabegrössen, eingeteilt in zwei Spalten bis zu 10 Elementen aus. Jedes Element besteht aus einer Beschreibung (Array label()), aus den eingebenen oder ausgegebenen Daten (Array data1()), aus einem weiteren Array, das ggf. die Datenbank-id's zu den Elementen im Hintergrund speichert (id()) und einem Array, das anzeigt, ob Elemente vor Eingaben geschützt sind. (protect()). Bei Anzeigen von Auswahlmenüs sind z.B. alle Elemente bei protect() auf eins gesetzt – es ist keine Modifikation möglich – was auch unsinnig wäre, denn in diesem Fall werden Eingabedaten data1() gar nicht angezeigt.

In xrow() und xcol() wird das Layout festgelegt. In name wird der Maske ein Name gegeben, anhand dem sie selbst jederzeit feststellen kann, in welchem Kontext sie grade erzeugt wurde.

Die Maske muss mit initempty() initialisiert werden. Da die Eingaben von Buchungen komplizierter und sehr kontextabhängig sind (Prüfung buchungsspezifischer Zusammenhänge), liegt es nahe, Call-backs zurück in die aufrufende Buchungsklasse (tbook) zu ermöglichen – daher die optionale Übergabe des aufrufenden Zeigers. Damit können dann die ganzen Bearbeitungen in der Klasse vorgenommen werden, wo sie auch hingehören.

Ist tmask einmal mit einem tbook-Zeiger initialisiert, bleibt diese Initialisierung erhalten. Darum müssen wir uns nicht mehr kümmern. Anschliessend können spezifische Initialisierungen der Maske mit Inhalten via init_sc(), bzw. init_b() vorgenommen werden. Dies geschieht von den main-Routinen der beiden Klassen aus, die die Hauptfunktionen "Flugbuchung" und "Kundenpflege" bereitstellen, tbook und tsearchcustomer.

display ist eine sehr schlichte und schnelle Methode, die die Maske zeichnet. Sie wird oft aufgerufen – wann immer der Verdacht besteht, dass sich an den Inhalten etwa geändert hat. Sie ist übrigens der Anknüpfungspunkt, um weniger schlichte Masken darzustellen, dem Buchungsterminal sozuagen seinen neuen "Skin" zu verpassen.

Die Eingabeverarbeitung der Klasse tmask

Diese hat es in sich, denn je nach Kontext müssen etwas kompliziertere Regeln verfolgt werden. Bei den Kunden ist noch alles sehr einfach. Die Hauptroutine main() lauscht auf Tastendrücke, sortiert die Steuertasten aus, und springt bei Betätigen der ENTER-Taste die input1()-Methode an. Diese prüft zunächst protect() und mündet, falls durchgelassen, in einem Standard-Basic-Input, eingehüllt in eine Fehlerschleife, welche zu lange Eingaben abfängt. Es gibt allerdings zwei Ausnahmen. Die eine ist, dass der Input im book-Kontext stattfindet. Dann wird das Szepter an book_input() übergeben. Oder tmask befindet sich im submenu-Mode, d.h. führt grade die Umsetzung eines Untermenüs durch. Dann passiert fast nichts. Die ENTER-Taste hat ja schon die Auswahl des Items signalisiert.

input_book()

Wie sieht es im book-Kontext aus? Hier wird stark nach dem Element unterschieden. Element 0 ist das Datum und wird als normaler String eingelesen, allerdings dann auf Datumsformat geprüft. Ansonsten handelt es sich nur um Elemente mit Untermenü. Um welches Untermenü, das wird in smenuflag() mit einem Schlüsselwort gekennzeichnet. Der Start geschieht mit call_sub_menu(), welchem das Schlüsselwort und ein Zeiger auf sich selbst, also die aktuelle Book-Maske mitgegeben wird. Das ist sehr wichtig, denn die Befüllung des einen Masken-Elements erfordert Interaktionen mit den anderen Elementen.

call_sub_menu()

Hier befinden wir uns in der book-Klasse, denn nun geschehen Dinge, die sehr flugbuchungsspezifisch sind. (An sich hätte auch schon input_book innerhalb von tbook platziert werden können – das ist eine Geschmacksfrage.) Hier wird eine weitere Maske für das Submenü deklariert. Dann werden die einzelnen möglichen Submenüs abgearbeitet: "Origin", "Destination" und "Class". Ziel ist eine Tabelle, deren Inhalt die Auswahlpunkte des Submenüs sind. Die bekommen wir mit einem passenden SQL. Nachdem das gebildet wurde, wird es innerhalb einer Methode fill_smenu abgefeuert. Dort wird dann eine Stringdatenstruktur resulttab, die im Programm immer als Ergebnisspeicher von SQL's benutzt wird, in Labels und ID's der Submenü-Maske (auch Klasse tmask) umgeschüttet. Damit können wir das Submenü initialisieren und starten. Rückgegeben wird in diesem Fall der String und die ID, die der Position des ausgewählten Elements entspricht – so wurde tmask für den submenumode angelegt.

Wir sehen also: Es wird praktisch alles mit tmask abgedeckt. Eleganter als die verschiedenen Modes und der Rückgriff auf Kontextbedingungen wäre es gewesen, ein paar weitere Klassen zu definieren, die aus der Basis-tmask-Klasse abgeleitet ist, d.h. eben ein paar zusätzliche oder modifzierte Methoden besitzt. Das nennt man dann "Vererbung", aber das kann Freebasic zur Zeit (Ver. 0.20b) noch nicht.

Hier der komplette Quelltext (45K)