Reputation: 748
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.
Upvotes: 2
Views: 893
Reputation: 8072
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 think you get away with this:
import tkinter as tk
root = tk.Tk()
b = tk.Button(root, text='top', command= lambda: tk.Toplevel(root))
b.pack()
def check ():
condition = root.focus_get()
root.after(500, check)
if condition == None:
print('pause music')
pass
check()
root.mainloop()
Because root.focus_get() just returns None if all windows are minimized or loses focus. Most other methods I've tried was returning None as soon as the root was minimized.
This method also fails by a builtin messagebox, because these aren't related to the root window. Thats why you would need to build your own like:
def popupmsg(msg):
popup = tk.Toplevel()
popup.wm_title("Warning!")
label = tk.Label(popup, text=msg)
label.pack(side="top", fill=BOTH, expand=True)
B1 = tk.Button(popup, text="okay", command = popup.destroy)
B1.pack()
So the full exampel would be like this:
import tkinter as tk
from tkinter import messagebox
def popupmsg(msg):
popup = tk.Toplevel(root)
popup.wm_title("Warning!")
label = tk.Label(popup, text=msg)
label.pack(side="top", fill='both', expand=True)
B1 = tk.Button(popup, text="okay", command = popup.destroy)
B1.pack()
root = tk.Tk()
b = tk.Button(root, text='top', command= lambda: tk.Toplevel(root))
b.pack()
b1 = tk.Button(root, text='msg', command= lambda: messagebox.showinfo('title', 'msg'))
b1.pack()
b2 = tk.Button(root, text='popup', command= lambda: popupmsg('I dare you!'))
b2.pack()
def check ():
condition = root.focus_get()
root.after(500, check)
if condition == None:
print('pause music')
pass
check()
root.mainloop()
Another way
I found this code here: Determining what tkinter window is currently on top
and implemented in the code. It has the benefit that it works with messagebox, but only when another tk window that is related to your root is aktive. This code isn't about focus, it just tells you which window is currently on top.
So the conditions is all of the windows needs to be iconify.
import tkinter as tk
from tkinter import messagebox
def popupmsg(msg):
popup = tk.Toplevel(root)
popup.wm_title("Warning!")
label = tk.Label(popup, text=msg)
label.pack(side="top", fill='both', expand=True)
B1 = tk.Button(popup, text="okay", command = popup.destroy)
B1.pack()
root = tk.Tk()
b = tk.Button(root, text='top', command= lambda: tk.Toplevel(root))
b.pack()
b1 = tk.Button(root, text='msg', command= lambda: [root.iconify(), messagebox.showinfo('title', 'msg')])
b1.pack()
b2 = tk.Button(root, text='popup', command= lambda: popupmsg('I dare you!'))
b2.pack()
def check ():
condition1 = root.tk.eval('wm stackorder '+str(root))
root.after(500, check)
if condition1 == "":
print('pause music')
pass
else:
print('return music')
check()
root.mainloop()
Upvotes: 2