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 @@ -1544,7 +1544,7 @@ Base and mixin classes
This is typically used to wait for the user to finish interacting with a
dialog box.

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

Wait until the visibility state of *window* changes, for example when it
first appears on the screen, continuing to process events in the
Expand All @@ -1553,6 +1553,13 @@ Base and mixin classes
This is typically used to wait for a newly created window to become
visible before acting on it.

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

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

The methods with the ``focus_`` prefix manage the keyboard focus.

.. method:: focus_set()
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_visibility` 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:`86657`.)

xml
---

Expand Down
16 changes: 16 additions & 0 deletions 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 @@ -611,6 +612,21 @@ def test_wait_window(self):
self.root.wait_window(top) # Returns once the window is destroyed.
self.assertFalse(top.winfo_exists())

def test_wait_visibility_timeout(self):
# The widget becomes viewable before the timeout elapses.
top = tkinter.Toplevel(self.root)
self.assertIs(
self.root.wait_visibility(top, timeout=support.SHORT_TIMEOUT), True)
self.assertTrue(top.winfo_viewable())
# The widget never becomes viewable: give up instead of blocking.
hidden = tkinter.Toplevel(self.root)
hidden.withdraw()
start = time.monotonic()
self.assertIs(self.root.wait_visibility(hidden, timeout=0.2), False)
self.assertGreaterEqual(time.monotonic() - start, 0.2)
self.assertFalse(hidden.winfo_viewable())
hidden.destroy()

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

Expand Down
41 changes: 38 additions & 3 deletions Lib/tkinter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -834,14 +834,49 @@ def wait_window(self, window=None):
window = self
self.tk.call('tkwait', 'window', window._w)

def wait_visibility(self, window=None):
def wait_visibility(self, window=None, *, timeout=None):
"""Wait until the visibility of a WIDGET changes
(e.g. it appears).

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 once the widget is viewable, or False if the
timeout elapsed before that (or the widget was destroyed while
waiting). Without a timeout the call blocks until the visibility of
the widget changes and always returns True."""
if window is None:
window = self
self.tk.call('tkwait', 'visibility', window._w)
if timeout is None:
self.tk.call('tkwait', 'visibility', window._w)
return True

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

def setvar(self, name, value):
"""Set Tcl variable NAME to VALUE."""
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_visibility`.
If the widget does not become viewable within the given time, the call
returns ``False`` instead of blocking indefinitely.
Loading