Skip to content

Commit f189bd3

Browse files
miss-islingtonserhiy-storchakaGiovaLomba
authored
[3.12] gh-75666: Tkinter: "unbind(sequence, funcid)" now only unbinds "funcid" (GH-111322) (GH-112802)
Previously, "widget.unbind(sequence, funcid)" destroyed the current binding for "sequence", leaving "sequence" unbound, and deleted the "funcid" command. Now it removes only "funcid" from the binding for "sequence", keeping other commands, and deletes the "funcid" command. It leaves "sequence" unbound only if "funcid" was the last bound command. (cherry picked from commit cc7e45c) Co-authored-by: Serhiy Storchaka <[email protected]> Co-authored-by: GiovanniL <[email protected]>
1 parent 399a3f2 commit f189bd3

File tree

3 files changed

+51
-11
lines changed

3 files changed

+51
-11
lines changed

Lib/test/test_tkinter/test_misc.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -425,26 +425,46 @@ def test2(e): pass
425425

426426
def test_unbind2(self):
427427
f = self.frame
428+
f.wait_visibility()
429+
f.focus_force()
430+
f.update_idletasks()
428431
event = '<Control-Alt-Key-c>'
429432
self.assertEqual(f.bind(), ())
430433
self.assertEqual(f.bind(event), '')
431-
def test1(e): pass
432-
def test2(e): pass
434+
def test1(e): events.append('a')
435+
def test2(e): events.append('b')
436+
def test3(e): events.append('c')
433437

434438
funcid = f.bind(event, test1)
435439
funcid2 = f.bind(event, test2, add=True)
440+
funcid3 = f.bind(event, test3, add=True)
441+
events = []
442+
f.event_generate(event)
443+
self.assertEqual(events, ['a', 'b', 'c'])
436444

437-
f.unbind(event, funcid)
445+
f.unbind(event, funcid2)
438446
script = f.bind(event)
439-
self.assertNotIn(funcid, script)
440-
self.assertCommandNotExist(funcid)
441-
self.assertCommandExist(funcid2)
447+
self.assertNotIn(funcid2, script)
448+
self.assertIn(funcid, script)
449+
self.assertIn(funcid3, script)
450+
self.assertEqual(f.bind(), (event,))
451+
self.assertCommandNotExist(funcid2)
452+
self.assertCommandExist(funcid)
453+
self.assertCommandExist(funcid3)
454+
events = []
455+
f.event_generate(event)
456+
self.assertEqual(events, ['a', 'c'])
442457

443-
f.unbind(event, funcid2)
458+
f.unbind(event, funcid)
459+
f.unbind(event, funcid3)
444460
self.assertEqual(f.bind(event), '')
445461
self.assertEqual(f.bind(), ())
446462
self.assertCommandNotExist(funcid)
447463
self.assertCommandNotExist(funcid2)
464+
self.assertCommandNotExist(funcid3)
465+
events = []
466+
f.event_generate(event)
467+
self.assertEqual(events, [])
448468

449469
# non-idempotent
450470
self.assertRaises(tkinter.TclError, f.unbind, event, funcid2)

Lib/tkinter/__init__.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1448,10 +1448,24 @@ def bind(self, sequence=None, func=None, add=None):
14481448
return self._bind(('bind', self._w), sequence, func, add)
14491449

14501450
def unbind(self, sequence, funcid=None):
1451-
"""Unbind for this widget for event SEQUENCE the
1452-
function identified with FUNCID."""
1453-
self.tk.call('bind', self._w, sequence, '')
1454-
if funcid:
1451+
"""Unbind for this widget the event SEQUENCE.
1452+
1453+
If FUNCID is given, only unbind the function identified with FUNCID
1454+
and also delete the corresponding Tcl command.
1455+
1456+
Otherwise destroy the current binding for SEQUENCE, leaving SEQUENCE
1457+
unbound.
1458+
"""
1459+
if funcid is None:
1460+
self.tk.call('bind', self._w, sequence, '')
1461+
else:
1462+
lines = self.tk.call('bind', self._w, sequence).split('\n')
1463+
prefix = f'if {{"[{funcid} '
1464+
keep = '\n'.join(line for line in lines
1465+
if not line.startswith(prefix))
1466+
if not keep.strip():
1467+
keep = ''
1468+
self.tk.call('bind', self._w, sequence, keep)
14551469
self.deletecommand(funcid)
14561470

14571471
def bind_all(self, sequence=None, func=None, add=None):
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Fix the behavior of :mod:`tkinter` widget's ``unbind()`` method with two
2+
arguments. Previously, ``widget.unbind(sequence, funcid)`` destroyed the
3+
current binding for *sequence*, leaving *sequence* unbound, and deleted the
4+
*funcid* command. Now it removes only *funcid* from the binding for
5+
*sequence*, keeping other commands, and deletes the *funcid* command. It
6+
leaves *sequence* unbound only if *funcid* was the last bound command.

0 commit comments

Comments
 (0)