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
9 changes: 8 additions & 1 deletion Doc/library/tkinter.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1536,14 +1536,21 @@ Base and mixin classes

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

.. method:: wait_window(window=None)
.. method:: wait_window(window=None, *, timeout=None)

Wait until *window* is destroyed, continuing to process events in the
meantime.
If *window* is omitted, this widget is used.
This is typically used to wait for the user to finish interacting with a
dialog box.

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

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

.. method:: wait_visibility(window=None)

Wait until the visibility state of *window* changes, for example when it
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_window` 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:`100617`.)

xml
---

Expand Down
19 changes: 18 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 @@ -608,9 +609,25 @@ def test_wait_variable(self):
def test_wait_window(self):
top = tkinter.Toplevel(self.root)
self.root.after(1, top.destroy)
self.root.wait_window(top) # Returns once the window is destroyed.
# Returns once the window is destroyed.
self.assertIs(self.root.wait_window(top), True)
self.assertFalse(top.winfo_exists())

def test_wait_window_timeout(self):
# The window is destroyed before the timeout elapses.
top = tkinter.Toplevel(self.root)
self.root.after(1, top.destroy)
self.assertIs(
self.root.wait_window(top, timeout=support.SHORT_TIMEOUT), True)
self.assertFalse(top.winfo_exists())
# The window outlives the timeout: give up instead of blocking.
top = tkinter.Toplevel(self.root)
start = time.monotonic()
self.assertIs(self.root.wait_window(top, timeout=0.2), False)
self.assertGreaterEqual(time.monotonic() - start, 0.2)
self.assertTrue(top.winfo_exists())
top.destroy()

def test_tk_focusFollowsMouse(self):
self.root.tk_focusFollowsMouse() # No exception.

Expand Down
36 changes: 33 additions & 3 deletions Lib/tkinter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -826,13 +826,43 @@ def wait_variable(self, name):
self.tk.call('tkwait', 'variable', name)
waitvar = wait_variable # XXX b/w compat

def wait_window(self, window=None):
def wait_window(self, window=None, *, timeout=None):
"""Wait until a WIDGET is destroyed.

If no parameter is given self is used."""
If no parameter is given self is used.

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

if not window.winfo_exists():
return True
timed_out = []
# 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 timed_out:
self.tk.dooneevent(_tkinter.ALL_EVENTS)
if not window.winfo_exists():
return True
return False
except TclError:
# the widget or application was torn down
return True
finally:
# Cleanup may fail if the application was torn down.
try:
self.after_cancel(timer)
except TclError:
pass

def wait_visibility(self, window=None):
"""Wait until the visibility of a WIDGET changes
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_window`.
If the widget is not destroyed within the given time, the call returns
``False`` instead of blocking indefinitely.
Loading