How do I get a Tkinter application to jump to the front? Currently, the window appears behind all my other windows and doesn't get focus.
Is there some method I should be calling?
Assuming you mean your application windows when you say "my other windows", you can use the lift() method on a Toplevel or Tk:
root.lift()
If you want the window to stay above all other windows, use:
root.attributes("-topmost", True)
Where root is your Toplevel or Tk. Don't forget the - infront of "topmost"!
To make it temporary, disable topmost right after:
def raise_above_all(window):
window.attributes('-topmost', 1)
window.attributes('-topmost', 0)
Just pass in the window you want to raise as a argument, and this should work.
Add the following lines before the mainloop():
root.lift()
root.attributes('-topmost',True)
root.after_idle(root.attributes,'-topmost',False)
It works perfectly for me. It makes the window come to the front when the window is generated, and it won't keep it always be in the front.
If you're doing this on a Mac, use AppleEvents to give focus to Python. Eg:
import os
os.system('''/usr/bin/osascript -e 'tell app "Finder" to set frontmost of process "Python" to true' ''')
Regarding the Mac, I noticed there can be a problem in that if there are multiple python GUIs running, every process will be named "Python" and AppleScript will tend to promote the wrong one to the front. Here's my solution. The idea is to grab a list of running process IDs before and after you load Tkinter. (Note that these are AppleScript process IDs which seem to bear no relation to their posix counterparts. Go figure.) Then the odd man out will be yours and you move that one to frontmost. (I didn't think that loop at the end would be necessary, but if you simply get every process whose ID is procID, AppleScript apparently returns the one object identified by name, which of course is that non-unique "Python", so we are back to square one unless there's something I'm missing.)
import Tkinter, subprocess
def applescript(script):
return subprocess.check_output(['/usr/bin/osascript', '-e', script])
def procidset():
return set(applescript(
'tell app "System Events" to return id of every process whose name is "Python"'
).replace(',','').split())
idset = procidset()
root = Tkinter.Tk()
procid = iter(procidset() - idset).next()
applescript('''
tell app "System Events"
repeat with proc in every process whose name is "Python"
if id of proc is ''' + procid + ''' then
set frontmost of proc to true
exit repeat
end if
end repeat
end tell''')
On Mac OS X PyObjC provides a cleaner and less error prone method than shelling out to osascript:
import os
from Cocoa import NSRunningApplication, NSApplicationActivateIgnoringOtherApps
app = NSRunningApplication.runningApplicationWithProcessIdentifier_(os.getpid())
app.activateWithOptions_(NSApplicationActivateIgnoringOtherApps)
Recently, I had the same question on the Mac. I have combined several answers using #MagerValp for the Mac and #D K for other systems:
import platform
if platform.system() != 'Darwin':
root.lift()
root.call('wm', 'attributes', '.', '-topmost', True)
root.after_idle(root.call, 'wm', 'attributes', '.', '-topmost', False)
else:
import os
from Cocoa import NSRunningApplication, NSApplicationActivateIgnoringOtherApps
app = NSRunningApplication.runningApplicationWithProcessIdentifier_(os.getpid())
app.activateWithOptions_(NSApplicationActivateIgnoringOtherApps)
root.mainloop()
Somewhat of a combination of various other methods, this works on OS X 10.11, and Python 3.5.1 running in a venv, and should work on other platforms too. It also targets the app by process id rather than app name.
from tkinter import Tk
import os
import subprocess
import platform
def raise_app(root: Tk):
root.attributes("-topmost", True)
if platform.system() == 'Darwin':
tmpl = 'tell application "System Events" to set frontmost of every process whose unix id is {} to true'
script = tmpl.format(os.getpid())
output = subprocess.check_call(['/usr/bin/osascript', '-e', script])
root.after(0, lambda: root.attributes("-topmost", False))
You call it right before the mainloop() call, like so:
raise_app(root)
root.mainloop()
There's a hint on how to make the Tkinter window take focus when you call mainloop() in the Tkinter._test() function.
# The following three commands are needed so the window pops
# up on top on Windows...
root.iconify()
root.update()
root.deiconify()
root.mainloop()
This is the cleanest most proper way I've found to do this, but it's only needed for Windows systems.
This answer is to make one Tkinter Window pop up overtop of other Tkinter windows.
In my app I have a large window toplevel which calls a much smaller window top2 which initially appears on top of toplevel.
If user clicks within toplevel window it gains focus and smothers much smaller top2 window until toplevel window is dragged off of it.
The solution is to click the button in toplevel to launch top2 again. The top2 open function knows it is already running so simply lifts it to the top and gives it focus:
def play_items(self):
''' Play 1 or more songs in listbox.selection(). Define buttons:
Close, Pause, Prev, Next, Commercial and Intermission
'''
if self.top2_is_active is True:
self.top2.focus_force() # Get focus
self.top2.lift() # Raise in stacking order
root.update()
return # Don't want to start playing again
On macOS High Sierra, py3.6.4, here is my solution:
def OnFocusIn(event):
if type(event.widget).__name__ == 'Tk':
event.widget.attributes('-topmost', False)
# Create and configure your root ...
root.attributes('-topmost', True)
root.focus_force()
root.bind('<FocusIn>', OnFocusIn)
The idea is to bring it to the front until user interacts with it, i.e., taking focus.
I tried the accepted answer, .after_idle(), and .after(). They all fail in one case: When I run my script directly from an IDE like PyCharm, the app window will stay behind.
My solution works in all the cases that I encountered.
One more line (needed for Python 3.11 and tkinter 8.6):
def lift_window(window):
window.attributes('-topmost', True)
window.update_idletasks() # get window on top
window.attributes('-topmost', False) # prevent permanent focus
window.focus_force() # focus to the window
This will lift the window to the front, and also focus on the window.
def lift_window(window):
window.attributes('-topmost',True)
window.attributes('-topmost',False) # disable the topmost attribute after it is at the front to prevent permanent focus
window.focus_force() # focus to the window
Related
I've read through related answers and it seems that the accepted way to do this is by binding callbacks to <Map> and <Unmap> events in the Toplevel widget. I've tried the following but to no effect:
from Tkinter import *
tk = Tk()
def visible(event):
print 'visible'
def invisible(event):
print 'invisible'
tk.bind('<Map>', visible)
tk.bind('<Unmap>', invisible)
tk.mainloop()
I'm running python 2.7 on Linux. Could this be related to window manager code in different operating systems?
Calling tk.iconify() before tk.mainloop() has no effect either. In fact, the only command that produces the correct behavior is tk.withdraw() which is certainly not the same thing as minimizing the window. Additionally, if <Map> and <Unmap> events are triggered by calling pack(), grid(), or place(), why is <Map> triggered when the application window is minimized on Windows and/or Mac, as this and this answer suggest. And why would they be triggered when calling withdraw() and deiconify() on Linux?
Unmapping on Linux
The term Unmap has a quite different meaning on Linux than it has on Windows. On Linux, Unmapping a window means making it (nearly) untraceable; It does not appear in the application's icon, nor is it listed anymore in the output of wmctrl -l. We can unmap / map a window by the commands:
xdotool windowunmap <window_id>
and:
xdotool windowmap <window_id>
To see if we can even possibly make tkinter detect the window's state minimized, I added a thread to your basic window, printing the window's state once per second, using:
root.state()
Minimized or not, the thread always printed:
normal
Workaround
Luckily, if you must be able to detect the window's minimized state, on Linux we have alternative tools like xprop and wmctrl. Although as dirty as it gets, it is very well scriptable reliably inside your application.
As requested in a comment, below a simplified example to create your own version of the bindings with external tools.
How it works
When the window appears (the application starts), We use wmctrl -lp to get the window's id by checking both name and pid (tkinter windows have pid 0).
Once we have the window id, we can check if the string _NET_WM_STATE_HIDDEN is in output of xprop -id <window_id>. If so, the window is minimized.
Then we can easily use tkinter's after() method to include a periodic check. In the example below, the comments should speak for themselves.
What we need
We need both wmctrl and xprop to be installed. On Dedian based systems:
sudo apt-get install wmctrl xprop
The code example
import subprocess
import time
from Tkinter import *
class TestWindow:
def __init__(self, master):
self.master = master
self.wintitle = "Testwindow"
self.checked = False
self.state = None
button = Button(self.master, text = "Press me")
button.pack()
self.master.after(0, self.get_state)
self.master.title(self.wintitle)
def get_window(self):
"""
get the window by title and pid (tkinter windows have pid 0)
"""
return [w.split() for w in subprocess.check_output(
["wmctrl", "-lp"]
).decode("utf-8").splitlines() if self.wintitle in w][-1][0]
def get_state(self):
"""
get the window state by checking if _NET_WM_STATE_HIDDEN is in the
output of xprop -id <window_id>
"""
try:
"""
checked = False is to prevent repeatedly fetching the window id
(saving fuel in the loop). after window is determined, it passes further checks.
"""
self.match = self.get_window() if self.checked == False else self.match
self.checked = True
except IndexError:
pass
else:
win_data = subprocess.check_output(["xprop", "-id", self.match]).decode("utf-8")
if "_NET_WM_STATE_HIDDEN" in win_data:
newstate = "minimized"
else:
newstate = "normal"
# only take action if state changes
if newstate != self.state:
print newstate
self.state = newstate
# check once per half a second
self.master.after(500, self.get_state)
def main():
root = Tk()
app = TestWindow(root)
root.mainloop()
if __name__ == '__main__':
main()
My own implementation of the hack suggested by Jacob.
from Tkinter import Tk, Toplevel
import subprocess
class CustomWindow(Toplevel):
class State(object):
NORMAL = 'normal'
MINIMIZED = 'minimized'
def __init__(self, parent, **kwargs):
Toplevel.__init__(self, parent, **kwargs)
self._state = CustomWindow.State.NORMAL
self.protocol('WM_DELETE_WINDOW', self.quit)
self.after(50, self._poll_window_state)
def _poll_window_state(self):
id = self.winfo_id() + 1
winfo = subprocess.check_output(
['xprop', '-id', str(id)]).decode('utf-8')
if '_NET_WM_STATE_HIDDEN' in winfo:
state = CustomWindow.State.MINIMIZED
else:
state = CustomWindow.State.NORMAL
if state != self._state:
sequence = {
CustomWindow.State.NORMAL: '<<Restore>>',
CustomWindow.State.MINIMIZED: '<<Minimize>>'
}[state]
self.event_generate(sequence)
self._state = state
self.after(50, self._poll_window_state)
if __name__ == '__main__':
root = Tk()
root.withdraw()
window = CustomWindow(root)
def on_restore(event):
print 'restore'
def on_minimize(event):
print 'minimize'
window.bind('<<Restore>>', on_restore)
window.bind('<<Minimize>>', on_minimize)
root.mainloop()
For me, on Win10, your code works perfectly, with the caveat that the middle frame button produces 'visible' whether it means 'maximize' or 'restore'. So maximize followed by restore results in 2 new 'visibles' becoming visible.
I did not particularly expect this because this reference says Map is produced when
A widget is being mapped, that is, made visible in the application. This will happen, for example, when you call the widget's .grid() method.
Toplevels are not managed by geometry. The more authoritative tk doc says
Map, Unmap
The Map and Unmap events are generated whenever the mapping state of a window changes.
Windows are created in the unmapped state. Top-level windows become mapped when they transition to the normal state, and are unmapped in the withdrawn and iconic states.
Try adding tk.iconify() before the mainloop call. This should be doing the same as the minimize button. If it does not result in 'invisible', then there appears to be a tcl/tk bug on Linux.
I'm writing an application in Python with the Tkinter GUI framework. It listens for keyboard and mouse events, so it must have focus. When it is launched from a terminal in Ubuntu, the following code works:
from Tkinter import *
root = Tk()
root.focus_force()
def key(event):
print "pressed", event.char
def callback(event):
print "clicked at", event.x, event.y
frame = Frame(root, width=100, height=100)
frame.bind("<Key>", key)
frame.bind("<Button-1>", callback)
frame.pack()
frame.focus_force()
root.mainloop()
However, when launched from a terminal in Mac OS X 10.8.4 (stock Python 2.7.2), focus is retained by the terminal emulator until the user clicks on the window. Does anyone know of a workaround for this?
I tried this and it worked well for me:
from os import system
from platform import system as platform
# set up your Tk Frame and whatnot here...
if platform() == 'Darwin': # How Mac OS X is identified by Python
system('''/usr/bin/osascript -e 'tell app "Finder" to set frontmost of process "Python" to true' ''')
Of course, that'll bring your entire application to the front, not just a specific window, but after you do that, you can then use focus_force() on a specific frame or window and that'll get moved to become the frontmost of all your application windows.
For those interested, I didn't write the system() call myself. I found it in this thread on SourceForge.
The fact that I put the system() call within an if block that verifies this is running on OS X makes the solution cross platform - my understanding is that focus_force() works on all other platforms exactly as you want, and just performing it after the system() invocation wouldn't cause any problems in OS X, either.
came here wondering the same question, but I found this wise sounding answer from Kevin Walzer which suggests to use py2app:
Yes, this is standard behavior for OS X. Running an app in Terminal
keeps focus in the Terminal unless you switch by clicking windows. The
Command-Tab behavior is determined by the windowing system, not by a
newly spawned process.
The way around this is to wrap your application up in a standard Mac app
bundle using py2app. The average Mac user isn't going to launch a
Python-based game from the command line.
Kevin
(from https://groups.google.com/forum/#!topic/comp.lang.python/ZPO8ZN5dx3M)
Do wait_visibility and event_generate help ?
eg. something like -
from Tkinter import *
root = Tk()
def key(event):
print "pressed", event.char
def callback(event):
print "clicked at", event.x, event.y
frame = Frame(root, width=100, height=100)
frame.bind("<Key>", key)
frame.bind("<Button-1>", callback)
frame.pack()
frame.focus_set()
root.wait_visibility()
root.event_generate('<Button-1>', x=0, y=0)
root.mainloop()
I have written an application in python 2.7 and tkinter. I created a tool bar with several buttons that open up respective top windows that display various options. I used ttk.Checkbutton with the 'toolbutton' style as an indicator to show whether the option windows are open or closed.
The problem is that the option windows will go to the back if another window is selected. Currently, if one selects the toolbutton again, the option window will close. However, I only want to close the window if it is on top. If the option window is not on top, I want the window to moved to the front.
Some of the code I have working:
class MainWindow:
def __init__(self,application):
self.mainframe=tk.Frame(application)
application.geometry("900x600+30+30")
self.otherOptionsSelect=tk.IntVar()
self.otherOptions_Button=ttk.Checkbutton(application,style='Toolbutton',variable=self.otherOptionsSelect,
onvalue=1, offvalue=0,image=self.optionsIcon, command=self.otherOptions)
def otherOptions(self):
if self.otherOptionsSelect.get()==0:
self.otherOptions.destroy()
return
self.otherOptions=tk.Toplevel()
self.otherOptions.title("IsoSurface Options")
self.otherOptions.geometry("200x165+"+str(int(application.winfo_x())+555)+"+"+str(int(application.winfo_y())+230))
self.otherOptApply_button=ttk.Button(self.otherOptions,text="Apply",command=self.showFrame)
self.otherOptApply_button.place(x=20,y=80,width=50,height=30)
self.otherOptClose_button=ttk.Button(self.otherOptions,text="Close",command=self.otherOptionsClose)
self.otherOptClose_button.place(x=80,y=80,width=50,height=30)
def otherOptionsClose(self):
self.otherOptionsSelect.set(0)
self.otherOptions.destroy()
Here is a picture of the entire application I have written:
In the above image, each window has their respective ttk.checkbutton. At the moment, toggling the checkbutton either opens or closes the window. However, what I really want it to do is close the window if the window is in front of the application, or bring the window to the front if it is behind the application.
Hopefully this clears some things up.
Thanks in advance!
It is in fact possible to check stacking order of windows. Using Tkinter, you have to do some funny tcl evals to get at the information. I found the answer at TkDoc in the section on Windows and Dialogs, scroll down until you get to "Stacking Order". The code baffled me until I started playing around with it interactively. My test code was:
import Tkinter as tk
root = tk.Tk()
root.title('root')
one = tk.Toplevel(root)
one.title('one')
two = tk.Toplevel(root)
two.title('two')
I then manipulated the windows so that two was on top, one under that and root below them all. In that configuration, the following weirdness can tell you relative layering of windows:
root.tk.eval('wm stackorder '+str(two)+' isabove '+str(root))
returns 1, meaning "Yes, window two is above window root." While the following:
root.tk.eval('wm stackorder '+str(root)+' isabove '+str(two))
returns 0, meaning "No, window root is not above window two." You can also use the command:
root.tk.eval('wm stackorder '+str(root))
Which gives back the full window stacking order in the form of a weird string something like this:
'. .68400520L .68401032L'
Which starts to make sense when you run the commands:
str(root)
str(one)
str(two)
and figure out that root has the internal name '.', one is '.68400520L' and two is '.68401032L'. You read the output of root.tk.eval('wm stackorder '+str(root)) backwards so it's saying two is on top, one is under that and root is below both.
I have a Tkinter program and running it like: python myWindow.py starts it all right, but the window is behind the terminal that I use to start it.
Is there a way to make it grab the focus and be the foreground application? Does it depend on the platform?
This might be a feature of your particular window manager. One thing to try is for your app to call focus_force at startup, after all the widgets have been created.
Have you tried this at the end of your script ?
root.iconify()
root.update()
root.deiconify()
root.mainloop()
Somewhat of a combination of various other methods found online, this works on OS X 10.11, and Python 3.5.1 running in a venv, and should work on other platforms too. On OS X, it also targets the app by process id rather than app name.
from tkinter import Tk
import os
import subprocess
import platform
def raise_app(root: Tk):
root.attributes("-topmost", True)
if platform.system() == 'Darwin':
tmpl = 'tell application "System Events" to set frontmost of every process whose unix id is {} to true'
script = tmpl.format(os.getpid())
output = subprocess.check_call(['/usr/bin/osascript', '-e', script])
root.after(0, lambda: root.attributes("-topmost", False))
You call it right before the mainloop() call, like so:
raise_app(root)
root.mainloop()
I'm using OS X. I'm double clicking my script to run it from Finder. This script imports and runs the function below.
I'd like the script to present a Tkinter open file dialog and return a list of files selected.
Here's what I have so far:
def open_files(starting_dir):
"""Returns list of filenames+paths given starting dir"""
import Tkinter
import tkFileDialog
root = Tkinter.Tk()
root.withdraw() # Hide root window
filenames = tkFileDialog.askopenfilenames(parent=root,initialdir=starting_dir)
return list(filenames)
I double click the script, terminal opens, the Tkinter file dialog opens. The problem is that the file dialog is behind the terminal.
Is there a way to suppress the terminal or ensure the file dialog ends up on top?
Thanks,
Wes
For anybody that ends up here via Google (like I did), here is a hack I've devised that works in both Windows and Ubuntu. In my case, I actually still need the terminal, but just want the dialog to be on top when displayed.
# Make a top-level instance and hide since it is ugly and big.
root = Tkinter.Tk()
root.withdraw()
# Make it almost invisible - no decorations, 0 size, top left corner.
root.overrideredirect(True)
root.geometry('0x0+0+0')
# Show window again and lift it to top so it can get focus,
# otherwise dialogs will end up behind the terminal.
root.deiconify()
root.lift()
root.focus_force()
filenames = tkFileDialog.askopenfilenames(parent=root) # Or some other dialog
# Get rid of the top-level instance once to make it actually invisible.
root.destroy()
Use AppleEvents to give focus to Python. Eg:
import os
os.system('''/usr/bin/osascript -e 'tell app "Finder" to set frontmost of process "Python" to true' ''')
I had this issue with the window behind Spyder:
root = tk.Tk()
root.overrideredirect(True)
root.geometry('0x0+0+0')
root.focus_force()
FT = [("%s files" % ftype, "*.%s" % ftype), ('All Files', '*.*')]
ttl = 'Select File'
File = filedialog.askopenfilename(parent=root, title=ttl, filetypes=FT)
root.withdraw()
filenames = tkFileDialog.askopenfilenames(parent=root,initialdir=starting_dir)
Well parent=root is enough for making tkFileDialog on top. It simply means that your root is not on top, try making root on top and automatically tkFileDialog will take top of the parent.
None of the other answers above worked for me 100% of the time.
In the end, what worked for me was adding 2 attibutes: -alpha and -topmost
This will force the window to be always on top, which was what I wanted.
import tkinter as tk
root = tk.Tk()
# Hide the window
root.attributes('-alpha', 0.0)
# Always have it on top
root.attributes('-topmost', True)
file_name = tk.filedialog.askopenfilename( parent=root,
title='Open file',
initialdir=starting_dir,
filetypes=[("text files", "*.txt")])
# Destroy the window when the file dialog is finished
root.destroy()
Try the focus_set method. For more, see the Dialog Windows page in PythonWare's An Introduction to Tkinter.