3.7 MAUSEVENTS

 

 

EINFÜHRUNG

 

Bisher verstehst du den Computer so, dass er  Anweisung um Anweisung ausführt. Er kann auch auf Grund von Bedingungen den Ablauf verändern und Wiederholschleifen durchlaufen.  Die entsprechenden Programmstrukturen heissen Sequenz, Selektion und Iteration. Bereits 1966 habe Böhm und Jacopini in einem berühmten Artikel bewiesen, dass sich alle Rechenverfahren (Algorithmen) mit diesen drei Strukturen programmieren lassen.

Dies gilt allerdings nur, solange man keine äusseren Einflüsse mit einbezieht. Beispielsweise kann man ein Programm auch abbrechen, indem man in irgend einem Moment mit der Maus auf die Schaltfläche "Schliessen" (close-Button) klickt. Solche Vorgänge brauchen ein neues Programmierkonzept: die Ereignissteuerung (event handling). Das Grundprinzip hast du bereits im Kapitel Turtlegrafik/Ereignissteuerung kennengelernt. Es handelt sich dabei um  Abläufe der Art:

"Wann immer das Ereignis E auftritt, führe die Funktion f aus".

Die Implementierung ist einfach und seit den Anfängen der Computertechnik in den Fünfzigerjahren des letzten Jahrhunderts bekannt. Man definiert eine Funktion f (damals Interruptroutine genannt), welche vom eigenen Programm gar nie aufgerufen wird. Sie schläft sozusagen, bis ein bestimmtes Ereignis E auftritt und sie durch diesen äusseren Einfluss vom System automatisch aufgerufen wird.  Heute nennt man eine solche Funktion Callback und sagt anschaulich, dass der Callback f  durch den Event E "gefeuert" wird. Oft werden Callbacks mit Parameterwerten aufgerufen, die wichtige Informationen über den Event enthalten, beispielsweise, welcher Mausknopf gedrückt wurde oder wo sich die Maus befindet.

PROGRAMMIERKONZEPTE: Ereignisgesteuertes Programm, Callback, Registrieren von Callbacks

 

 

AUF EINEN MAUSEVENT REAGIEREN

  Wie in der Turtlegrafik kannst du auch im GPanel Mausevents verwenden. Im ersten Beispiel wird beim Drücken der linken oder rechten Maustaste an der aktuellen Mausposition ein grüner Kreis gezeichnet. Du gehst wie folgt vor:

Zuerst definierst du in einer Funktion  mit beliebig gewähltem Namen, was beim Drücken einer Maustaste geschehen soll. Du wählst einen Namen, hier z.B. onMousePressed(x, y), der möglichst gut ausdrückt, was die Funktion macht. Der Callback erhält beim Aufruf durch das System als Parameterwerte die aktuellen Koordinaten des Mauscursors.

Als nächstes musst du dem System bekannt geben, dass es deinen Callback aufrufen soll, wenn der Mauskbutton gedrückt wird. Man nennt diesen Vorgang Registrieren des Callbacks.

 

Um deinen Callback zu registrieren, verwendest du einen benannten Parameter von makeGPanel(), der mousePressed heisst.

from gpanel import *

def onMousePressed(x, y):
    move(x, y) 
    fillCircle(0.02)

makeGPanel(mousePressed = onMousePressed)
setColor("green")              
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

MEMO

 

Ein Callback wird nicht durch das eigene Programm aufgerufen, sondern automatisch, wenn der Event ausgelöst wird. Die Registrierung des Callbacks erfolgt durch einen benannten Parameter.

Das Drücken einer Maustaste kannst du mit zwei verschiedenen Callbacks erfassen: einem Click-Event oder einem Press-Event. Der Click-Event wird erst ausgelöst, nachdem die Taste wieder losgelassen wird, der Press-Event bereits beim Drücken der Taste.

 

 

MAUSBEWEGUNG ERFASSEN

 

Auch die Mausbewegung kann man als Event auffassen, der bei der Bewegung der Maus in rascher Abfolge ausgelöst wird. Dein Programm zeichnet bei jedem Aufruf des Callbacks einen rot gefüllten Kreis mit einer schwarzen Umrandung, wodurch sich lustige röhrenartige Bilder zeichnen lassen.

 

from gpanel import *      

def onMouseMoved(x, y):
    move(x, y)
    setColor("red")
    fillCircle(.04) 
    setColor("black")
    circle(.04)  

makeGPanel(mouseMoved = onMouseMoved)
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

MEMO

 

Der Callback onMouseMoved(x, y) wird durch den benannten Parameter mouseMoved registriert.

 

 

FREIHANDZEICHNEN MIT GEDRÜCKTER MAUSTASTE

 

Jetzt bist du bereits in der Lage, ein einfaches Zeichnungsprogramm zu schreiben, mit dem du mit der Maus freihändig eine Figur zeichnen kann. Dazu brauchst du noch den Drag-Event, der dann in rascher Abfolge ausgelöst wird, wenn du die Maus mit gedrückter Taste bewegst. Die Programmlogik ist ganz einfach: Du setzt den Grafikcursor beim Press-Event auf den aktuellen Ort und zeichnest im Drag-Event mit draw() eine Linie.

 
from gpanel import *

def onMousePressed(x, y):
    move(x, y)

def onMouseDragged(x, y):
    draw(x, y)

makeGPanel(mousePressed = onMousePressed, 
           mouseDragged = onMouseDragged)
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

MEMO

 

Man kann mehrere Callbacks mit benannten Parameter gleichzeitig registrieren. Die Reihenfolge der Parameter ist unwesentlich.

 

 

LINKE UND RECHTE MAUSTASTE

 

Wie du sicher gemerkt hast, werden die Mausevents sowohl mit der linken als auch rechten Maustaste ausgelöst. Willst du zwischen der linken und rechten Maustaste unterscheiden, verwendest du Funktionen isLeftMouseButton() und isRightMouseButton(). Diese geben True zurück, wenn die linke bzw. rechte Maustaste beteiligt ist.

Das Programm öffnet beim Drücken der rechten Maustaste ein Farbpalette. Mit der linken Maustaste kannst du die Füllfarbe des Kreises auswählen.

 
from gpanel import *

def onMousePressed(x, y):
  if isLeftMouseButton():
      pixColor = getPixelColor(x, y)
      if pixColor == makeColor("white"):
          return
      clear()
      setColor(pixColor)
      move(5, 5)
      fillCircle(2)
      
  if isRightMouseButton():
      for i in range(5):
          move(9, 2 * i + 1)
          if i == 0:
              setColor("deep pink")
          if i == 1:
              setColor("green")
          if i == 2:
              setColor("yellow")
          if i == 3:
              setColor("deep sky blue")
          if i == 4:
              setColor("dark violet")
          fillRectangle(2, 2)

makeGPanel(0, 10, 0, 10, mousePressed = onMousePressed)
move(5, 5)
fillCircle(2)
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

MEMO

 

Die registrierten Maus-Callbacks werden mit der linken und rechten Maustaste ausgelöst. Du kannst mit isLeftMouseButton() bzw. isRightMouseButton() herausfinden, welche Taste es war.

 

 

GUMMIBANDLINIEN

 

Wenn du mit einem Zeichnungsprogramm Linien zeichnen möchtest, so markierst du durch Drücken der Maustaste den Anfangspunkt. Dann legst du beim Ziehen der Maus eine provisorische Linie wie ein Gummiband, das am Anfangspunkt festgemacht ist. Erst wenn du mit der Lage der Linie zufrieden bist, lässt du die Maustaste los und die Linie wird definitiv gezeichnet.

Du benötigst hier also drei Callback:
onMousePresssed
, onMouseDragged und onMouseReleased.

 
Es gibt aber ein besonderes Problem: Bewegt sich das Gummiband über die Zeichnungsfläche, muss es in seiner alten Lage immer wieder gelöscht und an der neuen Lage gezeichnet werden, ohne dass dabei die bereits vorhandene Zeichnung verunstaltet wird. Würdest du die Linie löschen, indem du sie mit der Hintergrundfarbe überschreibst, so ergäben sich in der bestehenden Zeichnung bei den Überschneidungspunkten Lücken.

Um dieses Problem zu lösen, muss man im Press-Callback die bereits vorhandene Zeichnung abspeichern (man sagt auch "retten"). Das Löschen der temporären Gummibandlinie geschieht jetzt so, dass  man diese "alte" Zeichnung wiederherstellt. Du kannst die Zeichnung mit storeGraphics() abspeichern und mit recallGraphics() wiederherstellen.

from gpanel import *

def onMousePressed(x, y):
    global x1, y1, x2, y2
    storeGraphics()
    x1 = x
    y1 = y
    x2 = x1
    y2 = y1
    setColor("red")
 
def onMouseDragged(x, y):
    global x2, y2
    recallGraphics()
    x2 = x
    y2 = y
    line(x1, y1, x2, y2) 

def onMouseReleased(x, y):
    setColor("white")
    if not (x1 == x2 and y1 == y2):
        line(x1, y1, x2, y2)
    else:
        recalGraphics() 

x1 = 0
y1 = 0
x2 = 0
y2 = 0

makeGPanel(mousePressed = onMousePressed, 
              mouseDragged = onMouseDragged,
              mouseReleased = onMouseReleased)
title("Press And Drag To Draw Lines")
bgColor("blue")
setColor("white")
lineWidth(2)
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

MEMO

 

Merke dir das Prinzip zum Zeichnen von Gummibandlinien:

Beim Press-Event werden die Endpunkte der Linie initialisiert und die Grafik gespeichert.

Beim Drag-Event wird die gespeicherte Grafik wiederhergestellt, die temporäre Linie mit dem neuen Endpunkt gezeichnet und der neue Endpunkt gespeichert.

Beim Release-Event wird die Linie definitiv gezeichnet, allerdings nur, falls die Maus überhaupt bewegt wurde.

 

 

AUFGABEN

 

1.


Zeichne einen grün gefüllten Kreis. Wenn du die Maus in den Kreis bewegst, soll die Füllfarbe auf rot wechseln. Beim Herausfahren wird sie wieder grün.

Es ist vorteilhaft, die Koordinaten so zu wählen, dass der 0-Punkt in der Mitte des Fensters ist:
makeGPanel(-10, 10, -10, 10,
                   mouseMoved = onMouseMoved)

 



2.

Dein Programm soll beim jedem Mausklick ein Linienstück zeichnen.
 


3.


Dein Programm soll bei der Bewegung mit gedrückter Maustaste eine röhrenartige Figur zeichnen, wobei die Röhre bei der Bewegung von einer Anfangsdicke von 0.01 bis zu einer Dicke von 0.1 anschwellen soll, um dann wieder zur Anfangsdicke zurückzukehren.

 


4.

Schreibe ein Programm, mit dem du auf schwarzem Hintergrund grüne Rechtecke zeichnen kannst. Dabei sollst du durch Drücken und Ziehen der Maus zuerst eine provisorisches "Gummirechteck" platzieren können, bevor es dann beim Loslassen der Maus definitiv dargestellt wird. Verwende dazu die Rechteck-Funktion, die mit den Koordinaten von zwei gegenüberliegenden Eckpunkten des Rechtecks aufgerufen weden (rectangle(x1, y1, x2, y2)).
 

 

   

ZUSATZSTOFF

 

CALLBACKS REGISTRIEREN MIT DECORATORS

 

Statt benannte Parameter von makeGPanel() zu verwenden, um einen Callback zu registrieren, kann eine beliebig genannte Funktion mit zwei Parameter x und y durch eine vorangestellte Zeile, die mit einem At-Symbol @ eingeleitet wird, so "dekoriert" werden, dass sie von TigerJython automatisch als Callback registriert und beim Eintreten des entsprechenden Events aufgerufen wird. Als Decorators stehen zur Verfügung:

@onMousePressed

Maustaste wird gedrückt

@onMouseReleased

Maustaste wird losgelassen

@onMouseClicked

Maustaste wird gedrückt und losgelassen

@onMouseDragged

Maus wird mit gedrückter Taste bewegt

@onMouseMoved

Maus wird mit nicht gedrückter Taste bewegt

@onMouseEntered

Maus tritt in das Grafikfenster ein

@onMouseExited

Maus tritt aus dem Grafikfenster aus

Das oben gezeigte Programm, welches beim Drücken einer Maustaste eine Kreis zeichnet, kannst du unter Verwendung eines Decorators auch so schreiben:

from gpanel import *

@onMousePressed
def doIt(x, y):
    move(x, y) 
    fillCircle(0.02)

makeGPanel()
setColor("green")         
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)