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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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
Comment thread
serhiy-storchaka marked this conversation as resolved.
# 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 numberDiff line numberDiff 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