I am working on a cross platform project to control a CNC router written in python 2 with a GUI created using kivy.
Overall I love kivy however the kivy file chooser is slow and doesn't look the way most users would expect it to. I would like to use a file chooser which has a native look for most users so I'm looking at using the tkinter askopenfilename() function to give me a UI native file chooser.
This works alright, except that if the user clicks outside of the file chooser to bring the main program to the front, the file chooser is left in the background and the UI is locked up because askopenfilename() is blocking.
Is there a way to make askopenfilename() close automatically if it loses focus?
Any and all help is appreciated.
Here is the code as it stands:
def openFile(self):
'''
Open The Pop-up To Load A File
Creates a new pop-up which can be used to open a file.
'''
root = Tk()
root.withdraw() # we don't want a full GUI, so keep the root window from appearing
initialDir = path.dirname(self.data.gcodeFile)
if initialDir is "":
initialDir = path.expanduser('~')
validExtensions = self.data.config.get('Ground Control Settings', 'validExtensions').replace(",", "")
root.bind("<FocusOut>", self.app_lost_focus)
filename = askopenfilename( parent=root, initialdir = initialDir, filetypes = (("Gcode files", validExtensions),("all files","*.*")), title = "Open Gcode File") # show an "Open" dialog box and return the path to the selected file
if filename is not "":
self.data.gcodeFile = filename
self.data.config.set('Maslow Settings', 'openFile', str(self.data.gcodeFile))
self.data.config.write()
#close the parent popup
self.parentWidget.close()
When filedialog.askopenfilename is called method without creating any GUI itself, it calls underlying widget.tk.call("tk_getOpenFile", **options), which then calls ::tk::dialog::file::. Which seems to handle the file-dialog-gui, on which tkinter doesn't have control over directly at least.
Perhaps a good workaround would be to hide the kivy window while the process is ongoing.
Related
I'm pretty new to PyQt5...
With that said, I've added a menu with File->Open.
I've implemented this using QFileDialog.
# Create the Actions for the Menu Bar
LoadVrfAction = QAction("&Load New File", self)
LoadVrfAction.setShortcut("Ctrl-N")
LoadVrfAction.setStatusTip("New VRF Data")
LoadVrfAction.triggered.connect(self.control.loadVrfFile)
menuBar = self.menuBar()
fileMenu = menuBar.addMenu("&File")
fileMenu.addAction(LoadVrfAction)
def loadVrfFile(self):
vrfFileName = QFileDialog.getOpenFileName(self.view, "Open File")
myFile = vrfFileName[0]
# Load the file
logging.debug(f"Start: Reading File = {myFile}")
self.view.core.readVrfFile(myFile)
logging.debug(f"End: Reading File = {myFile}")
My problem is that when I click "Open" on the file I want to load, I want the Menu Dialog box to go hide or go away before I execute the function "readVrfFile". Since the file I'm reading into the program is large it takes about 15 seconds and my diaglog box just appears frozen while it's loading.
Is there an easy way to hide the getOpenFileName box and instead print status to a textbox/status bar while its loading?
I'm creating a tkinter GUI for the purposes of opening and analyzing an EDF file named "raw" using the mne package. Currently, when I run the script, named "my_script.py", it first prompts me to open a file using the following code:
window = Tk()
window.filename = filedialog.askopenfilename(initialdir = "Users/fishbacp/Desktop",title =
"Select file",filetypes = (("EDF files","*.edf"),("all files","*.*")))
raw = mn.io.read_raw_edf(window.filename, preload=True)
# Now perform my first operation on the returned file, "raw":
data,times=raw[:,:]
# In subsequent lines I fill my gui with checkboxes, input boxes, labels etc. to perform more tasks.
This works fine, but the file dialog starts before I see the various widgets in my window.
Now I want to run the module so that instead of the file dialog starting automatically, I first see the GUI and then use a button in it to run the file dialog. Here's my attempt:
window = Tk()
def open_file():
window.filename = filedialog.askopenfilename(initialdir = "Users/fishbacp/Desktop",title
= "Select file",filetypes = (("EDF files","*.edf"),("all files","*.*")))
raw = mn.io.read_raw_edf(window.filename, preload=True)
return raw
open_file_button=Button(window, bg='lightgray',text="Select
File",command=open_file)
open_file_button.grid(row=0,column=0,padx=10, pady=10)
# Now perform my first operation on the returned file, "raw":
data,times=raw[:,:]
The error message states, "Traceback (most recent call last):
File "/Users/fishbacp/Desktop/my_script.py", line 67, in
data,times=raw[:,:]
NameError: name 'raw' is not defined
So, I'm missing something basic about how tkinter works in terms of what I should be doing to get my open_file function return the raw file as I want.
You cannot return something from the command callback. Your code processing raw content depends on the fact a button has been clicked by the user. It hence has to be located in the callback itself.
Some more clarification:
When creating a UI, you go from procedural programing (one thing after the other) to event driven programing: a User event triggers the next operation the program will do. It is because the user will click "open file" button that you can process the data. The call to windows.mainloop() tells TK to start processing user events (clicks,...). In practice you cannot do anything on the edf file as long as user has not clicked on the button so it makes sense to put all this code in the callback.
Something like: is a starting point:
window = Tk()
def open_file():
window.filename = filedialog.askopenfilename(initialdir = "Users/fishbacp/Desktop",title
= "Select file",filetypes = (("EDF files","*.edf"),("all files","*.*")))
raw = mn.io.read_raw_edf(window.filename, preload=True)
# Now perform my first operation on the returned file, "raw":
# this can be done only once the user has selected the file to open
# which is "triggered" by the user clicking on "Select File"
data,times=raw[:,:]
...
open_file_button=Button(window, bg='lightgray',text="Select
File",command=open_file)
open_file_button.grid(row=0,column=0,padx=10, pady=10)
The scenario is that i want to:
-Open a specific text file
-Import text from text file to be shown in a text widget
-Then the text from the text widget to then replace what was in the text file before, with what is currently in the text widget.
Currently i have worked out how to open the specific text file into the text widget and for it to be displayed however i can not figure out how to do the last step.
I have tried to define a 'Save' function however have not got anywhere, you can see this below.
Current code for step 1 and 2:
class PropertynewsA(Propertynews):
def __init__(self):
Propertynews.__init__(self)
def save():
file.write(txt)
file.close()
messagebox.showinfo('Saved!','Your Message has been Saved')
self.delete
file = open('PropertynewsA.txt', 'r+') #Text file i am using
with open('PropertynewsA.txt') as file: # Use file to refer to the file object
data = file.read() #date=current text in text file
#Text widget
Time = strftime("%d %b %Y\n")#getting gmt time
txt = Text(self.GenericGui,height=14, width=53,font=('TkDefaultFont',12)) #Creating text widget
txt.insert('1.0',data) #showing text from text file in widget
txt.tag_configure('format', background='lightblue', font='helvetica 14 bold', relief='raised')
txt.focus()
txt.place(x=8,y=40) #places text widget
If anyone can assist me here that would be great
Cheers guys
Once you know how the Widget indices work and you know the insert and get methods on the Text widget:
starting_text = "THIS WOULD COME FROM A FILE"
...
textbox = TEXT_WIDGET_SETUP_ALREADY
textbox.insert("1.0",starting_text)
...
ending_text = textbox.get("1.0","end-1c")
The tricky part is accessing the text while the program is closing but not after the widget is destroyed (or you get a _tkinter.TclError: invalid command name ".4384096888" error):
import tkinter as tk
class Text(tk.Text):
def destroy(self):
global ending_text
ending_text = self.get("1.0","end-1c")
super(Text,self).destroy()
although if you use from tkinter import * notation you will need to call your class something other then Text as well as probably not using ending_text as a global variable, but that was the easiest way to show how to do it.
This is the full code I used for testing with IO, although if you don't understand how to work with files already there are references elsewhere.
import tkinter as tk
filename = "test.txt"
class Text(tk.Text):
def destroy(self):
global ending_text
ending_text = self.get("1.0","end-1c")
super(Text,self).destroy()
try:
with open(filename) as f:
text = f.read()
except IOError:
text = ""
root = tk.Tk()
textbox = Text(root)
textbox.insert("1.0",text)
textbox.grid()
#this would probably just be put in the destroy method
def finish(event=None):
with open(filename,"w") as f:
f.write(ending_text)
textbox.bind("<Destroy>",finish) #this will happen after Text.destroy() so textbox.get() fails if used from this point
root.mainloop()
I created a simple user interface that will allow you to open the text file of your choice from a file dialog. I prefer to set the options of this later apart for scalability reasons (in case you would love, in the future, to read document files instead, for instance):
# Opening file options
self.file_options={}
self.file_options['defaultextension'] = '.txt'
self.file_options['filetypes'] = [('text files', '.txt'), ('all files', '.*')]
self.file_options['parent'] = self.parent
self.file_options['title'] = 'Open a text file'
self.file_options['initialdir']='/home/'
I am runing Linux, so in case you're on MS Windows OS, you may change self.file_options['initialdir']='/home/' to whatever directory path you want. Note that you can also remove it, in which case the file dialog window will prompt you to the directory where you are running the application, by default.
The method initialize_user_interface() does what its name reflects. Mainly, it offers a comfortable way to exit the application on a click and choose the file to read:
self.filemenu.add_command(label="Open",command=self.text_replacement)
self.filemenu.add_command(label="Exit",command=self.parent.quit)
You can then add a Text widget. Preferably, it is better to have a scrollable text zone in case you stumble on a large content file.
For this, you need to create the scrollbar, and attach it to the Text widget because the Text widget do not maintain its own scrollbars.
Here is the complete program:
'''
Created on Feb 25, 2016
#author: begueradj
'''
import Tkinter # Tkinter -> tkinter in Python3
import Tkconstants
import tkFileDialog
class Begueradj(Tkinter.Frame):
""" Get text file content and past it into a scrollable Text widget.
Replace the content of the file with the pre-existing Text widget content.
"""
def __init__(self,parent):
""" Set some class variables:
mainly the file dialog interface options.
"""
Tkinter.Frame.__init__(self,parent)
self.parent=parent
# Opening file options
self.file_options={}
self.file_options['defaultextension'] = '.txt'
self.file_options['filetypes'] = [('text files', '.txt'), ('all files', '.*')]
self.file_options['parent'] = self.parent
self.file_options['title'] = 'Open a text file'
self.file_options['initialdir']='/home/'
self.initialize_user_interface()
def initialize_user_interface(self):
""" Design of the user interface.
It mainly consists of a bar menu and a horizontally & vertically
scrollable Text widget in case the text file to read is large.
"""
self.parent.title("Text replacement")
# Set the bar menu and its items
self.menubar=Tkinter.Menu(self.parent)
self.filemenu=Tkinter.Menu(self.menubar,tearoff=0)
self.filemenu.add_command(label="Open",command=self.text_replacement)
self.filemenu.add_command(label="Exit",command=self.parent.quit)
self.menubar.add_cascade(label="File",menu=self.filemenu)
self.parent.config(menu=self.menubar)
self.parent.grid_rowconfigure(0,weight=1)
self.parent.grid_columnconfigure(0,weight=1)
# Set the horizontal and vertical scrollbars
self.hscrollbar=Tkinter.Scrollbar(self.parent,orient=Tkconstants.HORIZONTAL)
self.hscrollbar.grid(row=1,column=0,sticky=Tkinter.E+Tkinter.W)
self.vscrollbar=Tkinter.Scrollbar(self.parent)
self.vscrollbar.grid(row=0,column=1,sticky=Tkinter.N+Tkinter.S)
# Set the Text widget and make it scrollable
self.text=Tkinter.Text(self.parent,wrap=Tkinter.NONE,bd=0,
xscrollcommand=self.hscrollbar.set,
yscrollcommand=self.vscrollbar.set)
self.text.grid(row=0,column=0,sticky=Tkinter.E+Tkinter.W+Tkinter.S+Tkinter.N)
self.text.insert("1.0","Original text here")
self.hscrollbar.config(command=self.text.xview)
self.vscrollbar.config(command=self.text.yview)
def text_replacement(self):
""" Return the name of a file
opened in read mode
"""
self.filename = tkFileDialog.askopenfilename(**self.file_options)
if self.filename:
self.original=self.text.get("0.0","end-1c")
print self.original
with open(self.filename) as self.filetoread:
self.txtfilecontent=self.filetoread.read()
self.filetoread.close()
self.text.delete("1.0", Tkinter.END) # Erase the previous Text widget content
self.text.insert("1.0", self.txtfilecontent)
with open(self.filename,'w') as self.filetowrite:
self.filetowrite.write(self.original)
self.filetowrite.close()
def main():
""" Main method to be executed.
Instantiate Begueradj class
"""
root=Tkinter.Tk()
b=Begueradj(root)
root.geometry("300x250+300+300")
root.mainloop()
if __name__=="__main__":
""" Run the application
"""
main()
Application demo:
The demo consists of 3 screenshots showing:
Original text set into the Text widget and a file dialog window to pick up the file to read.
Loading the content of file1.txt into Tkinter.Text widget
Check if the original file of the Text widget is saved (replaces) in file1.txt
We're trying to store a directory path in a variable using Tkinter's tkFileDialog, and it won't work (details later).
from Tkinter import *
import os
from tkFileDialog import askopenfilename, askdirectory
# Create the window
root = Tk()
# Application title & size
root.title("Title")
root.geometry("1000x600")
# Creating frame to add things to
app = Frame(root)
app.grid() # Adding app frame to grid
# Method that opens file chooser
# Gets used when button is clicked (command)
def openFileBox():
directoryPicked = tkFileDialog.askdirectory()
#easygui.fileopenbox()
for filePicked in os.listdir(directoryPicked):
if filePicked.lower().endswith(".jpg") or filePicked.lower().endswith(".gif") or filePicked.lower().endswith(".png"):
print filePicked
#TODO: add button 'Select Folder'
loaderButton = Button(app)
loaderButton["text"] = "Select Folder"
loaderButton["command"] = openFileBox
loaderButton.grid()
# Tells the program to run everything above
root.mainloop()
So what needs to happen? The way we see it (and we're beginners looking for feedback here), it should be running the openFileBox method when the button is pressed. When the method runs, it should store a selected directory to directoryPicked and print it to the console just to be sure it's working, but when we press the button it simply says 'tkFileDialog' is not defined.
Any thoughts?
It's because you're only importing askopenfilename, askdirectory from tkFileDialog you're not actually importing tkFileDialog itself
So you need to change directoryPicked = tkFileDialog.askdirectory() to directoryPicked = askdirectory()
I am running a script that prompts the user for a file. There is no gui except for the file browser that opens up. I have 2 options: browse for file, or select entire folder using askdirectory(). The latter opens on top of all other windows, but the first one opens under everything, I have to minimize other windows to find it.
Here is the method I'm using for these operations
from Tkinter import Tk
from tkFileDialog import askdirectory, askopenfilename
root = Tk()
root.withdraw()
self.inpath = askdirectory() # To open entire folder
Path = askopenfilename() # Open single file
root.destroy() # This is the very last line in my main script.
This is everything Tk related in my code. askdirectory opens on top, askopenfilename doesn't.
Is there a way to force it to open on top?
root.wm_attributes('-topmost', 1) did it for me. I found it in another SO thread to be honest :-).
I had the same problem.
For me it works with:
file = filedialog.askopenfilename(parent=root)
So, the file dialog gets in front of toplevel window without uncomment root.attributes("-topmost", True)
I want to share that the following lines worked superbly in my case. But I had to use both window.wm_attributes('-topmost', 1) and window=parent to make this work, see below:
import tkinter as tk
from tkinter import filedialog
window = tk.Tk()
window.wm_attributes('-topmost', 1)
window.withdraw() # this supress the tk window
filename = filedialog.askopenfilename(parent=window,
initialdir="",
title="Select A File",
filetypes = (("Text files", "*.txt"), ("All files", "*")))
# Here, window.wm_attributes('-topmost', 1) and "parent=window" argument help open the dialog box on top of other windows
I had the same issue of the file dialog window opening below my current window but I couldn't reproduce the issue with your code (in Python 2 or 3).
This is the minimal example where the issue occurs (context is Windows 10, Python 3, script is called from Idle, and note the input function:
File dialog opens below:
from tkinter import filedialog, Tk
root = Tk()
root.withdraw()
input("\nType anything> ")
file = filedialog.askopenfilename()
To open file dialog on top, both root.lift() or root.attributes("-topmost", True) work (but the latter is specific for Windows)
from tkinter import filedialog, Tk
root = Tk()
#root.attributes("-topmost", True) # this also works
root.lift()
root.withdraw()
input("\nType anything> ")
file = filedialog.askopenfilename()
I'm running python 3.x so there is a difference in code, but both opened on top for me. Try giving it focus, it should put in on top.
self.inpath.focus()
I'm not sure if it's gonna work, since I cant reproduce the problem.