Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1059,6 +1059,11 @@ tkinter
with outdated names.
(Contributed by Serhiy Storchaka in :gh:`143754`.)

* Added :class:`!Event` attributes :attr:`!user_data` for Tk virtual events
and :attr:`!detail` for ``Enter``, ``Leave``, ``FocusIn``, ``FocusOut``,
and ``ConfigureRequest`` events.
(Contributed by Matthias Kievernagel and Serhiy Storchaka in :gh:`47655`.)


.. _whatsnew315-tomllib-1-1-0:

Expand Down
32 changes: 31 additions & 1 deletion Lib/test/test_tkinter/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,8 @@ def test_focus(self):
self.assertEqual(e.x_root, '??')
self.assertEqual(e.y_root, '??')
self.assertEqual(e.delta, 0)
self.assertEqual(e.user_data, '??')
self.assertEqual(e.detail, 'NotifyAncestor')
self.assertEqual(repr(e), '<FocusIn event>')

def test_configure(self):
Expand Down Expand Up @@ -669,6 +671,8 @@ def test_configure(self):
self.assertEqual(e.x_root, '??')
self.assertEqual(e.y_root, '??')
self.assertEqual(e.delta, 0)
self.assertEqual(e.user_data, '??')
self.assertEqual(e.detail, '??')
self.assertEqual(repr(e), '<Configure event x=0 y=0 width=150 height=100>')

def test_event_generate_key_press(self):
Expand Down Expand Up @@ -705,6 +709,8 @@ def test_event_generate_key_press(self):
self.assertEqual(e.x_root, -1)
self.assertEqual(e.y_root, -1)
self.assertEqual(e.delta, 0)
self.assertEqual(e.user_data, '??')
self.assertEqual(e.detail, '??')
self.assertEqual(repr(e),
f"<KeyPress event state={e.state:#x} "
f"keysym=z keycode={e.keycode} char='z' x={e.x} y={e.y}>")
Expand Down Expand Up @@ -740,8 +746,17 @@ def test_event_generate_enter(self):
self.assertEqual(e.x_root, 100 + f.winfo_rootx())
self.assertEqual(e.y_root, 50 + f.winfo_rooty())
self.assertEqual(e.delta, 0)
self.assertEqual(e.user_data, '??')
self.assertEqual(e.detail, 'NotifyAncestor')
self.assertEqual(repr(e), '<Enter event focus=False x=100 y=50>')

f.event_generate('<Enter>', x=100, y=50, detail='NotifyPointer')
self.assertEqual(len(events), 2, events)
e = events[1]
self.assertIs(e.type, tkinter.EventType.Enter)
self.assertEqual(e.user_data, '??')
self.assertEqual(e.detail, 'NotifyPointer')

def test_event_generate_button_press(self):
f = tkinter.Frame(self.root, width=150, height=100)
f.pack()
Expand Down Expand Up @@ -774,6 +789,8 @@ def test_event_generate_button_press(self):
self.assertEqual(e.x_root, f.winfo_rootx() + 100)
self.assertEqual(e.y_root, f.winfo_rooty() + 50)
self.assertEqual(e.delta, 0)
self.assertEqual(e.user_data, '??')
self.assertEqual(e.detail, '??')
self.assertEqual(repr(e), '<ButtonPress event num=1 x=100 y=50>')

def test_event_generate_motion(self):
Expand Down Expand Up @@ -808,6 +825,8 @@ def test_event_generate_motion(self):
self.assertEqual(e.x_root, f.winfo_rootx() + 100)
self.assertEqual(e.y_root, f.winfo_rooty() + 50)
self.assertEqual(e.delta, 0)
self.assertEqual(e.user_data, '??')
self.assertEqual(e.detail, '??')
self.assertEqual(repr(e), '<Motion event state=Button1 x=100 y=50>')

def test_event_generate_mouse_wheel(self):
Expand Down Expand Up @@ -842,9 +861,11 @@ def test_event_generate_mouse_wheel(self):
self.assertEqual(e.x_root, f.winfo_rootx() + 100)
self.assertEqual(e.y_root, f.winfo_rooty() + 50)
self.assertEqual(e.delta, -5)
self.assertEqual(e.user_data, '??')
self.assertEqual(e.detail, '??')
self.assertEqual(repr(e), '<MouseWheel event delta=-5 x=100 y=50>')

def test_generate_event_virtual_event(self):
def test_event_generate_virtual_event(self):
f = tkinter.Frame(self.root, width=150, height=100)
f.pack()
self.root.wait_visibility() # needed on Windows
Expand Down Expand Up @@ -876,9 +897,18 @@ def test_generate_event_virtual_event(self):
self.assertEqual(e.x_root, f.winfo_rootx() + 50)
self.assertEqual(e.y_root, -1)
self.assertEqual(e.delta, 0)
self.assertEqual(e.user_data, '')
self.assertEqual(e.detail, '??')
self.assertEqual(repr(e),
f"<VirtualEvent event x=50 y=0>")

f.event_generate('<<Spam>>', data='spam')
self.assertEqual(len(events), 2, events)
e = events[1]
self.assertIs(e.type, tkinter.EventType.VirtualEvent)
self.assertEqual(e.user_data, 'spam')
self.assertEqual(e.detail, '??')


class BindTest(AbstractTkTest, unittest.TestCase):

Expand Down
21 changes: 17 additions & 4 deletions Lib/tkinter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,10 @@ class Event:
type - type of the event as a number
widget - widget in which the event occurred
delta - delta of wheel movement (MouseWheel)
detail - certain fixed strings (see Tcl/Tk documentation)
(Enter, Leave, FocusIn, FocusOut, ConfigureRequest)
user_data - data string which was passed to event_generate() or empty
string (VirtualEvent)
"""

def __repr__(self):
Expand Down Expand Up @@ -1538,7 +1542,7 @@ def bind(self, sequence=None, func=None, add=None):
<Alt-A> for pressing A and the Alt key (KeyPress can be omitted).
An event pattern can also be a virtual event of the form
<<AString>> where AString can be arbitrary. This
event can be generated by event_generate.
event can be generated by event_generate().
If events are concatenated they must appear shortly
after each other.
Expand Down Expand Up @@ -1723,7 +1727,7 @@ def _root(self):
w = self
while w.master is not None: w = w.master
return w
_subst_format = ('%#', '%b', '%f', '%h', '%k',
_subst_format = ('%#', '%b', '%d', '%f', '%h', '%k',
'%s', '%t', '%w', '%x', '%y',
'%A', '%E', '%K', '%N', '%W', '%T', '%X', '%Y', '%D')
_subst_format_str = " ".join(_subst_format)
Expand All @@ -1744,11 +1748,14 @@ def getint_event(s):
if any(isinstance(s, tuple) for s in args):
args = [s[0] if isinstance(s, tuple) and len(s) == 1 else s
for s in args]
nsign, b, f, h, k, s, t, w, x, y, A, E, K, N, W, T, X, Y, D = args
# Missing: (a, c, d, m, o, v, B, R)
nsign, b, d, f, h, k, s, t, w, x, y, A, E, K, N, W, T, X, Y, D = args
# Missing: (a, c, m, o, v, B, R)
e = Event()
# serial field: valid for all events
# number of button: ButtonPress and ButtonRelease events only
# detail: for Enter, Leave, FocusIn, FocusOut and ConfigureRequest
# events certain fixed strings (see Tcl/Tk documentation)
# user_data: data string from a virtual event or an empty string
# height field: Configure, ConfigureRequest, Create,
# ResizeRequest, and Expose events only
# keycode field: KeyPress and KeyRelease events only
Expand All @@ -1762,6 +1769,12 @@ def getint_event(s):
# KeyRelease, and Motion events
e.serial = getint(nsign)
e.num = getint_event(b)
if T == EventType.VirtualEvent:
e.user_data = d
e.detail = '??'
else:
e.user_data = '??'
e.detail = d
try: e.focus = getboolean(f)
except TclError: pass
e.height = getint_event(h)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add support for user data of Tk virtual events and detail for
``Enter``, ``Leave``, ``FocusIn``, ``FocusOut``, and
``ConfigureRequest`` events to :mod:`tkinter`.
Loading