Skip to content

Commit 7e42fdd

Browse files
gh-113951: Tkinter: "tag_unbind(tag, sequence, funcid)" now only unbinds "funcid" (GH-113955)
Previously, "tag_unbind(tag, sequence, funcid)" methods of Text and Canvas widgets destroyed the current binding for "sequence", leaving "sequence" unbound, and deleted the "funcid" command. Now they remove only "funcid" from the binding for "sequence", keeping other commands, and delete the "funcid" command. They leave "sequence" unbound only if "funcid" was the last bound command.
1 parent 3ddc515 commit 7e42fdd

File tree

3 files changed

+117
-11
lines changed

3 files changed

+117
-11
lines changed

Lib/test/test_tkinter/test_misc.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,101 @@ def test3(e): pass
706706
self.assertCommandExist(funcid2)
707707
self.assertCommandExist(funcid3)
708708

709+
def _test_tag_bind(self, w):
710+
tag = 'sel'
711+
event = '<Control-Alt-Key-a>'
712+
w.pack()
713+
self.assertRaises(TypeError, w.tag_bind)
714+
tag_bind = w._tag_bind if isinstance(w, tkinter.Text) else w.tag_bind
715+
if isinstance(w, tkinter.Text):
716+
self.assertRaises(TypeError, w.tag_bind, tag)
717+
self.assertRaises(TypeError, w.tag_bind, tag, event)
718+
self.assertEqual(tag_bind(tag), ())
719+
self.assertEqual(tag_bind(tag, event), '')
720+
def test1(e): pass
721+
def test2(e): pass
722+
723+
funcid = w.tag_bind(tag, event, test1)
724+
self.assertEqual(tag_bind(tag), (event,))
725+
script = tag_bind(tag, event)
726+
self.assertIn(funcid, script)
727+
self.assertCommandExist(funcid)
728+
729+
funcid2 = w.tag_bind(tag, event, test2, add=True)
730+
script = tag_bind(tag, event)
731+
self.assertIn(funcid, script)
732+
self.assertIn(funcid2, script)
733+
self.assertCommandExist(funcid)
734+
self.assertCommandExist(funcid2)
735+
736+
def _test_tag_unbind(self, w):
737+
tag = 'sel'
738+
event = '<Control-Alt-Key-b>'
739+
w.pack()
740+
tag_bind = w._tag_bind if isinstance(w, tkinter.Text) else w.tag_bind
741+
self.assertEqual(tag_bind(tag), ())
742+
self.assertEqual(tag_bind(tag, event), '')
743+
def test1(e): pass
744+
def test2(e): pass
745+
746+
funcid = w.tag_bind(tag, event, test1)
747+
funcid2 = w.tag_bind(tag, event, test2, add=True)
748+
749+
self.assertRaises(TypeError, w.tag_unbind, tag)
750+
w.tag_unbind(tag, event)
751+
self.assertEqual(tag_bind(tag, event), '')
752+
self.assertEqual(tag_bind(tag), ())
753+
754+
def _test_tag_bind_rebind(self, w):
755+
tag = 'sel'
756+
event = '<Control-Alt-Key-d>'
757+
w.pack()
758+
tag_bind = w._tag_bind if isinstance(w, tkinter.Text) else w.tag_bind
759+
self.assertEqual(tag_bind(tag), ())
760+
self.assertEqual(tag_bind(tag, event), '')
761+
def test1(e): pass
762+
def test2(e): pass
763+
def test3(e): pass
764+
765+
funcid = w.tag_bind(tag, event, test1)
766+
funcid2 = w.tag_bind(tag, event, test2, add=True)
767+
script = tag_bind(tag, event)
768+
self.assertIn(funcid2, script)
769+
self.assertIn(funcid, script)
770+
self.assertCommandExist(funcid)
771+
self.assertCommandExist(funcid2)
772+
773+
funcid3 = w.tag_bind(tag, event, test3)
774+
script = tag_bind(tag, event)
775+
self.assertNotIn(funcid, script)
776+
self.assertNotIn(funcid2, script)
777+
self.assertIn(funcid3, script)
778+
self.assertCommandExist(funcid3)
779+
780+
def test_canvas_tag_bind(self):
781+
c = tkinter.Canvas(self.frame)
782+
self._test_tag_bind(c)
783+
784+
def test_canvas_tag_unbind(self):
785+
c = tkinter.Canvas(self.frame)
786+
self._test_tag_unbind(c)
787+
788+
def test_canvas_tag_bind_rebind(self):
789+
c = tkinter.Canvas(self.frame)
790+
self._test_tag_bind_rebind(c)
791+
792+
def test_text_tag_bind(self):
793+
t = tkinter.Text(self.frame)
794+
self._test_tag_bind(t)
795+
796+
def test_text_tag_unbind(self):
797+
t = tkinter.Text(self.frame)
798+
self._test_tag_unbind(t)
799+
800+
def test_text_tag_bind_rebind(self):
801+
t = tkinter.Text(self.frame)
802+
self._test_tag_bind_rebind(t)
803+
709804
def test_bindtags(self):
710805
f = self.frame
711806
self.assertEqual(self.root.bindtags(), ('.', 'Tk', 'all'))

Lib/tkinter/__init__.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1537,16 +1537,19 @@ def unbind(self, sequence, funcid=None):
15371537
Otherwise destroy the current binding for SEQUENCE, leaving SEQUENCE
15381538
unbound.
15391539
"""
1540+
self._unbind(('bind', self._w, sequence), funcid)
1541+
1542+
def _unbind(self, what, funcid=None):
15401543
if funcid is None:
1541-
self.tk.call('bind', self._w, sequence, '')
1544+
self.tk.call(*what, '')
15421545
else:
1543-
lines = self.tk.call('bind', self._w, sequence).split('\n')
1546+
lines = self.tk.call(what).split('\n')
15441547
prefix = f'if {{"[{funcid} '
15451548
keep = '\n'.join(line for line in lines
15461549
if not line.startswith(prefix))
15471550
if not keep.strip():
15481551
keep = ''
1549-
self.tk.call('bind', self._w, sequence, keep)
1552+
self.tk.call(*what, keep)
15501553
self.deletecommand(funcid)
15511554

15521555
def bind_all(self, sequence=None, func=None, add=None):
@@ -1558,7 +1561,7 @@ def bind_all(self, sequence=None, func=None, add=None):
15581561

15591562
def unbind_all(self, sequence):
15601563
"""Unbind for all widgets for event SEQUENCE all functions."""
1561-
self.tk.call('bind', 'all' , sequence, '')
1564+
self._root()._unbind(('bind', 'all', sequence))
15621565

15631566
def bind_class(self, className, sequence=None, func=None, add=None):
15641567
"""Bind to widgets with bindtag CLASSNAME at event
@@ -1573,7 +1576,7 @@ def bind_class(self, className, sequence=None, func=None, add=None):
15731576
def unbind_class(self, className, sequence):
15741577
"""Unbind for all widgets with bindtag CLASSNAME for event SEQUENCE
15751578
all functions."""
1576-
self.tk.call('bind', className , sequence, '')
1579+
self._root()._unbind(('bind', className, sequence))
15771580

15781581
def mainloop(self, n=0):
15791582
"""Call the mainloop of Tk."""
@@ -2885,9 +2888,7 @@ def bbox(self, *args):
28852888
def tag_unbind(self, tagOrId, sequence, funcid=None):
28862889
"""Unbind for all items with TAGORID for event SEQUENCE the
28872890
function identified with FUNCID."""
2888-
self.tk.call(self._w, 'bind', tagOrId, sequence, '')
2889-
if funcid:
2890-
self.deletecommand(funcid)
2891+
self._unbind((self._w, 'bind', tagOrId, sequence), funcid)
28912892

28922893
def tag_bind(self, tagOrId, sequence=None, func=None, add=None):
28932894
"""Bind to all items with TAGORID at event SEQUENCE a call to function FUNC.
@@ -3997,9 +3998,7 @@ def tag_add(self, tagName, index1, *args):
39973998
def tag_unbind(self, tagName, sequence, funcid=None):
39983999
"""Unbind for all characters with TAGNAME for event SEQUENCE the
39994000
function identified with FUNCID."""
4000-
self.tk.call(self._w, 'tag', 'bind', tagName, sequence, '')
4001-
if funcid:
4002-
self.deletecommand(funcid)
4001+
return self._unbind((self._w, 'tag', 'bind', tagName, sequence), funcid)
40034002

40044003
def tag_bind(self, tagName, sequence, func, add=None):
40054004
"""Bind to all characters with TAGNAME at event SEQUENCE a call to function FUNC.
@@ -4010,6 +4009,11 @@ def tag_bind(self, tagName, sequence, func, add=None):
40104009
return self._bind((self._w, 'tag', 'bind', tagName),
40114010
sequence, func, add)
40124011

4012+
def _tag_bind(self, tagName, sequence=None, func=None, add=None):
4013+
# For tests only
4014+
return self._bind((self._w, 'tag', 'bind', tagName),
4015+
sequence, func, add)
4016+
40134017
def tag_cget(self, tagName, option):
40144018
"""Return the value of OPTION for tag TAGNAME."""
40154019
if option[:1] != '-':
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Fix the behavior of ``tag_unbind()`` methods of :class:`tkinter.Text` and
2+
:class:`tkinter.Canvas` classes with three arguments. Previously,
3+
``widget.tag_unbind(tag, sequence, funcid)`` destroyed the current binding
4+
for *sequence*, leaving *sequence* unbound, and deleted the *funcid*
5+
command. Now it removes only *funcid* from the binding for *sequence*,
6+
keeping other commands, and deletes the *funcid* command. It leaves
7+
*sequence* unbound only if *funcid* was the last bound command.

0 commit comments

Comments
 (0)