https://github.com/python/cpython/commit/f8ce51a52209aa04c781d20fe0778247df5a465b
commit: f8ce51a52209aa04c781d20fe0778247df5a465b
branch: main
author: Matthias Kievernagel <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-02-25T10:34:00+02:00
summary:

gh-47655: Add support for user data and detail of Tk events to tkinter (GH-7142)

Expose the %d substitution as the tkinter.Event attributes:

* "detail" for Enter, Leave, FocusIn, FocusOut, and ConfigureRequest events
* "user_data" for virtual events

Co-authored-by: Serhiy Storchaka <[email protected]>

files:
A Misc/NEWS.d/next/Library/2018-05-11-12-26-16.bpo-3405.CacMw9.rst
M Doc/whatsnew/3.15.rst
M Lib/test/test_tkinter/test_misc.py
M Lib/tkinter/__init__.py

diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst
index 816d45e0756824..37ebdfee7915fe 100644
--- a/Doc/whatsnew/3.15.rst
+++ b/Doc/whatsnew/3.15.rst
@@ -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:
 
diff --git a/Lib/test/test_tkinter/test_misc.py 
b/Lib/test/test_tkinter/test_misc.py
index a6ba55b3fcadb3..f017e94a8b3c22 100644
--- a/Lib/test/test_tkinter/test_misc.py
+++ b/Lib/test/test_tkinter/test_misc.py
@@ -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):
@@ -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):
@@ -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}>")
@@ -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()
@@ -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):
@@ -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):
@@ -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
@@ -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):
 
diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py
index d695e3ec9cb1b4..ba8365f56c37a7 100644
--- a/Lib/tkinter/__init__.py
+++ b/Lib/tkinter/__init__.py
@@ -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):
@@ -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.
 
@@ -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)
@@ -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
@@ -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)
diff --git a/Misc/NEWS.d/next/Library/2018-05-11-12-26-16.bpo-3405.CacMw9.rst 
b/Misc/NEWS.d/next/Library/2018-05-11-12-26-16.bpo-3405.CacMw9.rst
new file mode 100644
index 00000000000000..bf97bf0231fc0a
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2018-05-11-12-26-16.bpo-3405.CacMw9.rst
@@ -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`.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to