Events
Events sind etwa Mausklicks, oder Tastatureingaben. Diese werden mit Methoden an beispielsweise Widgets gebunden. Diese Bindung enthält den Empfänger des Events (z. B. ein Widget), das Ereignis (z. B. Mausklick) und eine Callback.
Ereignisse werden grundsätzlich von einem Widget zum Nächsten weitergereicht, bis zu einem Empfänger. Der Abschnitt Bindtags erläutert das genauer.
Empfänger von Event-Bindungen
Events werden mit Methoden an unterschiedliche Bereiche eines Programmes gebunden:
Ziel | Erläuterung |
---|---|
App-Bindung | Der gesamten App wird ein Ereignis mit
root.bind_all(…) zugeordnet |
Class-Bindung | Allen Widgets, die eine gemeinsame
class_ -Option benutzen, kann mit bind_class(…)
eine gemeinsame Menge an Events zugeordnet werden |
Tag-Bindung | Beispiel Tags von Canvas- oder Treeviewitems: Werden mit
Methoden wie w.tag_bind(…) erzeugt und beziehen
sich alleine auf Tags. Siehe auch das Beispiel
Tag Click im
Treeview-Kapitel |
Widget-Bindung | Einzelnen Widgets kann per
w.bind(…) ein Ereignis zugeordnet werden |
Das folgende Programm zeigt die obigen Bindungen. Das Label
la
wird als Ziel an das Ereignis
'<Button-1>'
(linker Mausknopf) gebunden, die Klasse
foobar
erhält eine Klassenbindung an den linken Mausknopf und
die App wird an die Tastenkombination STRG-q
gebunden.
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog as fd
class A(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('400x400')
self._createWidgets()
def _createWidgets(self):
# Ereignis wird an genau das Label gebunden
la = ttk.Label(self, text='Klick mich', relief=tk.SUNKEN )
la.pack(side=tk.TOP, fill=tk.BOTH, expand=tk.YES)
la.bind('<Button-1>', self._label1Click)
lf = ttk.Labelframe(self, text='Class Labels')
lf.pack(side=tk.TOP, fill=tk.BOTH, expand=tk.YES)
# Ereignis wird an die Klasse (Gruppe) gebunden
lb = ttk.Label(lf, class_='foobar', text='klickbar1', relief=tk.SUNKEN)
lb.pack(side=tk.LEFT, fill=tk.BOTH, expand=tk.YES)
lc = ttk.Label(lf, class_='foobar', text='klickbar2', relief=tk.SUNKEN)
lc.pack(side=tk.RIGHT, fill=tk.BOTH, expand=tk.YES)
lb.bind_class('foobar', '<Button-1>', self._label2Click)
# Anzeige, wohin geklickt wurde
self.labelInfoVar = tk.StringVar(value='Wer klickt was?')
ld = ttk.Label(self, textvariable=self.labelInfoVar, relief=tk.SUNKEN)
ld.pack(side=tk.TOP, fill=tk.X)
b = ttk.Button(self, text="Ende!", command=self.destroy)
b.pack(side=tk.BOTTOM)
# Ereignis wird an App gebunden
self.bind_all('<Control-KeyPress-q>', self._goodbye)
def _label1Click(self, event):
self.labelInfoVar.set('Label A wurde geklickt')
def _label2Click(self, event):
self.labelInfoVar.set('Gruppenklick, B oder C')
def _goodbye(self, event):
print('Googbye...')
self.destroy()
if __name__ == '__main__':
window = A()
window.mainloop()
Weiterverarbeitung von Events abbrechen
Im folgenden Beispiel wird die Eingabe von '1'
in ein Entry an eine Callback gebunden.
Diese Callback soll die Eingabe verarbeiten, die Eins soll aber nicht im Entry erscheinen. Die
Weiterverarbeitung der Eingabe im Entry wird unterbunden mit return 'break'
:
import tkinter as tk
from tkinter import ttk
class A(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('200x200')
self._createWidgets()
def _createWidgets(self):
self.entry = ttk.Entry(self)
self.entry.pack()
self.entry.bind('<KeyPress-1>', self._onOne)
def _onOne(self, event):
self.entry.delete(0, 'end')
self.entry.insert(0, '1 gedrückt')
return 'break'
if __name__ == '__main__':
window = A()
window.mainloop()
Eventtypen
Mausereignisse
Mausereignisse entstehen, wenn die Maus bewegt wird, über bestimmte Bereiche fährt oder Mausknöpfe betätigt werden.
Ereignisname | Beschreibung |
---|---|
'<Button>' | Ein Mausknopf wurde gedrückt |
'<Button-1>', '<1>' | Der erste Mausknopf wurde gedrückt |
'<ButtonRelease>' | Ein Mausknopf wurde losgelassen |
'<ButtonRelease-1>' | Der erste (linke?) Mausknopf wurde losgelassen |
'<Enter>' | Die Maus wird in einen Bereich, ein Widget hinein bewegt |
'<Leave>' | Die Maus wird aus einen Bereich, ein Widget heraus bewegt |
'<Motion>' | Die Maus über einem Bereich, einem Widget bewegt |
'<MouseWheel>' | Das Mausrad wurde bewegt |
Hinweis: Unter Linux wird '<MouseWheel>'
nicht erkannt.
Hier ist das Ereignis stattdessen '<Button-4>'
für Mausrad rauf und
'<Button-5>'
für Mausrad runter.
Das folgende Programm stellt einige Mausevents vor. Es wird dabei auch die String-Repräsentation
der event
-Parameter ausgegeben:
import tkinter as tk
from tkinter import ttk
class A(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('400x400')
self._createWidgets()
def _createWidgets(self):
label = ttk.Label(self, text='Ereignisfeld', relief=tk.SUNKEN)
label.pack(side=tk.TOP, fill=tk.BOTH, expand=tk.YES)
label.bind('<Button-1>', self._onClick)
label.bind('<Enter>', self._onEnterLeave)
label.bind('<Leave>', self._onEnterLeave)
self.labelInfoVar = tk.StringVar(value='Beschreibung')
labelInfo = ttk.Label(self, textvariable=self.labelInfoVar)
labelInfo.pack(side=tk.TOP, fill=tk.BOTH, expand=tk.YES)
def _onClick(self, event):
msg = F'Click: {event.x} {event.y} \n{event}'
self.labelInfoVar.set(msg)
def _onEnterLeave(self, event):
if event.type == tk.EventType.Enter:
msg = F'Enter: {event}'
self.labelInfoVar.set(msg)
else:
msg = F'Enter: {event}'
self.labelInfoVar.set(msg)
if __name__ == '__main__':
window = A()
window.mainloop()
Tastaturereignisse
Tastaturereignisse entstehen bei Tastendrücken auf der Tastatur. Manche Tasten, wie
etwa STRG
, NumLock
oder ALT
dienen dabei als
"Modifier".
Ereignisname | Beschreibung |
---|---|
'<KeyPress>' | Eine Taste wurde gedrückt |
'x' | Die Taste x wurde gedrückt |
'<Control-KeyPress-x>' | Die Taste x wurde zusammen
mit der STRG -Taste gedrückt |
'<KeyRelease>' | Taste wurde losgelassen |
Tastaturereignisse liefern event.keysym
(ein symbolischer Name für die Taste),
event.keycode
(Zahlencode der reinen Taste), und
event.keysym_num
(Zahlencode unter Berücksichtigung eventuell
gedrückter Modifier) zurück. Zudem noch mit event.char
das eigentliche
Zeichen.
Das folgende Programm wertet allgemein den event
-Parameter aus, indem
Felder des Parameters untersucht werden:
import tkinter as tk
from tkinter import ttk
class A(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('400x400')
self._createWidgets()
def _createWidgets(self):
self.bind_all('<KeyPress>', self._onKeypress)
self.labelInfoVar = tk.StringVar(value='Beschreibung')
labelInfo = ttk.Label(self, textvariable=self.labelInfoVar)
labelInfo.pack(side=tk.TOP, fill=tk.BOTH, expand=tk.YES)
def _onKeypress(self, event):
self.labelInfoVar.set(F'KeyPress\nkeysym={event.keysym}\n'
F'keycode={event.keycode}\n'
F'keysym_num={event.keysym_num}\n'
F'char={event.char}\n'
F'Modifier={event.state}')
if __name__ == '__main__':
window = A()
window.mainloop()
Weitere Ereignisse
Ereignisname | Beschreibung |
---|---|
'<Configure>' | Widget verändert seine Größe |
'<Destroy>' | Widget wird endgültig entfernt, weil beispielsweise die App beendet wird |
'<Expose>' | Wird aufgerufen, wenn Teile des Widgets/Fensters neu gezeichnet werden müssen |
'<FocusIn>', '<FocusOut>' | Ein Widget bekommt oder verliert den (Eingabe-)Fokus |
'<Map>', '<Unmap>' | Widget wird sichtbar, weil es in ein Layout eingefügt wird oder unsichtbar, weil es aus diesem wieder entfernt wird |
'<Visibility>' | Fenster wird sichtbar, weil es minimiert war oder gerade erst erzeugt wurde, oder maximiert |
Das folgende Programm stellt einige dieser Ereignisse vor:
import tkinter as tk
from tkinter import ttk
class A(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('400x400')
self._createWidgets()
def _createWidgets(self):
labelEvent = ttk.Label(self, text='Ereignis', relief=tk.SUNKEN)
labelEvent.pack(side=tk.TOP, fill=tk.BOTH, expand=tk.YES)
labelEvent.bind('<Configure>', self._onResize)
labelEvent.bind('<Expose>', self._onExpose)
labelEvent.bind('<Visibility>', self._onVisibility)
# Um dieses Ereignis auszulösen springt man mit der Tab-Taste in das entry
# und wieder hinaus in das entry2
entry = ttk.Entry(self)
entry.pack(side=tk.TOP, fill=tk.X, expand=tk.YES)
entry.bind('<FocusIn>', self._onFocus)
entry.bind('<FocusOut>', self._onFocus)
entry2 = ttk.Entry(self)
entry2.pack(side=tk.TOP, fill=tk.X, expand=tk.YES)
def _onVisibility(self, event):
print('Visibility ')
def _onResize(self, event):
print('Resize: ', event.width, event.height)
def _onExpose(self, event):
print('Expose: ', event.width, event.height)
def _onFocus(self, event):
print('Focus: %s' % ('bekommen', 'verloren')[int(event.type != tk.EventType.FocusIn)] )
if __name__ == '__main__':
window = A()
window.mainloop()
Übersicht Eventfelder
Der event
-Parameter der Callbacks kennt die folgenden Felder,
die je nach Ereignis gesetzt sind:
Feld | Beschreibung |
---|---|
.char | Das eigentlich Zeichen bei KeyPress, KeyRelease |
.delta | MouseWheel: Änderung des Mausrades, plattformabhängig |
.height, .width | Configure: Neue Höhe/Breite des Widgets |
.keycode, .keysym, .keysym_num | KeyPress, KeyRelease: Beschreibung der gedrückten Taste |
.num | Der gedrückte Mausknopf |
.serial | Seriennummer |
.state | Beschreibung der gedrückten Modifier bei Maus- und Tastaturereignissen. Plattformabhängig |
.time | Wird jede Millisekunde um 1 erhöht |
.type | Der Event-Typ, kann verglichen werden mit tk.EventType.eventName |
.widget | Das Widget, welches das Event verursacht hat |
.x, .y | Mausposition relativ zum Widget |
.x_root, .y_root | Mausposition relativ zum Fenster |
Übersicht Modifier
Die folgenden Modifier können ergänzend zu Maus- und Tastaturereignissen gesetzt werden,
beispielsweise w.bind('<Alt-Button-1>', self.onClick)
:
Modifier | Beschreibung |
---|---|
Alt | Alt-Taste wird gedrückt |
Control | Strg-Taste |
Double | Doppelklick |
Lock | ShiftLock-Taste wurde gedrückt |
Shift | Shift-Taste |
Triple | Dreifachklick |
Virtuelle Events
Virtuelle Events erweitern das Konzept von Events durch Namen für bestimmte Aktionen, die nicht unmittelbar aus den physischen Ereignissen, wie beispielsweise Mausevents, hervorgehen. Virtuelle Events kommen von Widgets, können aber auch an diese gesendet werden.
Beispiele für solche virtuellen Ereignisse sind <<Cut>>
,
<<Copy>>
und <<Paste>>
in Textwidgets.
Das folgende Programm stellt erzeugt ein neues Ereignis namens
<<TestEvent>>
. Dieses Ereignis wird einerseits ausgelöst, indem
der linke Mausknopf gedrückt wird, andererseits durch Drücken der Taste
1
(Eins).
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog as fd
class A(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('400x400')
self._createWidgets()
def _createWidgets(self):
# Binde Taste "1" an _onKeyPressOne
self.bind('1', self._onKeyPressOne)
# Erstelle <<TestEvent>> auf der Basis von Event:Linke Maustaste
self.event_add('<<TestEvent>>', '<Button-1>')
# Binde <<TestEvent>> an _onTestEvent
self.bind('<<TestEvent>>', self._onTestEvent)
def _onKeyPressOne(self, event):
print('_onKeyPressOne')
# <<TestEvent>> wird in die Ereigniswarteschlange geworfen
self.event_generate('<<TestEvent>>')
def _onTestEvent(self, event):
# Informationen über events ausgeben:
print('_onTestEvent: ', self.event_info('<<TestEvent>>'), self.event_info() )
if __name__ == '__main__':
window = A()
window.mainloop()
Event-Methoden
Übersicht Event-Methoden
Methode | Parameter | Rückgabewert | Beschreibung |
---|---|---|---|
w.bind(sequenz, callback, add) | sequenz: Ereignisname, callback Funktion mit einem event-Parameter (optional), add kann '+' sein, dann werden Ereignisse angehängt, sonst ersetzt | - | Ruft eine Callback auf, wenn Ereignis eintritt. Kann vorhandene Ereignisse ersetzen oder ergänzen |
w.bind_all(sequenz, callback, add) | wie w.bind(…) | - | Ereignis wird für alle Widgets in der Anwendung verarbeitet |
w.bind_class(className, sequenz, callback, add) | className Klasse (Gruppe) von Widgets, auf die sich die Verarbeitung bezieht, sonst wie w.bind(…) | - | Ereignis wird für alle Widgets der Klasse (Gruppe) in der Anwendung verarbeitet |
w.bindtags(bindtags) | Siehe Abschnitt Bindtags | ||
w.event_add(virtualEvent, *physischesEvent) | virtualEvent: neues virtuelles Event, physischesEvent: Liste physischer Events oder 'None' | - | Erzeugt ein neues virtuelles Event auf der Basis physischer vorhandener Events |
w.event_delete(virtualEvent, *physischesEvent) | wie w.event_add(…) | - | Entfernt ein physisches Event vom virtuellen Event. Wurde das letze Event entfernt, kann das virtuelle Event nicht mehr ausgelöst werden |
w.event_generate(event, **kw) | event: ein beliebiges Event, kw eine Liste von Schlüsselwörtern, die das Event beschreiben | - | Erzeugt ein neues Event mit den angegebenen Parametern aus kw |
w.event_info(virtual=None) | virtual: Optionales virtuelles Event | - | Ohne Parameter werden alle virtuellen Events ausgegeben. Ansonsten werden die physischen Events zum angegebenen virtuellen Events ausgegeben |
w.mainloop() | - | - | Messageloop, verarbeitet alle Nachrichten und wird mit w.quit() verlassen |
w.quit() | - | - | Verlässt die Messageloop, beendet damit zumeist das Programm |
w.unbind(sequenz, callback) | wie w.bind(…) | - | Entfernt die Ereignisbearbeitung |
w.unbind_all(sequenz, callback) | wie w.bind_all(…) | - | Entfernt Ereignisbearbeitung für sequenz für alle Widgets |
w.unbind_class(className, sequenz) | wie w.bind_class(…) | - | Entfernt Ereignisbearbeitung für sequenz für alle Widgets der Gruppe className |
w.update() | - | - | Displayupdate, sollte nicht aus Ereignisbearbeitung aufgerufen werden |
w.update_idletasks() | - | - | Idletasks werden bearbeitet: Displayupdate, Resizing |
Klassenbindung
Mit w.bind_class(…)
können Klassen, also Widgets mit gemeinsamer
class_
-Option, an Ereignisse gebunden werden. Widgets haben grundsätzlich
voreingestellte Klassennamen, die sich ändern lassen.
Das folgende Beispiel bindet die Tab-Taste an ttk.Entry-Widgets, so dass Tabulatoren eingefügt werden. Das Standardverhalten ist sonst, den Fokus von einem Widget zum Nächsten zu bewegen. Die hier vorgestellte Implementierung überschreibt vorhandene Textmarkierungen (Textauswahl) nicht, das ist anders als bei der Standardimplementierung.
import tkinter as tk
from tkinter import ttk
class A(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('200x200')
self._createWidgets()
def _createWidgets(self):
edit1 = ttk.Entry(self)
edit1.pack(fill=tk.BOTH, expand=tk.YES)
edit2 = ttk.Entry(self)
edit2.pack(fill=tk.BOTH, expand=tk.YES)
self.bind_class('TEntry', '<KeyPress-Tab>', self._insertTab)
def _insertTab(self, event):
event.widget.insert(tk.INSERT, '\t')
return 'break'
if __name__ == '__main__':
window = A()
window.mainloop()
Bindtags
Weitergabe von Ereignissen
Events werden grundsätzlich weitergereicht, bis sie zu ihrem Empfänger gelangt sind. Für ein Widget, das den Eingabefokus hat, ist diese Reihenfolge:
- das Widget schaut, ob es mit dem Event, beispielsweise einem Tastendruck, etwas
anfangen kann. Das eigentliche Widget kann in der Regel nur dann etwas mit dem Ereignis
anfangen, wenn per
w.bind(…)
eine Bindung erzeugt wurde. - Kann das Widget nichts mit dem Ereignis anfangen oder bricht es nach dem Empfang die
Weitergabe nicht ab (per
return 'break'
), dann landet es bei der Klasse (class_
-Option oder Standardklasse). Die Klasse besteht unter Umständen aus vielen Widgets, die ihrerseits nun prüfen, ob das Ereignis für sie bestimmt ist. Dasjenige Widget, das den Eingabefokus hat und zur Klasse gehört erhält nun den Zuschlag. Dieses Widget kann seinerseits die Weitergabe abbrechen (perreturn 'break'
). - Kann die Klasse nicht mit dem Ereignis anfangen oder wurde die Weitergabe nicht abgebrochen, wird es weitergereicht an die App. In der Regel endet die Weitergabe hier.
- Wurde das Ereignis mit
w.bind_all(…)
gebunden, wird das Ereignis weitergereicht, so dass die all-Bindung das Ereignis verarbeiten kann.
Für ttk.Entry und weitere Widgets ist die Klassenbindung verantwortlich für die
Verarbeitung von Tastatureingaben. Bindet man das Ereignis per
w.bind_class(…)
, dann wird
unter Umständen das entsprechende Zeichen nicht im Editor angezeigt.
Das folgende Beispiel zeigt die Weitergabe eines Tastaturereignisses am Beispiel Tastendruck-x
.
Kommentiert man die Zeile entry.bind_class(…)
aus, dann wird x
im ttk.Entry
dargestellt, sonst nicht.
import tkinter as tk
from tkinter import ttk
class A(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('200x200')
self._createWidgets()
def _createWidgets(self):
entry = ttk.Entry(self)
entry.pack(fill=tk.BOTH, expand=tk.YES)
entry.bind('<KeyPress-x>', self._printHelloEntry)
entry.bind_class('TEntry', '<KeyPress-x>', self._printHelloClass)
self.bind('<KeyPress-x>', self._printHelloApp)
self.bind_all('<KeyPress-x>', self._printHelloAll)
def _printHelloEntry(self, event):
print('Hello Entry')
def _printHelloClass(self, event):
print('Hello Class')
def _printHelloApp(self, event):
print('Hello App')
def _printHelloAll(self, event):
print('Hello All')
if __name__ == '__main__':
window = A()
window.mainloop()
Bindtags
Die Reihenfolge, mit der Ereignisse bearbeitet werden, liefert die Methode
w.bindtags()
. Ruft man sie mit dem ttk.Entry aus obigem Beispiel auf,
liefert sie etwa ('.!entry', 'TEntry', '.', 'all')
. Die vier Elemente
des Tupels bedeuten: Das Widget in Tcl-Schreibweise, die Klasse (TEntry), die App
(in Tcl-Schreibweise nur ein Punkt) und die All-Bindung.
Toplevel-Fenster und die App haben nur drei Elemente, beispielsweise
('.', 'Tk', 'all')
, der Name des Fensters ist identisch zum Punkt.
Die obengenannte Reihenfolge der Ereignisbearbeitung lässt sich verändern, beispielsweise
umkehren. Ruft man w.bindtags(liste)
mit einer Liste von Bindtags auf, dann
gilt diese Reihenfolge. Folgendes Programm zeigt, wie man die Reihenfolge der
Ereignisbearbeitung umkehrt:
import tkinter as tk
from tkinter import ttk
class A(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('200x200')
self._createWidgets()
def _createWidgets(self):
entry = ttk.Entry(self)
entry.pack(fill=tk.BOTH, expand=tk.YES)
print('Bindtags der App:', self.bindtags())
print('Bindtags des Entry:', entry.bindtags())
bt = list(entry.bindtags())
bt.reverse()
entry.bindtags(bt)
print('Bindtags des Entry nach Umkehrung:', entry.bindtags())
entry.bind('<KeyPress-x>', self._printHelloEntry)
entry.bind_class('TEntry', '<KeyPress-x>', self._printHelloClass)
self.bind('<KeyPress-x>', self._printHelloApp)
self.bind_all('<KeyPress-x>', self._printHelloAll)
def _printHelloEntry(self, event):
print('Hello Entry')
def _printHelloClass(self, event):
print('Hello Class')
def _printHelloApp(self, event):
print('Hello App')
def _printHelloAll(self, event):
print('Hello All')
if __name__ == '__main__':
window = A()
window.mainloop()
Referenzen
Siehe Auch
- https://www.tcl.tk/man/tcl8.7/TkCmd/keysyms.htm
- https://www.tcl.tk/man/tcl8.7/TkCmd/bind.htm
- https://www.tcl.tk/man/tcl8.7/TkCmd/event.htm
- https://www.tcl.tk/man/tcl8.7/TkCmd/bindtags.htm
-
https://github.com/python/cpython/blob/master/Lib/tkinter/__init__.py,
siehe dort die Klassen
EventType
undEvent