How to give Tkinter file dialog focus - python

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.

Related

Can't close Tkinter window after selecting output folder for script

I have a function like this:
def ask_open_directory():
root = Tk()
# Prevent an actual interface from showing. We just want the file selection window
root.withdraw()
# Set focus on the window. This only works on MacOS
os.system('''/usr/bin/osascript -e 'tell app "Finder" to set frontmost of process "Python" to true' ''')
# Open selector
dirpath = tkinter.filedialog.askdirectory()
# Doesn't do anything
root.destroy()
return dirpath
which is called first to select an input directory, and closes just fine, and then again right after, to select an output directory.
The script takes a couple of minutes to churn through all the data, and all the while, the Tkinter window for selecting output directory stays frozen until the script completes.
E.g. my script is organized like
def massive_function():
input = custom_reader_function(input_location = ask_open_directory())
output = ask_open_directory()
lots of stuff happening
finish
What am I missing?
What you are trying to achieve does not justify more than on Tkinter.Tk() instance.
I mean, you should remove root = Tk() , root.withdraw() and root.destroy() from your function. You should instantiate Tkinter.Tk() in your main program, not within individual functions.

tkinter askopenfilename doubleclick passes event to parent

I have this simple example of the behavior:
import tkinter as tk
from tkinter import filedialog, ttk
INITIALDIR = 'C:\\'
class MainWindow(ttk.Frame):
def __init__(self, root, *args, **kwargs):
super().__init__(root, *args, **kwargs)
self.pack()
btnoptions = {'expand':True, 'fill': 'both'}
btn = ttk.Button(self, text='Select', command=self.ask_openfile)
btn.pack(**btnoptions)
def ask_openfile(self):
self.file_opt = options = {}
options['initialdir'] = INITIALDIR
filename = filedialog.askopenfilename(**self.file_opt)
return filename
if __name__=='__main__':
root = tk.Tk()
root.geometry('600x300')
MainWindow(root).pack(expand=True, fill='both', side='top')
root.mainloop()
Basically there is one big button, which opens a open file dialog. If I select a file and press open, it works fine. However, if I double click to select a file, it selects the file, closes the dialog, and immediately opens a new open file dialog. My guess, that the second click somehow is passed to the underlying window and it clicks on the button again (button has to be under the file which is about to be selected). Is there a way to avoid this behavior? Looks like it is Windows problem, tried on windows 7 and 10 with python 3.5. On debian linux everything is fine, however, I need this to work on Windows.
Seems like this is a known issue with tk:
https://core.tcl.tk/tk/tktview?name=faf37bd379
That ticket says that the issue is fixed in tk 8.6.8.
I was having this issue with tk 8.6 and updating isn't a great option so I tried to find some other workarounds. I tried disabling the buttons and then enabling them but the rouge click just occurred after the button was enabled. I also tried adding a delay before enabling the buttons again but that didn't work either.
The two workarounds that I found that actually worked are:
1) Change the buttons to require a double-click. This can be done with bind . I don't personally like having to double-click the button but maybe other people are less picky.
2) Change the file selection to a combobox with a list of the files in a selected directory. The directory can be selected using askdirectory in place of askopenfilename. askdirectory requires the user to click the "Select Folder" button so it does not have the same issue with double-clicks.

Toplevel in Tkinter: Prevent Two Windows from Opening

Say I have some simple code, like this:
from Tkinter import *
root = Tk()
app = Toplevel(root)
app.mainloop()
This opens two windows: the Toplevel(root) window and the Tk() window.
Is it possible to avoid the Tk() window (root) from opening? If so, how? I only want the toplevel. I want this to happen because I am making a program that will have multiple windows opening, which are all Toplevel's of the root.
Thanks!
The withdraw() method removes the window from the screen.
The iconify() method minimizes the window, or turns it into an icon.
The deiconify() method will redraw the window, and/or activate it.
If you choose withdraw(), make sure you've considered a new way to exit the program before testing.
e.g.
from Tkinter import * # tkinter in Python 3
root = Tk()
root.withdraw()
top = Toplevel(root)
top.protocol("WM_DELETE_WINDOW", root.destroy)
but = Button(top, text='deiconify')
but['command'] = root.deiconify
but.pack()
root.mainloop()
The protocol() method can be used to register a function that will be called when the
Toplevel window's close button is pressed. In this case we can use destroy() to exit.

How to make a Tkinter window jump to the front?

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

How to know whether a window with a given title is already open in Tk?

I’ve writen a little python script that just pops up a message box containing the text passed on the command line. I want to pop it up only when the window —resulting from a previous call— is not open.
from Tkinter import *
import tkMessageBox
root = Tk()
root.withdraw()
# TODO not if a window with this title exists
tkMessageBox.showinfo("Key you!", " ".join(sys.argv[1:]))
Any idea how to check that?
I believe you want:
if 'normal' != root.state():
tkMessageBox.showinfo("Key you!", " ".join(sys.argv[1:]))
The previous answer works accordingly to the code you have provided. You say it does not work because the answerer complies with "sois bête et discipliné" rule in that he did not add root.mainloop() to his code since your question does not either.
By adding the later line, for some reason caused by the event loop, you should test the exact string "withdrawn" as follows:
import tkinter as tk
from tkinter import messagebox
import sys
root = tk.Tk()
root.withdraw()
if 'withdrawn' != root.state():
messagebox.showinfo("Key you!", sys.argv[1:])
root.mainloop()
Note: do not run this code otherwise your Terminal session will hang up. To circumvent this discomfort, you will have to reset the window state using either root.state("normal") which will lead to the message box to disappear as if a click on the Ok button occurred, or root.iconify() through which you can stop the Terminal session to hang up by right clicking on the tkinter icon appearing on your OS taskbar.

Categories