Erstellung professioneller Bildschirmmasken

Worum geht es?

Angenommen, Sie möchten für eine Datenbankanwendung eine Bildschirmmaske für die Dateneingabe programmieren. Der einfachste Ansatz dafür wäre

' Dateneingabe für eine Adresse
LINE INPUT "Name", n$
LINE INPUT "Strasse", a$
LINE INPUT "PLZ/Wohnort"; w$

Doch LINE INPUT hat einige Nachteile:

Aus all diesen Gründen möchte ich Ihnen hier einige Ideen für eine professionellere Eingabe aufzeigen.

Ersatz für INPUT

Dazu eignet sich vorzüglich ein SUB-Unterprogramm mit grob folgendem Aufbau:

SUB EditiereZeile (t$, p%, Ins%, Verl%)
  ' t$ = zu bearbeitender Text (Stringlänge = Feldgrösse)
  ' p% = Aktuelle Cursorposition (0=Anfang)
  ' Ins% = Flag für Einfüge- (-1)/Überschreibmodus (0)
  ' Verl% = Rückgabewert:
  ' 1 = Return/Enter
  ' 2 = Strg + Return
  ' 3 = Tab
  ' 4 = Umschalt + Tab
  ' 5 = Pfeil hoch
  ' 6 = Pfeil unten
  ' 7 = Strg + Pos1
  ' 8 = Strg + Ende
  ' 9 = Esc
  ' 10..19 = <F1>..<F10>

  ' aktuelle Cursorposition sichern
  x% = POS(0)
  y% = CSRLIN
  IF p% >= LEN(t$) THEN
    p% = LEN(t$) - 1
  END IF
  Verl% = 0
  WHILE Verl% = 0
    ' Feld ausgeben
    LOCATE y%, x%
    PRINT t$;
    ' Cursor darstellen
    LOCATE y%, x% + p%, 1, 6 + 4 * Ins%, 7
    DO
      k$ = INKEY$
    LOOP WHILE k$ = ""
    LOCATE , , 0   ' Cursorblinken aus
    SELECT CASE k$
    CASE " " TO "~", CHR$(128) TO CHR$(255)
      ' normales Zeichen
      IF Ins% THEN
        t$ = LEFT$(t$, p%) + k$ + MID$(t$, p% + 1, LEN(t$) - p% - 1)
      ELSE
        MID$(t$, p% + 1) = k$
      END IF
      IF p% = LEN(t$) - 1 THEN
        BEEP   ' Warnung, Feldende erreicht
      ELSE
        p% = p% + 1   ' Cursor eins vorwärts
      END IF
    CASE CHR$(0) + "K" ' Pfeil links
      IF p% = 0 THEN   ' Cursor am Feldanfang?
        BEEP
      ELSE
         p% = p% - 1
      END IF
    CASE CHR$(0) + "M" ' Pfeil rechts
      IF p% = LEN(t$) - 1 THEN  ' bereits ganz rechts?
        BEEP
      ELSE
        p% = p% + 1
      END IF
    CASE CHR$(0) + "G"   ' Pos1
      p% = 0
    CASE CHR$(0) + "O"   ' Ende
      p% = LEN(RTRIM$(t$))
    CASE CHR$(0) + "R"   ' Einfügen
      Ins% = NOT Ins%    ' Wechseln zwischen Einfügen und Überschreiben
    CASE CHR$(0) + "S"   ' <Lösch>-Taste (Del)
      t$ = LEFT$(t$, p%) + MID$(t$, p% + 2) + " "
    CASE CHR$(8)         ' <Rück>-Taste (Backspace)
      IF p% = 0 THEN   ' Bereits am Anfang?
        BEEP
      ELSE
        t$ = LEFT$(t$, p% - 1) + MID$(t$, p% + 1) + " "
        p% = p% - 1
      END IF
    CASE CHR$(13)
      Verl% = 1
    CASE CHR$(10)
      Verl% = 2
    CASE CHR$(9)            ' Tab
      Verl% = 3
    CASE CHR$(0) + CHR$(15) ' Umschalt + Tab
      Verl% = 4
    CASE CHR$(0) + "H"      ' Pfeil hoch
      Verl% = 5
    CASE CHR$(0) + "P"      ' Pfeil unten
      Verl% = 6
    CASE CHR$(0) + "w"      ' Strg + Pos1
      Verl% = 7
    CASE CHR$(0) + "u"      ' Strg+Ende
      Verl% = 8
    CASE CHR$(27)
      Verl% = 9
    CASE CHR$(0) + ";" TO CHR$(0) + "D"  ' Funktionstaste <F1> - <F10>
      Verl% = ASC(RIGHT$(k$, 1)) - 49
    CASE ELSE            ' übrige Tasten
      BEEP
    END SELECT
  WEND
END SUB

Im Hauptprogramm sollten Sie mit Vorteil Feldvariablen zur Maskenbeschreibung verwenden. Dadurch wird es extrem einfach, die einzelnen Felder für die Navigation zu verknüpfen, ohne dass Sie Code für jedes Feld wiederholen müssen, denn wie unter häufige Fehler von Anfänger beschrieben sollten Sie einen Copy&Paste-Programmierstil vermeiden. Als Beispiel verwende ich eine Adressmaske:

' Adressmaske

DECLARE SUB EditiereZeile (t$, p%, Ins%, Verl%)

CONST nFelder% = 6

DIM cx%(1 TO nFelder%), cy%(1 TO nFelder%), fld$(1 TO nFelder%)

FOR i% = 1 TO nFelder%
  READ cx%(i%), cy%(i%), l%
  fld$(i%) = SPACE$(l%)
NEXT i%

DATA 6, 1, 20, 35, 1, 20
DATA 9, 2, 30
DATA 5, 3, 4, 14, 3, 14
DATA 6, 5, 20

COLOR 7, 0
CLS
PRINT "Name                      Vorname"
PRINT "Adresse"
PRINT "PLZ      Ort"
PRINT
PRINT "Tel."
PRINT
COLOR 15, 1
PRINT " F10 ";
COLOR 7, 0
PRINT " = Speichern & Ende  ";
COLOR 15, 1
PRINT " Esc ";
COLOR 7, 0
PRINT " = Abbruch"
COLOR 1, 7
FOR i% = 1 TO nFelder%
  LOCATE cy%(i%), cx%(i%)
  PRINT fld$(i%);
NEXT i%

Ins% = 0   ' Zu Beginn Überschreibmodus
p% = 0     ' Cursor am Anfang
AktF% = 1  ' aktuelles Feld
DO
  LOCATE cy%(AktF%), cx%(AktF%)
  EditiereZeile fld$(AktF%), p%, Ins%, rc%
  SELECT CASE rc%
  CASE 1
    AktF% = AktF% MOD nFelder% + 1
    p% = 0
  CASE 2
    AktF% = (AktF% + nFelder% - 2) MOD nFelder% + 1
    p% = 0
  CASE 3
    AktF% = AktF% MOD nFelder% + 1
    p% = LEN(RTRIM$(fld$(AktF%)))
  CASE 4
    AktF% = (AktF% + nFelder% - 2) MOD nFelder% + 1
    p% = LEN(RTRIM$(fld$(AktF%)))
  CASE 5
    AktF% = (AktF% + nFelder% - 2) MOD nFelder% + 1
  CASE 6
    AktF% = AktF% MOD nFelder% + 1
  CASE 7
    AktF% = 1
    p% = 0
  CASE 8
    AktF% = nFelder%
    p% = LEN(RTRIM$(fld$(AktF%)))
  CASE 9, 19
    ' Nicht tun
  CASE ELSE
    BEEP
  END SELECT
LOOP UNTIL rc% = 9 OR rc% = 19

COLOR 7, 0
LOCATE 9, 1
IF rc% = 9 THEN
  PRINT "Abgebrochen"
ELSE
  PRINT "Satz gespeichert"
END IF

END

Durch den Einsatz der Feldvariable fld$() zusammen mit cx%() und cy%() für die Cursorplazierung konnte das mehrfache Kopieren des Aufrufcodes vermieden werden, so dass dafür die Auswertung des Tasten-Rückgabecodes rc% ohne weiteres mit allen Spezialfällen ausprogrammiert werden konnte. Sie sehen nun selber, wie man eine mit F1 aufrufbare Online-Hilfefunktion implementieren kann: Weiterer CASE-Block genügt.

Weitere Tasten auswerten

Selbstverständlich können Sie auch das Unterprogramm EditiereZeile nach Belieben ausbauen. Als Beispiel möchten Sie dem Anwender die Möglichkeit geben, mit Alt+hervorgehobener Buchstabe Felder direkt anspringen zu können. Dazu erzeugen Sie zu Beginn eine Tastencode-Liste in Form eines Strings:

DIM SHARED ac$

ac$=""
FOR i% = 1 TO 36
  READ j%
  ac$ = ac$ + CHR$(j%)
NEXT i%
' Buchstaben Alt+A - Alt+Z (nur zweites Byte)
DATA 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37,  38, 50, 49, 24, 25, 16, 19
DATA 31, 20, 22, 47, 17, 45, 21, 44,
' Ziffern Alt+0 - Alt+9 (nur zweites Byte)
DATA 129, 120, 121, 122, 123, 124, 125, 126, 127, 128

Einen solchen Tastendruck können Sie dann innerhalb von EditiereZeile durch Ergänzen des letzten CASE-Blockes wie folgt auswerten:

  ' 9 = Esc
  ' 10..19 = <F1>..<F10>
  ' 20..45 = <Alt>+<A>..<Alt>+<Z>
  ' 46..55 = <Alt>+<0>..<Alt>+<9>

  ' aktuelle Cursorposition sichern
  ' ...
      ' ... (unverändert)
    CASE CHR$(0) + ";" TO CHR$(0) + "D"  ' Funktionstaste <F1> - <F10>
      Verl% = ASC(RIGHT$(k$, 1)) - 49
    CASE ELSE            ' übrige Tasten
      IF LEN(k$) = 2 AND LEFT$(k$, 1) = CHR$(0) THEN
        h% = INSTR(ac$, RIGHT$(k$, 1))
        IF h% > 0% THEN
          Verl% = 19 + h%
        ELSE
          BEEP
        END IF
      ELSE
        BEEP
      END IF
    END SELECT
  WEND
END SUB

Die Auswertung erfolgt am einfachsten im CASE ELSE-Block durch eine Suche im vorhin definierten Tabellenstring.

Ganze Formulare

Vielleicht möchten Sie ausser dem Texteingabefeld noch Radioknöpfe für Auswahllisten und Ankreuzboxen für ein- und ausschaltbare Elemente wie auf diesem Beispiel:

Parametereinstellbildschirmmaske. Grösserer Umfang von 9½KB
Beispiel einer Etikettenparametermaske in einer Anwendung

In einem solchen Fall empfiehlt sich die Erstellung von weiteren SUB-Unterprogrammen, also analog zu EditiereZeile auch für die übrigen Elemente jeweils ein solches Unterprogramm:

DECLARE SUB EditiereZeile (t$, p%, Ins%, Verl%)
DECLARE SUB RadioButtonFamilie (cx%(), cy%(), StartInd%, AnzKn%, AktW%, Verl%)
DECLARE SUB CheckBox (cx%, cy%, istEin%, Verl%)
' Gesamte Verarbeitung
DECLARE SUB BildschirmMaske (db%(), gk!(), s$(), StartInd%)

Und das allerwichtigste dabei ist das Unterprogramm BildschirmMaske selber, bei welchem Sie eine Beschreibung von Ihrer Maske (vergleichbar mit einer <FORM ..>-HTML-Datei) übergeben können. Weil QuickBASIC ja keine objektorientiert Sprache ist, bei welcher Sie eine Basisklasse Bedienelement erstellen können und davon TextFeld, CheckBox, Radioknopfgruppe usw. mit Hilfe sog. Vererbung erstellen können, müssen Sie mit Hilfe von Feldvariablen und eigenen Typen arbeiten. Beim obigen, sogar noch in GWBASIC.EXE geschriebenen Programm löste ich dies mit mehreren Maskendatenbeschreibungsvariablen DB%(), GK!() und S$(). Diese Variablen mussten für das Unterprogramm vorbereitet übergeben werden zusammen mit einem Startindex. Die »Sprache« sah dann in etwas so aus: Zuerst holte man sich das erste (Startindex!) Element aus DB%() und fand dann einen Bedienelementcode vor:

CodeBedeutung
0Textfeld freier String
1Textfeld für Ganzzahl
2Textfeld für Gleitkommazahl
16Radio-Auswahlmenü horizontal
17Checkbox-Auswahlmenü horizontal
32Radio-Auswahlmenü vertikal
33Checkbox-Auswahlmenü vertikal
-1Ende des Maskensystems
-2neue Seite, auf die mit BildAuf/BildAb geblättert werden kann

In der Implementation von BildschirmMaske gehört eine entsprechende WHILE db%(AktCod%) <> -2-Schleife hinein zusammen mit einem SELECT CASE-Block für die obige Tabelle. In db%() folgt nach diesem Code jeweils eine Beschreibung der zusätzlichen Daten. Als Beispiele nehme ich ein Textfeld für Ganzzahlen:

Beschreibung Ganzzahl-Textfeld
VariableBedeutung
db%(AktCod%)Kennung 1
db%(AktCod%+1)Zeile für LOCATE
db%(AktCod%+2)Spalte für LOCATE Beginn Eingabetextfeld
db%(AktCod%+3)Index auf s$() mit Prompttext
s$(db%(AktCod%+3))Prompttext, ASCII-Wert vom 1. Zeichen stellt die Position des hervorgehobenen Buchstabens dar: A=1, B=2 usw.
s$(db%(AktCod%+3)+1)Puffer mit Eingabewert als t$ dem Unterprogramm EditiereZeile zu übergeben
db%(AktCod%+4)kleinster erlaubter Wert für die Eingabe
db%(AktCod%+5)grösster erlaubter Wert für die Eingabe
db%(AktCod%+6)vom Benutzer effektiv eingegebener Wert

Beispiel für ein Radioknopf-Menü:

Beschreibung vertikales Radio-Menü
VariableBedeutung
db%(AktCod%)Kennung 32
db%(AktCod%+1)Zeile für LOCATE
db%(AktCod%+2)Spalte für LOCATE, Lage des Häkchen
db%(AktCod%+3)Index auf s%() mit Prompttext
s$(db%(AktCod%+3))Prompttext, z.B. »Papierausrichtung«
s$(db%(AktCod%+3)+1) .. s%(db%(AktCod%+3)+n) Liste der Texte, z.B. »Hochformat«, »Querformat«, ASCII-Wert vom 1. Zeichen stellt die Position des hervorgehobenen Buchstabens dar
db%(AktCod%+4Anzahl Optionen n
db%(AktCod%+5aktuelle Option (0..n-1)

Auf den ersten Blick sieht es recht kompliziert aus, aber in Wirklichkeit geht die Ausprogrammierung eines BildschirmMaske-Unterprogramms recht einfach und effizient, so dass Sie am Schluss mit einem wirklich sehr flexiblen und leistungsfähigen Software-Baustein für Ihre Projekte belohnt werden :-). Wichtig ist dabei am Schluss die Erhöhung von AktCod% innerhalb von jedem CASE-Block, da dort in db%() die Beschreibung des nächsten Bedienelementes folgt.

Aber auch das Hauptprogramm wird insgesamt betrachtet, recht einfach: Nachdem Sie Ihr Menü mit Papier und Bleistift entworfen haben, können Sie es in Form von DATA-Zeilen gemäss obigen Spezifikationen beschreiben und eingeben. Somit brauchen Sie lediglich die Felder db%(), gk!() und s$() zu dimensionieren und mit einer READ-Schleife zu Beginn laden.


Wieder zurück zur Übersicht


© 2000 by Andreas Meile