14. Arbeit mit der Tastatur


Zur Entgegennahme von Nutzereingaben über die Tastatur stehen dem R-BASIC Programmierer drei Möglichkeiten zur Verfügung:
  1. Im Klassischen BASIC Mode wird die Tastatur direkt abgefragt. Dazu stehen z.B. die Befehle INPUT, Inkey$ und GetKey zur Verfügung. Unter einem objektorientierten System wie GEOS sollten Sie diesen Weg vermeiden. Insbesondere die ständige Abfrage der Tastatur in einer Schleife bremst das System massiv aus und erhöht die CPU-Last.

  2. Der einfachste Weg ist die Verwendung von Textobjekten (Memo und InputLine). Diese Objekte können intelligent mit Tastatur und Maus umgehen und reichen für viele Zwecke völlig aus.

  3. Der universellste Weg ist das Schreiben eines Tastaturhandlers. Tastaturhandler werden automatisch gerufen wenn der Nutzer eine Taste drückt. Sie können die gerückte Taste mitlesen und entscheiden, ob Sie sie selbst behandeln oder einfach weitergeben wollen. Dieser Weg wird z.B. benutzt um ein Spielprogramm zu schreiben, dass mit der Tastatur gesteuert wird.
Dieses Kapitel widmet sich dem Schreiben von Tastaturhandlern, welche Möglichkeiten sich daraus ergeben und was es zu beachten gilt.

^

14.1 Überblick über Tastaturereignisse

Um mit den Tastaturhandlern arbeiten zu können sollte man mindestens in Grundzügen verstanden haben, wie GEOS mit Tastaturereignissen umgeht. Im Folgenden wir das Prinzip erklärt, für vollständige Informationen muss auf weiterführende Literatur verweisen werden.

Es gibt genau drei Situationen, in denen ein Tastaturereignis erzeugt wird:

  1. Der Nutzer drückt eine Taste nieder. Dieses Ereignis heißt FIRST_PRESS.

  2. Der Nutzer hält die Taste länger gedrückt. Dieses Ereignis heißt REPEAT_PRESS. Es wird immer wieder gesendet, solange der Nutzer die Taste unten hält. Die Häufigkeit (Tastatur-Wiederholungsrate) kann in den Voreinstellungen ausgewählt werden.

  3. Der Nutzer lässt die Taste wieder los. Dieses Ereignis heißt RELEASE.
Bei jedem dieser Tastaturereignisse passiert intern folgendes:
  1. Die Tastatur sendet die Nummer der Taste, den sogenannten Scancode, an den Computer. Dieser Scancode ist unabhängig vom eingestellten Tastaturlayout. Im Kapitel 14.3 finden Sie ein Bild mit den Scancodes einer PC-Tastatur.

  2. Der Tastaturtreiber kennt das Tastaturlayout und wandelt den Scancode in den zugehörigen ASCII-Code (0 ... 255) um. Dabei werden die "Modifier"-Tasten wie Shift oder AltGr bereits berücksichtigt.

    Tasten, denen kein ASCII-Code zugeordnet ist wie F1 oder die Cursortasten, erzeugen einen "erweiterten" ASCII-Code im Bereich von 65280 bis 65535 (hexadezimal &hFF00 bis &hFFFF).

  3. Jetzt erzeugt der Tastaturtreiber ein "Tastaturereignis" für das System und gibt es als Message an das Application-Objekt des aktiven Programms weiter. Diese Message enthält unter anderem den ASCII-Code (das schließt erweiterte ASCII-Codes ein), den Typ des Ereignisses (FIRST_PRESS usw.) und den aktuellen Zustand der Steuertasten (z.B. linke Strg-Taste gedrückt).

  4. Das Application-Objekt leitet das Ereignis an das Objekt weiter, dass den Focus hat.


Anmerkungen zu Punkt 2:

Bei den erweiterten ASCII-Codes sind die höherwertigen 8 Bit immer gesetzt, die unteren 8 Bit enthalten die Information, z.B. den Steuercode der zur gedrückten Taste gehört. Man kann ihn abfragen indem man die oberen 8 Bit mit der AND-Operation ausblendet.
code = character AND 255  ' d.h. AND &hFF
Diese Codes sind oft, aber nicht immer identisch mit den für das PRINT-Kommando verwendeten Steuercodes (siehe Anhang, Kapitel A). Zum Beispiel ist der PRINT Code für "Cursor nach links" 14 (&h0E), der Tastencode ist jedoch 147 (&h93).

In der Library "KeyCodes" finden Sie symbolische Konstanten und die Werte für die erweiterten Tastencodes.

Anmerkungen zu Punkt 3:

Drückt der Nutzer die Tasten Shift + 'a' wird der Buchstabe 'A' erzeugt. Die Shift-Taste ist damit ausgewertet und wird NICHT mehr in das Tastaturereignis aufgenommen.
Es ist nun möglich sich unter R-BASIC an bestimmten Stellen in den Tastaturhandler einzuklinken und Tastaturereignisse zu "überschreiben". Das heißt man kann sie mitlesen, bei Bedarf verhindern dass sie weitergeleitet werden oder auch dem System eigene "Ereignisse" unterschieben. Der folgende Abschnitt beschreibt die Konzepte dahinter.

^

14.2 Schreiben eines Tastaturhandlers

Um sich in das Tastaturhandling einzuklinken muss man einen Tastaturhandler (auch Keyboardhandler) schreiben. Dazu stehen die folgenden Instancevariablen zur Verfügung:

Tabelle

Tabelle

Der OnKeyPressed Handler wird gerufen, wenn das Objekt ein Tastaturereignis erhält. Er muss als KeyboardAction deklariert werden. Die inputFlags bestimmen, in welchen Fällen das Ereignis vom Objekt selbst, vom BASIC Handler oder von beiden behandelt werden soll.

Für die folgenden Objektklassen sind Keyboardhandler und inputFlags definiert:

  • Application

  • Memo und InputLine

  • View

  • VisContent

  • BitmapContent

  • VisObj
Wichtig: Es ist grundsätzlich so, dass zuerst das Objekt das Tastaturereignis behandelt (bzw. weiterleitet) und erst danach der BASIC Handler gerufen wird. Im Abschnitt 14.4 ist beschrieben, wie man trotzdem bestimmte Zeichen herausfiltern kann.

Einige der Objekte (z.B. InputLine und Memo) behandeln das Ereignis per default selbst, andere (z.B. View oder VisContent) leiten es nur an untergeordnete Objekte weiter. Details oder Besonderheiten zum Tastaturhandling der einzelnen Objekte finden Sie in den entsprechenden Kapiteln zu den Objekten.

OnKeyPressed

Der OnKeyPressed Handler wird aufgerufen, wenn das Objekt ein Tastaturereignis erhält. Der Handler muss als KeyboardAction deklariert sein. Der BASIC Handler wird erst gerufen nachdem das Objekt das Ereignis selbst behandelt bzw. an untergeordnete Objekte weitergeleitet hat.
Syntax UI- Code: OnKeyPressed = <Handler>

Schreiben: <obj>.OnKeyPressed = <Handler>


KeyboardAction Handler haben die folgenden Parameter:
sender: Das Objekt, welches den Handler aufgerufen hat

character: ASCII-Code oder erweiterter ASCII-Code der Taste.
Tipp: In der Library "KeyCodes" finden Sie symbolische Konstanten für die erweiterten ASCII-Codes.

keyState: Information, welche Status- oder Steuertasten aktuell gedrückt sind.

keyFlags: Information ob der Nutzer die Taste gerade gedrückt hat,
sie gedrückt hält oder gerade losgelassen hat.

scanCode: Der Scancode der gedrückten Taste.
Für Ereignisse, die mit der Methode KbdEvent erzeugt wurden hat scanCode den Wert Null.


KeyState enthält genau die Informationen, die man auch mit der BASIC Funktion GetKeyState erhalten kann. Der Zugriff auf den Parameter keyState ist jedoch wesentlich schneller. KeyState sind Bitflags. Jedes Bit hat eine eigene Bedeutung. Das niederwertige Byte enthält den "Shift"-Status, das höherwertige Byte (Bitwerte 256 und aufwärts) den "Toggle"- Status.

Folgende Werte bzw. Konstanten sind definiert:

Tabelle

Tabelle

Anmerkungen:

  1. Die Bits werden vom Host-System an GEOS und von dort an den R-BASIC Handler übergeben. Nicht verwendete Bits sind intern verwendet und könnten gesetzt sein oder nicht.

  2. Die Bits für "Modifier"-Tasten wie Shift oder AltGr sind i.A. nicht gesetzt, auch wenn die Tasten gedrückt sind. Sie wurden bei der Erzeugung des ASCII-Codes vom Tastaturtreiber "geschluckt".

  3. Die Toggle-State Bits enthalten die Information, ob der entsprechende Zustand eingerastet ist oder nicht. Auf der Tastatur sollten dann die entsprechenden Leuchtdioden aktiv sein. Erfahrungsgemäß haben verschiedene Hostsysteme und/oder Emulatoren damit aber Probleme, so dass die LED's nicht immer den aktuellen Zustand widerspiegeln. Beispielsweise startet die DosBox unter Windows 7 immer mit dem Zustand "NumLock nicht aktiv", obwohl die LED leuchtet und man muss die Taste zweimal betätigen um den NumLock Zustand zu ändern.
KeyFlags sind Bitflags. Jedes Bit hat eine eigene Bedeutung. Folgende Werte bzw. Konstanten sind definiert.

Tabelle

inputFlags

Mit Hilfe der Instancevariablen inputFlags kann man steuern, ob ein Tastaturereignis vom Objekt selbst, vom BASIC Handler oder von beiden behandelt werden soll.
Syntax UI-Code: inputFlags = bits

bits: Kombination der IF_-Werte laut Tabelle unten

Lesen: <numVar> = <obj>.inputFlags

Schreiben: <obj>.inputFlags = bits


Per default ist inputFlags Null, d.h. Tastaturereignisse werden sowohl vom Objekt als auch vom BASIC-Handler (so einer gesetzt ist) behandelt. Als Faustregel gilt: Setzen Sie nur die Bits, die sie auch wirklich für die Programmfunktion benötigen.


Die folgenden Werte bzw. Konstanten sind definiert:

Tabelle

IF_IGNORE_FIRST_PRESS
IF_IGNORE_REPEAT_PRESS
IF_IGNORE_RELEASE
IF_IGNORE_ANY_KEY
Diese Bits verhindern, dass das Objekt die entsprechenden Ereignisse selbst behandelt bzw. an seine Children / sein Content weiterleitet. Das betrifft jedoch nur "echte" Tastendrücke. Tastaturereignisse, die mit der Methode KbdEvent erzeugt wurden, werden immer vom Objekt behandelt bzw. weitergeleitet.

IF_FILTER_GENERATED_EVENTS
Dieses Bit bewirkt, dass die Bits IF_IGNORE_FIRST_PRESS, IF_IGNORE_REPEAT_PRESS, IF_IGNORE_RELEASE und IF_IGNORE_ANY_KEY auch auf Ereignisse wirken, die mit der Methode KbdEvent erzeugt wurden. Das Bit IF_FILTER_GENERATED_EVENTS wird sehr selten gebraucht.

IF_HANDLER_NO_FIRST_PRESS
IF_HANDLER_NO_REPEAT_PRESS
IF_HANDLER_NO_RELEASE
Diese Bits verhindern, dass der BASIC Handler die entsprechenden Ereignisse behandeln kann.

IF_HANDLER_GENERATED_EVENTS
Per default wird der BASIC Handler für Tastaturereignisse, die mit KbdEvent erzeugt wurden, nicht gerufen. Dieses Bit aktiviert den Handler für solche Ereignisse. Die IF_HANDLER_NO_~ Bits werden dabei berücksichtigt.
Das Bit IF_HANDLER_GENERATED_EVENTS wird sehr selten gebraucht.

IF_DONT_MAP_NUM_PAD
IF_MAPPED_NUM_PAD_EXT_BIT
Aus historischen Gründen senden die Ziffern und Operatorzeichen vom abgesetzten Ziffernblock eigentlich erweiterte Tastencodes. Das erschwert die Auswertung der Tasten jedoch sehr. Deshalb wandelt R-BASIC diese Tastencodes intern in "normale" Codes um, so dass für den BASIC Keyboardhandler kein Unterschied besteht. Das Flag IF_MAPPED_NUM_PAD_STATE_BIT weist R-BASIC an, für diese Tastencodes das Bit KF_EXTENDED zu setzen, so dass Sie diese Tasten wieder von den "normalen" Tasten unterscheiden können. Das Bit IF_DONT_MAP_NUM_PAD schaltet die Umwandlung komplett aus. Damit haben Sie die volle Kontrolle aber auch sehr viel mehr Aufwand.


Beispiel: Die Cursortasten sollen die Spielfigur Willy steuern.

Das Tastaturhandling wird von einem BitmapContent gemacht. Da dieses keinen eigenen Tastaturhandler und keine Children hat benötigen wir kein IF_IGNORE~ Bits. Außerdem wollen wir bei jedem Tastendruck nur genau einen Schritt machen, auch wenn die Taste länger gedrückt ist. Deswegen leiten wir REPEAT_PRESS und RELEASE-Ereignisse nicht an den BASIC Handler weiter. Die SUB's MoveWillyUp usw. müssen natürlich irgendwo definiert sein.

UI-Code

BitmapContent GameBoard
  OnKeyPressed = KeyHandler
  inputFlags = IF_HANDLER_NO_REPEAT_PRESS + \
                      IF_HANDLER_NO_RELEASE
End Object
BASIC Code
Include "KeyCodes"          ' Konstanten KEY_UP usw. einbinden

KEYBOARDACTION KeyHandler
  ON character SWITCH
  CASE KEY_UP:              ' Cursor nach oben
    MoveWillyUp
  END CASE
  CASE KEY_DOWN:            ' Cursor nach unten
    MoveWillyDown
  END CASE
  CASE KEY_LEFT:            ' Cursor nach links
    MoveWillyLeft
  END CASE
  CASE KEY_RIGHT:           ' Cursor nach rechts
    MoveWillyRight
  END CASE
  END SWITCH
End ACTION

^

14.3 Simulieren von Tastaturereignissen

Tabelle

Die Methoden KbdEvent und KbdEventWithScancode erzeugen ein Tastaturereignis und senden es an das entsprechende Objekt. Diese Methoden sind für alle Objektklassen definiert.

In den meisten Fällen reicht es aus, KbdEvent zu verwenden.

KbdEvent


Syntax im BASIC Code: <obj>.KbdEvent character, keyState, keyFlags

character: ASCII-Code oder erweiterter ASCII-Code

keyState: Kombination von KS_~Bits (siehe OnKeyPressed)

keyFlags: Eines der KF_~ Bits (siehe OnKeyPressed)


Die übergebenen Werte character, keyState und keyFlags werden 1:1 als Parameter an den Tastaturhandler des Objekts und an den BASIC Tastaturhandler weitergegeben. Im Unterschied zum echten Tastendruck übergibt KbdEvent als "scanCode" jedoch den Wert Null an das Objekt, so dass sowohl das Objekt als auch der BASIC Handler echte von vorgetäuschten Tastendrücken unterscheiden können. Per default werden mit KbdEvent simulierte Tastaturereignisse nicht vom Objekt behandelt, sondern sie werden gleich an den BASIC Handler weitergegeben. Details dazu siehe auch: inputFlags.


Beispiel 1: Kopieren der Tastatureingaben an ein zweites Textobjekt

UI-Code

Memo Text1
  OnKeyPressed = KeyHandler
End OBJECT

Memo Text2
End OBJECT
BASIC Code
KEYBOARDACTION KeyHandler
  ' Handler von Text1 sendet an Text2
  Text2.KbdEvent character, keyState, keyFlags
End ACTION


Beispiel 2: Senden eines Zeichens an das Application-Objekt. Das Ereignis wird dann intern an das Objekt, das den Focus hat, weitergeleitet.

DemoApplication.KbdEvent ASC("A"), 0, KF_FIRST_PRESS
DemoApplication.KbdEvent ASC("A"), 0, KF_RELEASE


Beispiel 3: Senden eines Zeichens an das Target-Objekt.

Target.KbdEvent ASC("Z"), 0, KF_FIRST_PRESS
Target.KbdEvent ASC("Z"), 0, KF_RELEASE

KbdEventWithScancode

In einigen Fällen werten die Objekte außer dem ASCII-Code und dem Tastaturstatus auch den Scancode der Taste aus. Das ist insbesondere bei der Tastaturnavigation durch Menüs der Fall, wenn bei der Definition des Tastenkürzels (siehe Instancevariable kbdShortcut, Objekthandbuch, Kapitel 3.1.4) das Bit KSM_PHYSICAL gesetzt ist. Typischer Weise ist dieses Bit bei den Menüeinträgen zum Drucken (Strg-P), Kopieren (Strg-C), Einfügen (Strg-V) usw. gesetzt.

Für den seltenen Fall, dass Sie einen solchen Button durch ein simuliertes Tastaturereignis aktivieren wollen gibt es die Methode KbdEventWithScancode. In allen anderen Fällen sollten Sie die Methode KbdEvent verwenden.


Syntax im BASIC Code:

<obj>.KbdEventWithScancode character, keyState, keyFlags, scanCode

character: ASCII-Code oder erweiterter ASCII-Code

keyState: Kombination von KS_~Bits (siehe OnKeyPressed)

keyFlags: Eines der KF_~ Bits (siehe OnKeyPressed)

scanCode: Scancode der zu 'character' gehörenden Taste


Die übergebenen Werte werden 1:1 als Parameter an den Tastaturhandler des Objekts und an den BASIC Tastaturhandler weitergegeben. Im Unterschied zu KbdEvent muss der Scancode der simulierten Taste übergeben werden. Das Objekt kann jetzt den simulierten Tastendruck nicht mehr von einem echten Tastendruck unterscheiden.

Passen ASCII-Code und Scancode nicht zueinander, so erkennen Objekte, die den Scancode auswerten, die Taste nicht. Das folgenden Bild zeigt die Scancodes einer MF II Tastatur. Diese Codes sind auch für neuere Tastaturen gültig, die weitere Tasten (und damit weitere Scancodes) haben.

Bild

Scancodes einer MF II Tastatur

^

14.4 Komplexes Beispiel - Filtern von Tastaturereignissen

Wir wollen bei einem Textobjekt nur Ziffern zulassen sowie die Buchstaben im Bereich von 'A' bis 'F'. Das Objekt soll alle anderen Buchstaben und Zeichen herausfiltern. Ein Blick auf die Instancevariable textFilter ergibt, dass es dafür keinen passenden Wert gibt.

Diese Aufgabe erfordert eine komplexe Lösung, da das Objekt per default das Tastaturereignis zuerst selbst behandelt. Im Anschluss daran ruft es den BASIC Handler. Wir wollen jedoch, dass der BASIC Handler das Ereignis zuerst erhält, damit wir unerwünschte Zeichen herausfiltern können. Erst danach darf das Textobjekt das Ereignis behandeln.

Um das zu erreichen muss man die inputFlags auf den Wert IF_IGNORE_ANY_KEY setzen und einen Keyboardhandler implementieren.

UI Code

Memo Text1
  OnKeyPressed = KeyHandler
  inputFlags = IF_IGNORE_ANY_KEY  ' betrifft nur echte Tastendrücke
End OBJECT
Intern passiert jetzt folgendes:
  1. Das Textobjekt erhält ein Tastaturereignis. Es prüft die inputFlags und den scanCode und stellt fest:

    - Ereignis nicht selbst behandeln

    - BASIC Handler rufen

  2. Der BASIC Handler erhält das Tastaturereignis. Er prüft den Tastencode und entscheidet, ob er den Tastendruck ignorieren oder an das Textobjekt zurückgeben soll. In folgenden Fällen soll der Tastendruck an das Textobjekt zurückgegeben werden:

    - Es ist ein Steuerzeichen oder eine Sondertaste.
    - Es ist ein erwünschtes ASCII-Zeichen (0 ... 9 und A ... F)

    Um das Ereignis an das Objekt zurückzusendene verwenden wir die Methode KbdEvent.

  3. Das Textobjekt erhält das vom BASIC-Handler "künstlich" erzeugte Tastaturereignis. Das Objekt erkennt automatisch, dass es sich um einen "unechten" Tastendruck handelt (weil der Parameter scanCode = 0 ist) und behandelt es deshalb jetzt, ohne den BASIC-Handler noch einmal zu rufen.
Im OnKeyPressed-Handler müssen wir unbedingt dafür sorgen, dass alle Steuertasten, Shift, Alt usw. durchgereicht werden. Sonst funktionieren die Keyboard Shortcuts und schlimmstenfalls die gesamte Tastaturnavigation nicht mehr. Ausnahmen wären Tastencodes, die wir bewusst selbst verwenden. Beispielsweise könnten wir die Entertaste herausfiltern. In unserem Fall lassen wir alle erweiterten Tasten passieren und blocken nur diejenige darstellbaren ASCII-Codes, die uns nicht interessieren. Bei der Gelegenheit wandeln wir auch gleich Kleinbuchstaben in Großbuchstaben um.

BASIC Code

! -------- Handler KeyHandler --------
! Aufgabe: Tastaturereignisse ausfiltern
! Parameter: sender AS Object, character AS Word,
! keyState AS Word, keyFlags AS Byte, scanCode AS Byte
!scanCode = Null wenn mit Methode KbdEvent erzeugt
! --------------------------------
KEYBOARDACTION KeyHandler
DIM ch

  ' Statt den Befehl GOTO zu verwenden erzeugen wir
  ' eine "Endlos-Schleife"
  ' Diese verlassen wir mit BREAK, wenn wir einen
  ' akzeptablen ASCII-Code gefunden haben. Dann wird
  ' hinter der Schleife weiter gemacht.
  ' Wir verlassen sie mit RETURN, wenn wir den ASCII-Code
  ' ignorieren wollen

REPEAT

  ' Steuerzeichen und Sondertasten haben die höherwertigen
  ' Bits im Parameter "character" gesetzt.
  ' Wir verlassen in diesem Fall die Schleife mit BREAK.
  IF character AND &hFF00 THEN BREAK

  ' Normale Codes prüfen
  ch = character          ' Das ist kürzer zu schreiben
                          ' aber eigentlich überflüssig
  ' Ziffer ?
  IF (ch >= ASC("0") ) AND (ch <= ASC("9")) THEN BREAK

  ' Buchstabe von A bis F ?
  IF (ch >= ASC("A") ) AND (ch <= ASC("F")) THEN BREAK

  ' Kleinbuchstabe von a bis f ?
  IF (ch >= ASC("a") ) AND (ch <= ASC("f")) THEN
    character = ch - 32   ' a->A usw.
    BREAK                 ' Buchstabe
  End IF

RETURN                    ' Code nicht akzeptieren
UNTIL TRUE

  ' Jetzt ASCII-Code an das Textobjekt zurücksenden
  sender.KbdEvent character, keyState, keyFlags

END ACTION
Den kompletten Quellcode hierfür finden Sie bei den Beispielen unter "Objekte\Text\Keyboard Handler Demo".

^

Weiter...