Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions Doc/library/tkinter.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1524,18 +1524,25 @@ Base and mixin classes
This updates the display of windows, for example after geometry changes,
but does not process events caused by the user.

.. method:: waitvar(name)
.. method:: waitvar(name, *, timeout=None)
:no-typesetting:

.. method:: wait_variable(name)
.. method:: wait_variable(name, *, timeout=None)

Wait until the Tcl variable *name* is modified, continuing to process
events in the meantime so that the application stays responsive.
*name* is usually a :class:`Variable` instance, such as an
:class:`IntVar` or :class:`StringVar`.

If *timeout* is given, it is the maximum time to wait in seconds.
Return ``True`` if the variable was modified, or ``False`` if the timeout elapsed before that (or the application was destroyed while waiting).
Without a *timeout* the call blocks until the variable is modified and always returns ``True``.

:meth:`waitvar` is an alias of :meth:`!wait_variable`.

.. versionchanged:: next
Added the *timeout* parameter and the return value.

.. method:: wait_window(window=None)

Wait until *window* is destroyed, continuing to process events in the
Expand Down
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,11 @@ tkinter
ttk version, and accepts mappings of button options as *buttons* entries.
(Contributed by Serhiy Storchaka in :gh:`59396`.)

* The :meth:`~tkinter.Misc.wait_variable` method now accepts an optional
keyword-only *timeout* argument that bounds how long it waits, instead of
blocking indefinitely.
(Contributed by Serhiy Storchaka in :gh:`40440`.)

xml
---

Expand Down
18 changes: 17 additions & 1 deletion Lib/test/test_tkinter/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import platform
import sys
import textwrap
import time
import unittest
import weakref
import tkinter
Expand Down Expand Up @@ -600,11 +601,26 @@ def test_wait_variable(self):
var = tkinter.StringVar(self.root)
self.assertEqual(self.root.waitvar, self.root.wait_variable)
self.root.after(1, var.set, 'done')
self.root.wait_variable(var) # Returns once the variable is set.
self.assertIs(self.root.wait_variable(var), True) # Returns once set.
self.assertEqual(var.get(), 'done')
# The name is required (gh-152587).
self.assertRaises(TypeError, self.root.wait_variable)

def test_wait_variable_timeout(self):
var = tkinter.StringVar(self.root)
# The variable is set before the timeout elapses.
self.root.after(1, var.set, 'done')
self.assertIs(
self.root.wait_variable(var, timeout=support.SHORT_TIMEOUT), True)
self.assertEqual(var.get(), 'done')
# The variable is never set: give up when the timeout elapses instead
# of blocking forever (gh-40440).
var.set('')
start = time.monotonic()
self.assertIs(self.root.wait_variable(var, timeout=0.2), False)
self.assertGreaterEqual(time.monotonic() - start, 0.2)
self.assertEqual(var.get(), '')

def test_wait_window(self):
top = tkinter.Toplevel(self.root)
self.root.after(1, top.destroy)
Expand Down
56 changes: 53 additions & 3 deletions Lib/tkinter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -818,12 +818,62 @@ def tk_inactive(self, reset=False, *, displayof=0):
else:
return self.tk.getint(self.tk.call(args))

def wait_variable(self, name):
def wait_variable(self, name, *, timeout=None):
"""Wait until the variable is modified.

A parameter of type IntVar, StringVar, DoubleVar or
BooleanVar must be given."""
self.tk.call('tkwait', 'variable', name)
BooleanVar must be given.

If timeout is given, it specifies the maximum time to wait in
seconds. Return True if the variable was modified, or False if
the timeout elapsed before that (or the application was destroyed
while waiting). Without a timeout the call blocks until the
variable is modified and always returns True."""
if timeout is None:
self.tk.call('tkwait', 'variable', name)
return True

name = str(name)
done = []
timed_out = []
cb = self.register(lambda *args: done.append(True))
try:
self.tk.call('trace', 'add', 'variable', name,
('write', 'unset'), (cb,))
try:
# A one-shot timer guarantees that dooneevent() wakes up at the
# deadline even if no other event arrives.
timer = self.after(max(int(timeout * 1000), 1),
lambda: timed_out.append(True))
try:
while not done and not timed_out:
self.tk.dooneevent(_tkinter.ALL_EVENTS)
# Stop instead of blocking forever if the application was
# destroyed (which also cancels our timer) while waiting.
if not self.tk.getboolean(
self.tk.call('winfo', 'exists', '.')):
break
return bool(done)
except TclError:
# the interpreter or application was torn down
return bool(done)
finally:
# Cleanup may fail if the application was torn down.
try:
self.after_cancel(timer)
except TclError:
pass
finally:
try:
self.tk.call('trace', 'remove', 'variable', name,
('write', 'unset'), cb)
except TclError:
pass
finally:
try:
self.deletecommand(cb)
except TclError:
pass
waitvar = wait_variable # XXX b/w compat

def wait_window(self, window=None):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add an optional *timeout* parameter to :meth:`tkinter.Misc.wait_variable`.
If the variable is not modified within the given time, the call returns
``False`` instead of blocking indefinitely.
Loading