I'm trying to subclass Tk
where it pauses audio if and only if the entire application loses focus (i.e. the Tk
instance loses focus and focus wasn't passed to a Toplevel
or messagebox
widget).
I managed to get it sort-of working with a 'hack' - when a messagebox
is open, it is the last child of the Tk
instance and also has no children. Here is what I've tried:
class TkWin(Tk):
def __init__(self, title):
super().__init__(className=title, baseName=title)
self.bind('<FocusOut>', lambda event: self.pause_audio())
def pause_audio(self):
if self.has_focus():
return
else:
pass
# pause the audio
def has_focus(self):
children = self.winfo_children()
if any(isinstance(x, Toplevel) for x in children):
return True
if len(children[-1].winfo_children()) == 0:
return True
return False
win = TkWin('test')
win.mainloop()
The above solution solves the problem of not pausing the audio if a Toplevel
or messagebox
is opened. However it fails if a Toplevel
or messagebox
widget is opened and then another window is given focus.
(I am aware this would fail if you opened a messagebox
and then created a container with some widgets inside it, but it works for how I build my applications)
Is there a better way to go about this?
Tried @Atlas345 's solution and am met with this error when trying to open a messagebox
:
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib/python3.6/tkinter/__init__.py", line 1705, in __call__
return self.func(*args)
File "/usr/lib/python3.6/tkinter/__init__.py", line 749, in callit
func(*args)
File "/home/inkblot/Desktop/win test.py", line 12, in check
if self.focus_get() is None:
File "/usr/lib/python3.6/tkinter/__init__.py", line 699, in focus_get
return self._nametowidget(name)
File "/usr/lib/python3.6/tkinter/__init__.py", line 1353, in nametowidget
w = w.children[n]
KeyError: '__tk__messagebox'
EDIT:
Trying to override the focus_get
method gets me somewhere but I have one last issue:
from tkinter import messagebox, Tk, Button, Label, Toplevel
class TkWin(Tk):
def __init__(self, title):
super().__init__(className=title, baseName=title)
self.focus_id = self.after(10, self.has_focus)
def focus_get(self):
try:
return super().focus_get() is not None
except KeyError:
print('messagebox is open')
return True
def has_focus(self):
print('resume audio') if self.focus_get() else print('pause audio')
self.focus_id = self.after(50, self.has_focus)
def popupmsg(msg):
popup = Toplevel(win)
popup.wm_title("Warning!")
Label(popup, text=msg).pack(side="top", fill='both', expand=True)
Button(popup, text="okay", command = popup.destroy).pack()
win = TkWin('test')
Button(win, text='top', command=lambda: Toplevel(win)).pack()
Button(win, text='msg', command=lambda: messagebox.showinfo('title', 'msg')).pack()
Button(win, text='popup', command= lambda: popupmsg('I dare you!')).pack()
win.mainloop()
This lets the above error fail silently but I have now noticed that the focus_get()
method returns None
if the topmost widget does not have focus. This means if any type of popup is open and I then click on the root window (or any popup that was not the most recently created), it assumes the entire application does not have focus and hence pauses the audio which is undesirable to say the least.