^

17.4 Typische Situationen

Dieser Abschnitt beschreibt die Behandlung typischer Fälle, die beim Arbeiten mit der Maus auftreten können.

^

17.4.1 Behandlung der Mousebuttons

In diesem Abschnitt wird am Beispiel der linken Maustaste das typische Vorgehen für diesen Fall erklärt

Im UI-Code sollten wir nur die Bits im Feld sendMouseEvents setzen, die wir auch wirklich benötigen. Das sind ME_LEFT_DOWN und ME_LEFT_UP, alle anderen Mausereignisse werden dann vom Objekt ignoriert.

Canvas MyOutputObj
  ....
  sendMouseEvents = ME_LEFT_DOWN + ME_LEFT_UP
  OnMouseButton = ButtonPressed
End Object
Im Actionhandler unterscheiden wir mit einer On - SWITCH Anweisung zwischen den beiden Ereignissen. Außerdem müssen wir beim Drücken der linken Maustaste die Maus "grabben" (GrabMouse), sonst wird das Ereignise "Loslassen" nicht an den Handler weitergeleitet. Das ist eine GEOS-interen Optimerungsfunktion. Entsprechend müssen wir beim Loslassen die Maus wieder freigeben (ReleaseMouse).
MOUSEACTION ButtonPressed
    ON event SWITCH

  CASE ME_LEFT_DOWN:
    sender.GrabMouse
    ' <... hier Aktionen ausführen ...>
  End CASE

  CASE ME_LEFT_UP:
    sender.ReleaseMouse
    ' <..hier Aktionen ausführen ...>
  End CASE

End SWITCH

End Action

Statt des LEFT_DOWN-Ereignisses könnte man auch das LEFT_HOLD-Ereignis abfragen. Das hätte den Vorteil, dass ein kurzer - eventuell versehentlicher - Klick wirkungslos bleibt. Entsprechende Beispiele finden Sie im nächsten Kapitel.

Bei entsprechender Programmierung kann man auch zwischen einem Einfachklick, einem Doppelklick und einem längeren Festhalten (HOLD) des Mausbuttons unterscheiden.

^

17.4.2 Arbeit mit dem OnMouseMove Handler

Wenn Sie während der Bewegung des Mauszeigers bestimmte Aktionen auslösen wollen (z.B. etwas zeichnen, siehe nächster Abschnitt) müssen Sie einen OnMouseMove Handler benutzen. Der Handler wird gerufen, sobald sich die Maus über dem Objekt bewegt. Da das in schneller Folge passiert ist es sinnvoll, diesen Handler nur freizuschalten, wenn er gebraucht wird. Ein sehr häufiger Fall ist, dass er nur benötigt wird, während die linke Maustaste gedrückt ist. Diese Situation wird im Folgenden beschrieben.

Im UI-Code setzen wir in sendMouseEvents nur die Bits ME_LEFT_HOLD und ME_LEFT_UP, d.h. der OnMouseMoveHandler ist zunächst inaktiv, weil wir das Bit ME_MOVE nicht setzen.

Ob wir zum Starten der Aktion das Ereignis ME_LEFT_DOWN oder ME_LEFT_HOLD verwenden hängt von der konkreten Situation und von den Intentionen des Programmierers ab. In einem Zeichenprogramm wird häufig ME_LEFT_DOWN bevorzugt während zum Bewegen von Objekten über den Schirm ME_LEFT_HOLD der Vorzug gegeben wird.

Der Code verwendet ein VisObj, er ist aber genauso auf jedes andere Objekt, dass die Maus unterstützt, anwendbar.

VisObj MyObj
  ....
  sendMouseEvents = ME_LEFT_HOLD + ME_LEFT_UP
  OnMouseButton = ButtonPressed
  OnMouseMove = MoveIt
End Object
Im Actionhandler "ButtonPressed" müssen wir zusätzlich zum Beispiel aus dem vorherigen Kapitel noch den OnMouseMove Handler freischalten bzw. deaktivieren. Der Parameter SET_BITS sorgt dafür, das das Bit ME_MOVE gesetzt wird, alle anderen Bits aber nicht geändert werden. Analog sorgt CLEAR_BITS dafür, dass das Bit ME_MOVE zurückgesetzt (auf Null gesetzt) wird, ohne dass die anderen Bits beeinflusst werden.
MOUSEACTION ButtonPressed
  ON event SWITCH

  CASE ME_LEFT_HOLD:
    sender.GrabMouse
    sender.sendMouseEvents = ME_MOVE, SET_BITS
    ' <... hier weitere Aktionen ausführen ...>
  End CASE

  CASE ME_LEFT_UP:
    sender.ReleaseMouse
    sender.sendMouseEvents = ME_MOVE, CLEAR_BITS
    ' <.. hier weitere Aktionen ausführen ...>
  End CASE
  End SWITCH
End Action
Der OnMouseMove Handler "MoveIt" wird nun nur gerufen, während die linke Maustaste gedrückt ist. Die Anweisung "sender.GrabMouse" sorgt im Übrigen auch dafür, dass dieser Handler auch dann gerufen wird, wenn der Nutzer die Maus aus dem Objekt heraus bewegt. Das ist sehr praktisch, da wir diesen Fall dann nicht extra behandeln müssen. Das Grafiksystem von GEOS sorgt dabei dafür, dass wir nicht über den Rand des zugehörigen Views (bzw. bei Canvas und Image Objekten nicht über den Rand des Objekts) malen können.
MOUSEACTION MoveIt
  ' <... hier Aktionen ausführen ...>
End Action

^

17.4.3 Zeichnen auf den Bildschirm

Sehr häufig wollen wir mit der Maus etwas auf den Bildschirm zeichnen. Dieser Abschnitt beschreibt, wie man dazu vorgehen muss. Wir setzen den folgenden UI-Code voraus:
BitmapContent MyBitmap
  ....
  sendMouseEvents = ME_LEFT_HOLD + ME_LEFT_UP
  OnMouseButton = ButtonPressed
  OnMouseMove = MoveIt
End Object
Beim Drücken der Linken Maustaste müssen wir jetzt unser Objekt zum "Screen" machen. Damit gehen alle Grafik- und Textausgaben an dieses Objekt und erscheinen somit auf dem Bildschirm. Analog müssen wir den Screen wieder zurücksetzen, wenn die Maustaste losgelassen wird.

Beim BitmapContent (wie im Beispiel) gehen die Grafik- und Textausgaben parallel dazu in die Bitmap, so dass sie automatisch "gespeichert" werden. Bei anderen Objekten, wie z.B. einem Canvas oder einem VisObj gehen sie nur auf den Bildschirm und wir müssen selbst dafür sorgen, dass unser "Ergebnis" auf geeignete Weise gespeichert wird.

Weil wir ein BitmapContent verwenden brauchen wir die Maus nicht zu Grabben und zu Releasen. Sobald eine Maustaste über einem VisContent- oder BitmapContent gerückt und festgehalten wird gehen alle Mausereignisse an dieses Objekt, auch wenn der Mauszeiger das zugehörige View zwischenzeitlich verlässt.

MOUSEACTION ButtonPressed
  ON event SWITCH

  CASE ME_LEFT_HOLD:
    sender.sendMouseEvents = ME_MOVE, SET_BITS
    Screen = sender
    ' < ... mehr ..>
  End CASE

  CASE ME_LEFT_UP:
    sender.sendMouseEvents = ME_MOVE, CLEAR_BITS
    Screen = NullObj ( )
    ' < ... mehr ..>
  End CASE

  End SWITCH

End Action
Zur Demonstration wollen wir eine "Gummilinie" implementieren. Dazu benötigen wir globale Variablen. X0 und y0 sind der Startpunkt, x1, und y1 der sich mit der Mausbewegung ändernde Endpunkt der Linie.
DIM x0, y0, x1, y1
Beim Drücken der linken Maustaste müssen wir die Linienkoordinaten auf die aktuelle Mausposition setzen. Wir stellen den MixMode MM_INVERT ein, der wie für eine Gummilinie gemacht ist. In diesem Modus werden die Pixels, auf die Linien und andere Grafikausgaben wirken, nicht mit der Vordergrundfarbe überschrieben, sondern die Farbcodes werden invertiert. Das hat zwei Konsequenzen. Erstens ist die Linie immer zusehen, egal welche Farbe der Hintergrund hat und zweitens bewirkt ein zweimaliges Zeichnen der gleichen Linie, dass sie wieder verschwindet. Das ist genau das was wir brauchen.

Schließlich stellen wir noch eine Linienbreite von 8 Pixeln ein und zeichnen sie erstmalig. In unserem Fall ergibt das nur einen Punkt auf dem Schirm, der aber nötig ist, weil der OnMove-Handler ihn wieder löscht.

Der fertige Codeabschnitt sieht also so aus:

CASE ME_LEFT_HOLD:
  sender.GrabMouse
  sender.sendMouseEvents = ME_MOVE, SET_BITS
  Screen = sender
  x0 = xPos : y0 = yPos
  x1 = xPos : y1 = yPos
  graphic.MixMode = MM_INVERT
  graphic.lineWidth = 8
  Line x0, y0, x1, y1
End CASE

Der OnMove Handler hat nun nur wenig zu tun. Die erste Line Anweisung löscht die aktuelle Line vom Bildschirm. Das geht, weil wir den MixMode MM_INVERT eingestellt haben. Dann speichern wir die neuen Endkoordinaten und zeichnen die Line erneut.
MOUSEACTION MoveIt
  Line x0, y0, x1, y1
  x1 = xPos
  y1 = yPos
  Line x0, y0, x1, y1
End Action

Etwas schneller - und damit weniger anfällig gegen Flackern - wäre folgende Sequenz:
  Line x0, y0, x1, y1
  Line x0, y0, xPos, yPos ' Koordinaten beachten!
  x1 = xPos
  y1 = yPos

Beim Loslassen der Maustaste löschen wir zuerst die vorhandene Linie, stellen den "normalen" MixMode MM_SET ein und zeichnen die Line dann permanent in weißer Farbe.
CASE ME_LEFT_UP:
  sender.ReleaseMouse
  sender.sendMouseEvents = ME_MOVE, CLEAR_BITS
  Line x0, y0, x1, y1
  graphic.MixMode = MM_SET
  INK WHITE
  Line x0, y0, x1, y1
  Screen = NullObj ( )
End CASE

Wichtiger Hinweis:

Das Beispiel geht davon aus, dass wir keinen Screen haben, wenn unser Objekt nicht aktiv ist. Sollte das nicht so sein (z.B. ein anders Objekt hat die Anweisung DefaultSceen im UI Code) so müssen wir uns den aktuellen Screen unbedingt in einer globalen Variablen merken.

DIM oldScreen as OBJECT

Die Anweisung "Screen = sender" wird dann zu
oldScreen = Screen
Screen = sender

und "Screen = NullObj ( )" wird zu
Screen = oldScreen

Außerdem sollten wir uns in einer globalen Variablen merken, dass die Maustaste gedrückt ist und dies im OnMove Hander abfragen. Der Grund ist, dass nach dem Loslassen der Maustaste noch ein OnMove Event in der Warteschlange sein könnte, dass dann auf den falschen Screen zeichnet. Die folgenden Codezeilen sind an den entsprechenden Stellen einzufügen.
DIM mouseDown' globale Variable
' Im ME_LEFT_HOLD-Zweig:
  mouseDown = TRUE
' Im ME_LEFT_UP-Zweig:
  mouseDown = FALSE
' Im MoveIt Handler:
  IF mouseDown = FALSE THEN RETURN

^

17.4.4 Behandeln von MouseOver Ereignissen

Gelegentlich ist es sinnvoll einfach nur zu wissen, ob sich die Maus über dem vom Objekt eingenommen Bildschirmbereich befindet oder nicht. Diesem Zweck dient der OnMouseOver Handler. Er wird gerufen wenn die Maus den Bereich des Objekts betritt (event = ME_ENTER) oder ihn verlässt (event = ME_LEAVE).

Beachten Sie, dass das Ereignis "Verlassen des Objektbereichs" nur gesendet wird, wenn das Objekt die Maus gegrabbt hat. Ausnahmen sind die Objekte VisContent und BitmapContent. Sie senden dieses ME_LEAVE-Ereignis in jedem Fall.

Eine typische Implementation könnte also wie folgt aussehen. Das Objekt stellt eine gelbe Ellipse dar, die rot wird, wenn sich der Mauszeiger über dem Objekt befindet.

UI-Code:

Canvas Area
  fixedSize = 200, 100
  sendMouseEvents = ME_ENTER + ME_LEAVE
  OnMouseOver = OverHandler
  OnDraw = DrawHandler
End Object

BASIC-Code:
DrawAction DrawHandler
  Rectangle 0, 0, MaxX, MaxY, Black
  Fillellipse 2, 2, MaxX-2, MaxY-2, Yellow
End Action

Im Handler für den Mauszeiger machen wir zunächst das das Canvas-Objekt zum Screen, grabben uns die Maus (damit das ME_LEAVE-Ereignis gesendet wird) und zeichnen dann eine rote Ellipse. Beim Verlassen des Objektbereichs machen wir die Ellipse wieder gelb, setzen den Screen zurück und geben die Maus wieder frei.
MouseAction OverHandler
  ON event Switch
  case ME_ENTER
    Screen = sender
    Sender.GrabMouse
    FillEllipse 2, 2, MaxX-2, MaxY-2, RED
  End CASE
  case ME_LEAVE
    FillEllipse 2, 2, MaxX-2, MaxY-2, YELLOW
    Screen = NullObj()
    Sender.ReleaseMouse
  End CASE
  End SWITCH
End Action

Das nächste Beispiel für einen OnMouseOver-Handler gibt eine Information an ein Textobjekt aus, je nach dem, ob sich die Maus über dem Objekt befindet oder nicht.
MouseAction OverHandler
  ON event Switch
  case ME_ENTER
    Sender.GrabMouse
    InfoText.text$ = "Maus ist über dem Objekt"
  End CASE
  case ME_LEAVE
    InfoText.text$ = ""
    Sender.ReleaseMouse
  End CASE
  End SWITCH
End Action

Hinweise / Technische Details

  1. Um zu erkennen, ob der Mauszeiger gerade den Objektbereich betritt oder verlässt muss sich das Objekt merken, ob der Mauszeiger vorher innerhalb oder außerhalb der Grenzen des Objekts war. Die Information, dass sich der Mauszeiger außerhalb der Grenzen des Objekts befindet bekommt es aber nur, wenn es die Maus gegrabbt hat. Unterlassen Sie das Grabben der Maus beim Betreten des Objekts so erkennt das Objekt nicht mehr, wenn der Mauszeiger seine Grenzen verlässt. Ein erneutes Betreten des Objektbereichs wird daher auch nicht erkannt und die entsprechende Message wird nicht noch einmal gesendet.

    Ausnahmen sind hier wieder das VisContent und das BitmapContent Objekt. Sie erhalten die Information "Objektbereich verlassen" bzw. ".. betreten" in jedem Fall.

  2. Bewegt der Nutzer die Maus so schnell, dass sie aus dem Objektbereich direkt in das Fenster eines anderen Programms springt (ohne dass noch ein Mausereignis innerhalb des eigenen Programms erzeugt wird), so wir das Ereignis "Objektbereich verlassen" zunächst nicht gesendet. Es wird stattdessen gesendet wenn der Mauszeiger das Hauptfenster unseres Programms wieder betritt. Das ist kein Fehler von R-BASIC sondern eine Eigenschaft des GEOS-Systems.

  3. Die Parameter xPos und yPos des OnMouseOver-Handlers enthalten für die Objekte VisContent und BitmapContent stets den Wert Null. Für alle anderen Objekte enthalten Sie die Koordinaten, bei denen das Objekt betreten bzw. verlassen wurde. Das ist im Allgemeinen dicht am Rand des Objekts.

^

17.4.5 Abfrage der Tastatur

Gelegentlich muss man in einem Maushandler unterschiedliche Operationen auslösen, je nach dem, ob gleichzeitig eine bestimmte Taste auf der Tastatur gedrückt ist oder nicht. Insbesondere die Steuertasten wie Shift, Ctrl (Strg) usw., die mit GetKeyState abgefragt werden können, sind hier interessant.

GetKeyState liefert einen Word-Wert, dessen einzelne Bits die folgende Bedeutung haben:

Tabelle

Tabelle


Zur Abfrage der Bits muss man die logische AND Funktion verwenden:

' Abfrage ob eine Shift-Taste gedrückt ist
  IF GetKeyState AND ( KS_LSHIFT OR KS_RSHIFT ) THEN ....

' Abfrage ob die NUM-Lock Taste gedrückt ist
  IF GetKeyState AND KS_NUM_LOCK THEN ....
Weitere Informationen zu GetKeyState finden Sie im Programmier-Handbuch.


Zur Tastaturabfrage innerhalb von Maushandlern eignen sich außerdem die folgenden Anweisungen bzw. globale Variablen:

GetKey

GetKeyLP: Globale Varibalen die die aktuell bzw.
zuletzt gedrückte Taste enthalten. Das kann ein ASCII-Code oder bei Steuertasten wie F1 ein erweiterter Code (> 256) sein.

Inkey$: Liest ein einzelnes Zeichen von der Tastatur.
Details dazu finden Sie an den entsprechenden Stellen im Programmier-Handbuch. Alternativ können Sie auch den Tastaturhandler des Objekts benutzen, falls es einen besitzt, um über den Zustand der Tastatur auf dem Laufenden zu sein. Bitte benutzen Sie nicht die Anweisungen Input bzw. InputBox. Das kann zu Konflikten oder zu unerwartetem Verhalten führen.

^

17.5 Utility Methoden

Tabelle


Syntax im BASIC Code: <z> = <obj>.TestInside (x, y)
<z> = <obj>.TestInsideAC (x, y)
<z>: numerische Variable

Return: z ist Null, wenn das Koordinatenpaar innerhalb des Objekts liegt.
z ist größer als Null, wenn nicht.

Die beiden Methoden prüfen, ob ein Koordinatenpaar im vom Objekt überdeckten Bildschirmbereich liegt oder nicht. TestInside setzt voraus, dass die linke obere Ecke des Objekts die Koordinaten (0; 0) hat. Das ist das gleiche Koordinatensystem, dass verwendet wird, wenn das Objekt der Screen ist (Vergleiche 17.4.3 Zeichnen auf den Bildschirm). Das heißt, die x-Koordinate liegt außerhalb des Objekts wenn gilt: x < 0 oder x > object.xSize. Analoges gilt für die y-Koordinate.

TestInsideAC (AC = absolute coordiates, absolute Koordinaten) berücksichtigt die Position des Objekts innerhalb des übergeordneten Fensters. Das heißt, die x-Koordinate liegt außerhalb des Objekts wenn gilt: x < object.xPosition oder x > (object.xPosition + object.xSize). Analoges gilt für die y-Koordinate.

TestInside bzw. TestInsideAC sind dabei sehr viel schneller als die manuelle Abfrage der Positionen entsprechend den obigen Beziehungen.

Außerdem enthält der zurückgelieferte Wert Informationen darüber, wo genau sich das Koordinatenpaar relativ zum Objekt befindet. Das Bild veranschaulicht das.

Tabelle

Der Wert ist die Summe aus folgenden Informationen:

1: Die y-Koordinate liegt oberhalb des Objekts

2: Die x-Koordinate liegt links vom Objekt

4: Die x-Koordinate liegt rechts vom Objekt

8: Die y-Koordinate liegt unterhalb des Objekts

Liegt das Koordinatenpaar links oberhalb des Objekts, beträgt der zurückgelieferte Wert 1 + 2 = 3.

Diese Informationen können mit der logischen Operation AND abgefragt werden. Der folgende Code fragt ob, ob die x-Koordinate rechts vom Objekt liegt. Die Print-Anweisung wird also für die Fälle 5, 4 und 12 ausgeführt:

  z = MyObj.TestInside(x, y)
  IF z AND 4 THEN Print "Rechts vom Objekt"
Wenn Sie nur die Information benötigen, ob sich der Mauszeiger innerhalb oder außerhalb des Objekts befindet, können Sie auch einen OnMouseOver-Handler verwenden und die Information in einer globalen Variablen speichern. Beachten Sie, dass Sie dazu gegebenenfalls die Maus grabben müssen.

^

Weiter...