^17.4 Typische SituationenDieser Abschnitt beschreibt die Behandlung typischer Fälle, die beim Arbeiten mit der Maus auftreten können.^17.4.1 Behandlung der MousebuttonsIn diesem Abschnitt wird am Beispiel der linken Maustaste das typische Vorgehen für diesen Fall erklärtIm 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 HandlerWenn 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 BildschirmSehr 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 EreignissenGelegentlich 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
^17.4.5 Abfrage der TastaturGelegentlich 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:
|
' 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.
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
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.
Der Wert ist die Summe aus folgenden Informationen: 1: Die y-Koordinate liegt oberhalb des ObjektsLiegt 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.
^ |