]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-38464: Make tkinter nametowidget() work with cloned menus (GH-152336) ...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Fri, 26 Jun 2026 23:27:42 +0000 (01:27 +0200)
committerGitHub <noreply@github.com>
Fri, 26 Jun 2026 23:27:42 +0000 (23:27 +0000)
Map the auto-generated name of a cloned menu (a menu used as a menubar
or a cascade) back to the original widget instead of raising KeyError.
(cherry picked from commit 5c3555bdc56a8e110a7d366f8ac0a93cd082e90f)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Lib/test/test_tkinter/test_misc.py
Lib/tkinter/__init__.py
Misc/NEWS.d/next/Library/2026-06-26-15-00-00.gh-issue-38464.yDPjKH.rst [new file with mode: 0644]

index b37aa4d1606ccc717c171f7b59445d561dbc760f..4434ed08270d76113f62545daae40ac3e118e38d 100644 (file)
@@ -373,6 +373,32 @@ class MiscTest(AbstractTkTest, unittest.TestCase):
         self.assertIs(self.root.nametowidget(str(b)), b)
         self.assertRaises(KeyError, self.root.nametowidget, '.nonexistent')
 
+    def test_nametowidget_menu_clone(self):
+        # A menu used as a menubar or cascade is cloned by Tk under an
+        # auto-generated name (each path component is the original name
+        # prefixed with one or more '#' clone markers).  nametowidget()
+        # maps such a name back to the original widget (gh-38464).
+        menubar = tkinter.Menu(self.root)
+        filemenu = tkinter.Menu(menubar, tearoff=0)
+        menubar.add_cascade(label='File', menu=filemenu)
+        submenu = tkinter.Menu(filemenu, tearoff=0)
+        filemenu.add_cascade(label='More', menu=submenu)
+        self.root['menu'] = menubar
+        self.root.update_idletasks()
+
+        originals = {menubar, filemenu, submenu}
+        clones = []
+        def collect(parent):
+            for name in self.root.tk.splitlist(
+                    self.root.tk.call('winfo', 'children', parent)):
+                clones.append(name)
+                collect(name)
+        collect('.')
+        # Every menu (originals and clones) resolves to an original widget.
+        self.assertTrue(any('#' in name for name in clones))
+        for name in clones:
+            self.assertIn(self.root.nametowidget(name), originals)
+
     def test_focus_methods(self):
         f = tkinter.Frame(self.root, width=150, height=100)
         f.pack()
index 637664ffa08846b31dfd383ba99fbce0c745d533..7b3f4bea7ce2c862b2fe64f0d87c1469fad754d3 100644 (file)
@@ -1674,7 +1674,16 @@ class Misc:
         for n in name:
             if not n:
                 break
-            w = w.children[n]
+            try:
+                w = w.children[n]
+            except KeyError:
+                # Menu clones (a menu used as a menubar or a cascade) get
+                # auto-generated names where each path component is the
+                # original name prefixed with one or more '#' clone markers.
+                # Map such a name back to the original widget.
+                if not n.startswith('#'):
+                    raise
+                w = w.children[n.rsplit('#', 1)[-1]]
 
         return w
 
diff --git a/Misc/NEWS.d/next/Library/2026-06-26-15-00-00.gh-issue-38464.yDPjKH.rst b/Misc/NEWS.d/next/Library/2026-06-26-15-00-00.gh-issue-38464.yDPjKH.rst
new file mode 100644 (file)
index 0000000..c1c272b
--- /dev/null
@@ -0,0 +1,3 @@
+:meth:`!tkinter.Misc.nametowidget` now resolves the auto-generated names of
+cloned menus (a menu used as a menubar or a cascade) back to the original
+widget.