tkinter askopenfilename doubleclick passes event to parent - python

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.

Related

Python tkinter askopenfilename was working for me and is now not responding

I'm developing a program with Python and tkinter that begins with the user adding a text file from their directory. I had built a GUI with tkinter that provided a button to be pressed and a popup window for them to select their file--it was working fine, and then suddenly when I tried to run it it would begin "Not Responding" when I pressed the button meant to launch the popup window.
I am running Python 3.7.3 on Windows 10 in Jupyter notebooks; the tkinter version is 8.6. I have 8 GB of RAM but I'm not using more than 80% of it.
I've tried looking at some similar Stack Overflow questions like the ones here and here:
windows thinks tkinter is not responding
Python tkinter askopenfilename not responding
Trying askopenfilenames() didn't work; neither did adding root.update() or %gui tk.
Here is the code I'm working with:
import tkinter as tk
from tkinter import *
from tkinter import messagebox
from tkinter.filedialog import *
root = Tk()
topFrame=Frame(root)
topFrame.pack()
middleFrame=Frame(root, width=200, height=250,
highlightbackground="yellow",
highlightthickness=3,
borderwidth=2,
relief=RAISED)
middleFrame.pack()
bottomFrame=Frame(root)
bottomFrame.pack()
ourdirectory=[]
def load1():
f1 = askopenfilename(filetypes=(('TXT files','*.txt'), ('All files', '*.*')))
ourdirectory.append(f1)
mylab = Label(topFrame, text="Hello and welcome!")
mylab.pack()
button = Button(bottomFrame, text="Add File", command=load1)
button.grid(row=5, sticky=W)
root.mainloop()
When the "Add File" button is pressed, I expected the program to launch a popup window where the user can navigate in the file directory. Instead, the title text just fades and it doesn't respond--and clicking on the window causes it to announce that it's stopped responding. (This also consistently causes the Jupyter kernel to die.) Still, when I run the code but close the main window without pressing the "File Add" button, it closes fine and nothing freezes or breaks.
I have encountered the same issue when running some code that would open, modify, and overwrite an image. Everything worked as expected until suddenly askopenfilename() started Not Responding and not opening the file dialog window.
I have attempted changing the initial directory of askopenfilename() and calling the method from a separate file and the command line without success.
One of my open windows was a directory (not the same as I was working with) on which file thumbnails failed to load and files could not be opened. Upon closing the window, the screen 'reloaded,' certain programs opened (such as sticky notes, which was previously closed but which typically opens upon restarting), and the method resumed normal functionality.

Tkinter Focus lost after askstring

I am currently implementing a program that uses many tkinter frames and while subframe is being opened I want the superframe to be locked for the user (otherwise things will not work out). After some research I found the grab_set and grab_release method which worked quite fine.
However once the subframe (instanciated by Toplevel) calls the askstring the grab is "losed" and the user can interact with the superlevel window again. An example would be this (very simplified code):
import tkinter as tk
import tkinter.simpledialog
root = tk.Tk()
def open_sublevel():
tl = tk.Toplevel(root)
def ask():
print(tk.simpledialog.askstring("askstring", "askstring"))
tk.Button(tl, text="ask", command=ask).pack()
tl.grab_set()
root.wait_window(tl)
tl.grab_release()
print("release")
tk.Button(root, text="asdf", command=open_sublevel).pack()
tk.mainloop()
Once the user opens the subframe by clicking "asdf" the frame containing "asdf" will be locked for the duration while the subframe is opened. However once the user selects the "ask"-Button in the subframe this "lock" somehow disappears.
According to the notes in the tkinter library:
A grab directs all events to this and descendant widgets in the application.
I am not able so far to find any documentation that would explain why the grab_set() is falling off after you finish submitting your askstring but I would imaging it is because once the widget is gone the grab_set() falls off. Just like if you were to close out the Toplevel window.
In this case tl.grab_release() does not appear to be needed as grab releases once the window closes.
From what I have tested if you reset the grab_set() after the askstring is done then it will still work properly.
You need to simply add tl.grab_set() just below print(tk.simpledialog.askstring("askstring", "askstring")).
Modified code below:
import tkinter as tk
import tkinter.simpledialog
root = tk.Tk()
def open_sublevel():
tl = tk.Toplevel(root)
tl.grab_set()
def ask():
print(tk.simpledialog.askstring("askstring", "askstring"))
tl.grab_set()
tk.Button(tl, text="ask", command=ask).pack()
print("release")
tk.Button(root, text="asdf", command=open_sublevel).pack()
tk.mainloop()
setting the parent for simpledialog will make simpledialog take focus
x = simpledialog(parent = window_x, title = z etc.)
this will make sure x takes focus and not withdraw

tkinter menu accelerators and modal dialog boxes

I want to have a menu that shows a modal dialog box. Everything is fine, until I add an accelerator. If I do this and use the accelerator to access to dialog, it hangs. I suspect that wait_window, used inside the modal dialog box is somehow in conflict with the mainloop, when called from a "bind". Here is an example:
import tkinter
from tkinter import simpledialog
class App(tkinter.Tk):
def __init__(self):
tkinter.Tk.__init__(self)
self.bind_all("<Control-f>", lambda event: self.menu_file())
menubar = tkinter.Menu(self)
fileMenu = tkinter.Menu(menubar, tearoff=False)
fileMenu.add_command(label="File", underline=0,
command=self.menu_file, accelerator="Control+f")
# fileMenu.add_command(label="File", underline=0,
# command=self.menu_file)
menubar.add_cascade(label="File",underline=0, menu=fileMenu)
self.config(menu=menubar)
def menu_file(self):
simpledialog.Dialog(self,"Message")
app=App()
app.mainloop()
If in the above code I comment out the line that adds the accelerator and uncomment the subsequent line, there is no hangup (I can of course still use Ctrl+F to access the dialog box). The only problem is that the accelerator string is not shown next to the File menu. According to the tkinter documentations on the web that I have found, adding the accelerator should only change how the menu is shown and nothing else, so I am really puzzled. Anyone any ideas? (I could of course emulate accelerators by modifying the strings to be displayed, but I would not consider this as an elegant solution.)
As I have noticed, this is a Mac-specific bug.
Following the workaround suggested for a known Tk bug on Mac (see link), changing the line which binds the menu method to the keystroke to the following:
self.bind_all("<Command-f>", lambda event: self.after(100,self.menu_file))
is "fixing" the bug. They also suggest to increase 100 to 150 on slower systems. Hmm..

How to prevent two root windows from coming up when I start Tkinter?

Question
How can I hide the two Tkinter root windows that are popping up in my program? I have tried to use root.widthdraw(). Here is a link to my Pastebin.
Background
I am trying to create a really basic email client to learn more about Tkinter and SMTP. I have decided that my program will first create a Toplevel window where the user will enter their credentials, and if the Server can authenticate them, then the program opens the email send dialogue. Annoyingly, I have not been able to hide the 2 other root windows which open when the program starts up. I have attempted to use root.widthdraw() to avoid this issue.
Relevant Code
#-----Authen is a toplevel class-------------
passcheck = Authen()
root = Tk()
root.mainloop()
root.widthdraw()
You should create your root window before creating any other windows. Otherwise you get exactly what you observe: Tkinter will automatically create a root window the first time you create some other widget, and the you are creating a second one.
I'm running Python2.7, with the default tkinter package, and my root object doesn't have a withdraw() method. Besides that, you can also just run mainloop off the toplevel called passcheck and it'll save you a window.
class Authen(Frame):
def __init__(self, master):
Frame.__init__(self, master)
then:
root = Tk()
passcheck = Authen(root)
root.mainloop()
edit: Here's a solution, instead of Authen being a TopLevel, have it be a Frame instead, and pass root as it's master. http://pastebin.com/TtnvU0er

How to give Tkinter file dialog focus

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.

Categories