Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 8122174

Browse files
Issue #22115: Added methods trace_add, trace_remove and trace_info in the
tkinter.Variable class. They replace old methods trace_variable, trace, trace_vdelete and trace_vinfo that use obsolete Tcl commands and might not work in future versions of Tcl.
1 parent 523ccd1 commit 8122174

5 files changed

Lines changed: 219 additions & 35 deletions

File tree

Doc/whatsnew/3.6.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,19 @@ telnetlib
391391
Stéphane Wirtel in :issue:`25485`).
392392

393393

394+
tkinter
395+
-------
396+
397+
Added methods :meth:`~tkinter.Variable.trace_add`,
398+
:meth:`~tkinter.Variable.trace_remove` and :meth:`~tkinter.Variable.trace_info`
399+
in the :class:`tkinter.Variable` class. They replace old methods
400+
:meth:`~tkinter.Variable.trace_variable`, :meth:`~tkinter.Variable.trace`,
401+
:meth:`~tkinter.Variable.trace_vdelete` and
402+
:meth:`~tkinter.Variable.trace_vinfo` that use obsolete Tcl commands and might
403+
not work in future versions of Tcl.
404+
(Contributed by Serhiy Storchaka in :issue:`22115`).
405+
406+
394407
typing
395408
------
396409

Lib/idlelib/configdialog.py

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -465,24 +465,24 @@ def CreatePageGeneral(self):
465465
return frame
466466

467467
def AttachVarCallbacks(self):
468-
self.fontSize.trace_variable('w', self.VarChanged_font)
469-
self.fontName.trace_variable('w', self.VarChanged_font)
470-
self.fontBold.trace_variable('w', self.VarChanged_font)
471-
self.spaceNum.trace_variable('w', self.VarChanged_spaceNum)
472-
self.colour.trace_variable('w', self.VarChanged_colour)
473-
self.builtinTheme.trace_variable('w', self.VarChanged_builtinTheme)
474-
self.customTheme.trace_variable('w', self.VarChanged_customTheme)
475-
self.themeIsBuiltin.trace_variable('w', self.VarChanged_themeIsBuiltin)
476-
self.highlightTarget.trace_variable('w', self.VarChanged_highlightTarget)
477-
self.keyBinding.trace_variable('w', self.VarChanged_keyBinding)
478-
self.builtinKeys.trace_variable('w', self.VarChanged_builtinKeys)
479-
self.customKeys.trace_variable('w', self.VarChanged_customKeys)
480-
self.keysAreBuiltin.trace_variable('w', self.VarChanged_keysAreBuiltin)
481-
self.winWidth.trace_variable('w', self.VarChanged_winWidth)
482-
self.winHeight.trace_variable('w', self.VarChanged_winHeight)
483-
self.startupEdit.trace_variable('w', self.VarChanged_startupEdit)
484-
self.autoSave.trace_variable('w', self.VarChanged_autoSave)
485-
self.encoding.trace_variable('w', self.VarChanged_encoding)
468+
self.fontSize.trace_add('write', self.VarChanged_font)
469+
self.fontName.trace_add('write', self.VarChanged_font)
470+
self.fontBold.trace_add('write', self.VarChanged_font)
471+
self.spaceNum.trace_add('write', self.VarChanged_spaceNum)
472+
self.colour.trace_add('write', self.VarChanged_colour)
473+
self.builtinTheme.trace_add('write', self.VarChanged_builtinTheme)
474+
self.customTheme.trace_add('write', self.VarChanged_customTheme)
475+
self.themeIsBuiltin.trace_add('write', self.VarChanged_themeIsBuiltin)
476+
self.highlightTarget.trace_add('write', self.VarChanged_highlightTarget)
477+
self.keyBinding.trace_add('write', self.VarChanged_keyBinding)
478+
self.builtinKeys.trace_add('write', self.VarChanged_builtinKeys)
479+
self.customKeys.trace_add('write', self.VarChanged_customKeys)
480+
self.keysAreBuiltin.trace_add('write', self.VarChanged_keysAreBuiltin)
481+
self.winWidth.trace_add('write', self.VarChanged_winWidth)
482+
self.winHeight.trace_add('write', self.VarChanged_winHeight)
483+
self.startupEdit.trace_add('write', self.VarChanged_startupEdit)
484+
self.autoSave.trace_add('write', self.VarChanged_autoSave)
485+
self.encoding.trace_add('write', self.VarChanged_encoding)
486486

487487
def remove_var_callbacks(self):
488488
"Remove callbacks to prevent memory leaks."
@@ -493,7 +493,7 @@ def remove_var_callbacks(self):
493493
self.keyBinding, self.builtinKeys, self.customKeys,
494494
self.keysAreBuiltin, self.winWidth, self.winHeight,
495495
self.startupEdit, self.autoSave, self.encoding,):
496-
var.trace_vdelete('w', var.trace_vinfo()[0][1])
496+
var.trace_remove('write', var.trace_info()[0][1])
497497

498498
def VarChanged_font(self, *params):
499499
'''When one font attribute changes, save them all, as they are

Lib/tkinter/__init__.py

Lines changed: 82 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -343,16 +343,9 @@ def set(self, value):
343343
def get(self):
344344
"""Return value of variable."""
345345
return self._tk.globalgetvar(self._name)
346-
def trace_variable(self, mode, callback):
347-
"""Define a trace callback for the variable.
348346

349-
MODE is one of "r", "w", "u" for read, write, undefine.
350-
CALLBACK must be a function which is called when
351-
the variable is read, written or undefined.
352-
353-
Return the name of the callback.
354-
"""
355-
f = CallWrapper(callback, None, self).__call__
347+
def _register(self, callback):
348+
f = CallWrapper(callback, None, self._root).__call__
356349
cbname = repr(id(f))
357350
try:
358351
callback = callback.__func__
@@ -366,25 +359,99 @@ def trace_variable(self, mode, callback):
366359
if self._tclCommands is None:
367360
self._tclCommands = []
368361
self._tclCommands.append(cbname)
362+
return cbname
363+
364+
def trace_add(self, mode, callback):
365+
"""Define a trace callback for the variable.
366+
367+
Mode is one of "read", "write", "unset", or a list or tuple of
368+
such strings.
369+
Callback must be a function which is called when the variable is
370+
read, written or unset.
371+
372+
Return the name of the callback.
373+
"""
374+
cbname = self._register(callback)
375+
self._tk.call('trace', 'add', 'variable',
376+
self._name, mode, (cbname,))
377+
return cbname
378+
379+
def trace_remove(self, mode, cbname):
380+
"""Delete the trace callback for a variable.
381+
382+
Mode is one of "read", "write", "unset" or a list or tuple of
383+
such strings. Must be same as were specified in trace_add().
384+
cbname is the name of the callback returned from trace_add().
385+
"""
386+
self._tk.call('trace', 'remove', 'variable',
387+
self._name, mode, cbname)
388+
for m, ca in self.trace_info():
389+
if self._tk.splitlist(ca)[0] == cbname:
390+
break
391+
else:
392+
self._tk.deletecommand(cbname)
393+
try:
394+
self._tclCommands.remove(cbname)
395+
except ValueError:
396+
pass
397+
398+
def trace_info(self):
399+
"""Return all trace callback information."""
400+
splitlist = self._tk.splitlist
401+
return [(splitlist(k), v) for k, v in map(splitlist,
402+
splitlist(self._tk.call('trace', 'info', 'variable', self._name)))]
403+
404+
def trace_variable(self, mode, callback):
405+
"""Define a trace callback for the variable.
406+
407+
MODE is one of "r", "w", "u" for read, write, undefine.
408+
CALLBACK must be a function which is called when
409+
the variable is read, written or undefined.
410+
411+
Return the name of the callback.
412+
413+
This deprecated method wraps a deprecated Tcl method that will
414+
likely be removed in the future. Use trace_add() instead.
415+
"""
416+
# TODO: Add deprecation warning
417+
cbname = self._register(callback)
369418
self._tk.call("trace", "variable", self._name, mode, cbname)
370419
return cbname
420+
371421
trace = trace_variable
422+
372423
def trace_vdelete(self, mode, cbname):
373424
"""Delete the trace callback for a variable.
374425
375426
MODE is one of "r", "w", "u" for read, write, undefine.
376427
CBNAME is the name of the callback returned from trace_variable or trace.
428+
429+
This deprecated method wraps a deprecated Tcl method that will
430+
likely be removed in the future. Use trace_remove() instead.
377431
"""
432+
# TODO: Add deprecation warning
378433
self._tk.call("trace", "vdelete", self._name, mode, cbname)
379-
self._tk.deletecommand(cbname)
380-
try:
381-
self._tclCommands.remove(cbname)
382-
except ValueError:
383-
pass
434+
cbname = self._tk.splitlist(cbname)[0]
435+
for m, ca in self.trace_info():
436+
if self._tk.splitlist(ca)[0] == cbname:
437+
break
438+
else:
439+
self._tk.deletecommand(cbname)
440+
try:
441+
self._tclCommands.remove(cbname)
442+
except ValueError:
443+
pass
444+
384445
def trace_vinfo(self):
385-
"""Return all trace callback information."""
446+
"""Return all trace callback information.
447+
448+
This deprecated method wraps a deprecated Tcl method that will
449+
likely be removed in the future. Use trace_info() instead.
450+
"""
451+
# TODO: Add deprecation warning
386452
return [self._tk.splitlist(x) for x in self._tk.splitlist(
387453
self._tk.call("trace", "vinfo", self._name))]
454+
388455
def __eq__(self, other):
389456
"""Comparison for equality (==).
390457

Lib/tkinter/test/test_tkinter/test_variables.py

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import unittest
2-
2+
import gc
33
from tkinter import (Variable, StringVar, IntVar, DoubleVar, BooleanVar, Tcl,
44
TclError)
55

@@ -87,6 +87,105 @@ def test_initialize(self):
8787
v.set("value")
8888
self.assertTrue(v.side_effect)
8989

90+
def test_trace_old(self):
91+
# Old interface
92+
v = Var(self.root)
93+
vname = str(v)
94+
trace = []
95+
def read_tracer(*args):
96+
trace.append(('read',) + args)
97+
def write_tracer(*args):
98+
trace.append(('write',) + args)
99+
cb1 = v.trace_variable('r', read_tracer)
100+
cb2 = v.trace_variable('wu', write_tracer)
101+
self.assertEqual(sorted(v.trace_vinfo()), [('r', cb1), ('wu', cb2)])
102+
self.assertEqual(trace, [])
103+
104+
v.set('spam')
105+
self.assertEqual(trace, [('write', vname, '', 'w')])
106+
107+
trace = []
108+
v.get()
109+
self.assertEqual(trace, [('read', vname, '', 'r')])
110+
111+
trace = []
112+
info = sorted(v.trace_vinfo())
113+
v.trace_vdelete('w', cb1) # Wrong mode
114+
self.assertEqual(sorted(v.trace_vinfo()), info)
115+
with self.assertRaises(TclError):
116+
v.trace_vdelete('r', 'spam') # Wrong command name
117+
self.assertEqual(sorted(v.trace_vinfo()), info)
118+
v.trace_vdelete('r', (cb1, 43)) # Wrong arguments
119+
self.assertEqual(sorted(v.trace_vinfo()), info)
120+
v.get()
121+
self.assertEqual(trace, [('read', vname, '', 'r')])
122+
123+
trace = []
124+
v.trace_vdelete('r', cb1)
125+
self.assertEqual(v.trace_vinfo(), [('wu', cb2)])
126+
v.get()
127+
self.assertEqual(trace, [])
128+
129+
trace = []
130+
del write_tracer
131+
gc.collect()
132+
v.set('eggs')
133+
self.assertEqual(trace, [('write', vname, '', 'w')])
134+
135+
trace = []
136+
del v
137+
gc.collect()
138+
self.assertEqual(trace, [('write', vname, '', 'u')])
139+
140+
def test_trace(self):
141+
v = Var(self.root)
142+
vname = str(v)
143+
trace = []
144+
def read_tracer(*args):
145+
trace.append(('read',) + args)
146+
def write_tracer(*args):
147+
trace.append(('write',) + args)
148+
tr1 = v.trace_add('read', read_tracer)
149+
tr2 = v.trace_add(['write', 'unset'], write_tracer)
150+
self.assertEqual(sorted(v.trace_info()), [
151+
(('read',), tr1),
152+
(('write', 'unset'), tr2)])
153+
self.assertEqual(trace, [])
154+
155+
v.set('spam')
156+
self.assertEqual(trace, [('write', vname, '', 'write')])
157+
158+
trace = []
159+
v.get()
160+
self.assertEqual(trace, [('read', vname, '', 'read')])
161+
162+
trace = []
163+
info = sorted(v.trace_info())
164+
v.trace_remove('write', tr1) # Wrong mode
165+
self.assertEqual(sorted(v.trace_info()), info)
166+
with self.assertRaises(TclError):
167+
v.trace_remove('read', 'spam') # Wrong command name
168+
self.assertEqual(sorted(v.trace_info()), info)
169+
v.get()
170+
self.assertEqual(trace, [('read', vname, '', 'read')])
171+
172+
trace = []
173+
v.trace_remove('read', tr1)
174+
self.assertEqual(v.trace_info(), [(('write', 'unset'), tr2)])
175+
v.get()
176+
self.assertEqual(trace, [])
177+
178+
trace = []
179+
del write_tracer
180+
gc.collect()
181+
v.set('eggs')
182+
self.assertEqual(trace, [('write', vname, '', 'write')])
183+
184+
trace = []
185+
del v
186+
gc.collect()
187+
self.assertEqual(trace, [('write', vname, '', 'unset')])
188+
90189

91190
class TestStringVar(TestBase):
92191

Misc/NEWS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ What's New in Python 3.6.0 alpha 3
1010
Library
1111
-------
1212

13+
- Issue #22115: Added methods trace_add, trace_remove and trace_info in the
14+
tkinter.Variable class. They replace old methods trace_variable, trace,
15+
trace_vdelete and trace_vinfo that use obsolete Tcl commands and might
16+
not work in future versions of Tcl.
17+
1318
- Issue #26243: Only the level argument to zlib.compress() is keyword argument
1419
now. The first argument is positional-only.
1520

0 commit comments

Comments
 (0)