Simple tkinter file path reader is not working - python

I'm new to tkinter and I want to read in a file. This simple operation turns out to be non-trivial. Here is my code:
import tkinter as tk
from matplotlib.backends.backend_tkagg \
import FigureCanvasTkAgg ### PROBLEM 1: REMOVING THIS IMPORT CAUSES AN
### ERROR WHEN OPENING THE DIALOG
def op():
global filename
filename = tk.filedialog.askopenfilename()
root = tk.Tk()
mainframe = tk.Frame(root)
mainframe.grid(column=0, row=0)
mainframe.columnconfigure(0, weight=1)
mainframe.rowconfigure(0, weight=1)
tk.Button(mainframe, text="Open file", command=op).grid(column=0, row=1)
#print(filename) ### PROBLEM 2: UNCOMMENTING THIS CAUSES AN ERROR
### UNLESS I ADD <filename = ""> ABOVE THE op
### FUNCTION DEFINITION
root.mainloop()
Questions:
1) It seems very weird that importing a totally different package, matplotlib, actually has an influence over whether my program works or not. Without that import, clicking on the opening button causes an error. With it, it works fine.
Could this be a bug?
2) Why is filename not accessible outside the function body, even though I'm declaring it global? A minimal working example that has the same structure as my tkinter code is this - and this works:
def test():
global testname
testname = 23
def call_test():
test()
call_test()
print(testname)
Oddly enough, I can get my tkinter code to not produce an error, if I insert a filename = "" at the top - but I still can't actually print out the filename, it's just that the error disappears.
3) Is there any other, more elegant way to access the path of the file I'm opening without using global variables? What is the best practice?

1
I don't think it's fair to call matplotlib "...a totally different package...", let alone calling backend_tkagg that. Internally the second import also imports filedialog. That's why you don't need it. You can replace the second import simply with:
import tkinter.filedialog
2
At the time which:
print(filename)
is executed filename simply does not exist as op was never called before. Python isn't compiled, it is interpreted, it simply skips runtime error(s) until the lines that cause it are run. Try:
op()
print(filename)
to see the difference.
3
Your file path reader does work. It's just you try to print the filepath before it starts to exist, or before it has a path in it.
I think another way in the context would be:
...
def op():
global root
root.filename = tk.filedialog.askopenfilename()
print(root.filename)

Related

Assigning tkinter filedialog value to a different variable

I wish to save the filepath we get from filedialog() in a variable outside the defined function openfile().
Below is the code snippet I am using:
import tkinter as tk
from tkinter import filedialog, Button
root = tk.Tk()
def openfile():
path = filedialog.askopenfilename()
return path
Button(root, text = "click to open the stock file", command=openfile).pack(pady=20)
file_path = openfile() # this seems to be causing the issue
The problem is that the filedialog() is getting executed without even getting clicked on.
You're correct about the cause of the filedialog getting executed. Callback functions like openfile() can't return values because it's tkinter that calls them (and it ignores whatever they return). GUI programs require a different programming paradigm than you're probably used to utilized — they're event-driven. This means that they (mostly) only do things as a result of processing user input. For that reason you will need to save the result of calling the askopenfilename() function in a global variable for use later if the value isn't going to be used immediately.
tkinter provides several different kinds of variable classes — BooleanVar, DoubleVar, IntVar, and StringVar — that are good for this sort of thing. In the code below, I show how to use a StringVar to store the path.
The next step will be adding code that does something with the value getting stored in file_path. One possibility would be to add another GUI element, like a Button, that calls another function that does something with the value.
import tkinter as tk
from tkinter import filedialog, Button
root = tk.Tk()
file_path = tk.StringVar()
def openfile():
path = filedialog.askopenfilename()
file_path.set(path) # Save value returned.
Button(root, text="click to open the stock file", command=openfile).pack(pady=20)
root.mainloop()
Here’s what you can do:
def open_file():
global path
path = filedialog.askopenfilename()
And then you can access the path variable wherever you want in the program(after the function is ran, obviously.)

When using Tkinter, error: TclError: image "pyimage8" doesn't exist

I keep getting the error, TclError: image "pyimage8" doesn't exist.
It is strange, as the number increases every time I run it?
I'm running python using spyder, dunno whether this affects anything.
Here is my code:
#import tkinter
import Tkinter as tk
homescreenImage = PhotoImage(file="Homescreen.gif")
#create a GUI window.
root = Tk()
#set the title.
root.title("Welcome to the Pit!")
#set the size.
root.geometry("1100x700")
homescreenFrame = tk.Frame(root, width=1100, height = 700)
homescreenFrame.pack()
homescreenLabel = tk.Label(homescreenFrame, image=homescreenImage)
homescreenLabel.pack()
#start the GUI
root.mainloop()
I found that my script would run once and then give me an error on subsequent runs. If I restarted the console, it would run again. I solved the problem by using the following code in the beginning of my script:
import sys
if "Tkinter" not in sys.modules:
from Tkinter import *
It works every time now.
If you import Tkinter as tk you should use the alias tk when calling tk, eg. root = tk.Tk(). Otherwise Python will not find Tk.
You don't need to import PIL for this.
You can not create a Photoimage before you create Tk.
Try this:
import Tkinter as tk
root = tk.Tk()
root.title("Welcome to the Pit!")
root.geometry("1100x700")
homescreenImage = tk.PhotoImage(file="Homescreen.gif")
homescreenFrame = tk.Frame(root, width=1100, height = 700,)
homescreenFrame.pack()
homescreenLabel = tk.Label(homescreenFrame, image=homescreenImage)
homescreenLabel.pack()
root.mainloop()
Be kind and paste the whole error message in your question also.
Following could be the errors:
1) Give the whole path to the file name
eg: "/home/user/Homescreen.gif"
2) If you are using windows and the above doesn't work:
use "\\C:\\home\\Homescreen.gif" (this is because, windows gets confused)
3) If that also, doesn't work, ensure that the directory of your python
program is the same as that of the image.
4) Also, create the photoimage only after you have created the root
window.
5) For some reason, while running in the debugger, if any previous
executions had thrown errors I get the "pyimage doesn't exist" error.
However, if I restart the debugger (or no previously executed scripts
have thrown errors), then the program runs fine.
6) Also, don't import PIL, it's not required.
Try all the above, if it doesn't work let me know.
Hope this helps.
i think this could be due to :
tkinter only supports .png format for images
Yet, there are other ways to add .gif`` instead of PhotoImage```
In my case, it was because I forgot to keep a reference to the image. Try adding this line after creating the label:
homescreenLabel.image=homescreenImage.
You should use Toplevel window that is directly managed by the window manager.
Just change :
root = Tk() to root = Toplevel()

Create a simple app in tkinter for displaying map

I am new to Tkinter,
I have a program which takes CSV as input containing, outlet's geo-location,
display it on a map, saving it as HTML.
format of my csv:
outlet_code Latitude Longitude
100 22.564 42.48
200 23.465 41.65
... and so on ...
Below is my python code to take this CSV and put it on a map.
import pandas as pd
import folium
map_osm = folium.Map(location=[23.5747,58.1832],tiles='https://korona.geog.uni-heidelberg.de/tiles/roads/x={x}&y={y}&z={z}',attr= 'Imagery from GIScience Research Group # University of Heidelberg — Map data © OpenStreetMap')
df = pd.read_excel("path/to/file.csv")
for index, row in df.iterrows():
folium.Marker(location=[row['Latitude'], row['Longitude']], popup=str(row['outlet_code']),icon=folium.Icon(color='red',icon='location', prefix='ion-ios')).add_to(map_osm)
map_osm
This will take display map_osm
Alternate way is to save map_osm as HTML
map_osm.save('path/map_1.html')
What I am looking for is a GUI which will do the same thing.
i.e prompt user to input the CSV, then execute my code below and display result
or at least save it in a location.
Any leads will be helpful
You question would be better received if you had provided any code you attempted to write for the GUI portion of your question. I know (as well as everyone else who posted on your comments) that tkinter is well documented and has countless tutorial sites and YouTube videos.
However if you have tried to write code using tkinter and just don't understand what is going on, I have written a small basic example of how to write up a GUI that will open a file and print out each line to the console.
This won't right out answer your question but will point you in the right direction.
This is a non-OOP version that judging by your existing code you might better understand.
# importing tkinter as tk to prevent any overlap with built in methods.
import tkinter as tk
# filedialog is used in this case to save the file path selected by the user.
from tkinter import filedialog
root = tk.Tk()
file_path = ""
def open_and_prep():
# global is needed to interact with variables in the global name space
global file_path
# askopefilename is used to retrieve the file path and file name.
file_path = filedialog.askopenfilename()
def process_open_file():
global file_path
# do what you want with the file here.
if file_path != "":
# opens file from file path and prints each line.
with open(file_path,"r") as testr:
for line in testr:
print (line)
# create Button that link to methods used to get file path.
tk.Button(root, text="Open file", command=open_and_prep).pack()
# create Button that link to methods used to process said file.
tk.Button(root, text="Print Content", command=process_open_file).pack()
root.mainloop()
With this example you should be able to figure out how to open your file and process it within a tkinter GUI.
For a more OOP option:
import tkinter as tk
from tkinter import filedialog
# this class is an instance of a Frame. It is not required to do it this way.
# this is just my preferred method.
class ReadFile(tk.Frame):
def __init__(self):
tk.Frame.__init__(self)
# we need to make sure that this instance of tk.Frame is visible.
self.pack()
# create Button that link to methods used to get file path.
tk.Button(self, text="Open file", command=self.open_and_prep).pack()
# create Button that link to methods used to process said file.
tk.Button(self, text="Print Content", command=self.process_open_file).pack()
def open_and_prep(self):
# askopefilename is used to retrieve the file path and file name.
self.file_path = filedialog.askopenfilename()
def process_open_file(self):
# do what you want with the file here.
if self.file_path != "":
# opens file from file path and prints each line.
with open(self.file_path,"r") as testr:
for line in testr:
print (line)
if __name__ == "__main__":
# tkinter requires one use of Tk() to start GUI
root = tk.Tk()
TestApp = ReadFile()
# tkinter requires one use of mainloop() to manage the loop and updates of the GUI
root.mainloop()

When importing modules written with tkinter and ttk, stuff doesn't work

I'm new to programming, Python, this website, and actually using these kinds of websites in general, so hear me out.
I've been writing a module for a larger program using the tkinter module and ttk module, and when I import my own module into the main program, for some reason none of the ttk stuff works as it should. I mean, it appears, but the style I've written for it (s=ttk.Style(); s.configure...etc.) doesn't change it in anyway. When I run the module on its own, everything works fine. When it's imported into the main program, it just doesn't.
Not only this, but when using entry boxes, I've only just discovered that the way I'd been told to use them, with, for example, var=StringVar() as the textvariable (which again works fine when the module is run on its own), now just leaves the variable var as empty when var.get() is called. Now I've sorted this by just removing all mention of StringVar() (wish I'd known how redundant these really are), but I'd still like to know why importing them in to the main program causes them to malfunction so badly. I would give you some sample code but there's so much I'd struggle to be selective enough...
I'd appreciate any guidance you can offer.
EDIT: Would giving you something like this have helped?
stackoverflowmodule.py
import sys
from tkinter import *
from tkinter import ttk
import time
from random import randint, choice
class Decimals():
def Question1(self):
DECFrame.destroy()
frame1=ttk.Frame(DECmaster, height=height, width=width, style="NewFrame.TFrame")
frame1.pack()
Q1Label=ttk.Label(frame1, text="Question 1:", style="TitleLabel.TLabel")
Q1Label.grid(column=0, row=0, pady=(50,0))
answer=StringVar()
entry1=ttk.Entry(frame1, textvariable=answer)
entry1.grid(column=0, row=1, pady=(200,0))
# Typing in Hello should give a correct answer.
def Question1Attempt():
attempt=answer.get()
if attempt!="Hello":
print("Incorrect")
else:
print("Correct")
button=ttk.Button(frame1, text="Ok", command=Question1Attempt)
button.grid(column=0, row=2, pady=(30,0))
def Start():
global DECmaster
global s
global DECFrame
global DEC
global width
global height
DECmaster = Tk()
width=str(1000)
height=str(800)
x1=str(0)
y1=str(0)
DECmaster.geometry(width+"x"+height+"+"+x1+"+"+y1)
DECmaster.configure(bg="#8afff0")
s=ttk.Style()
s.configure("NewFrame.TFrame", background="#8afff0")
s.configure("TitleLabel.TLabel", foreground= "blue", background="#8afff0")
DECFrame=ttk.Frame(DECmaster, style="NewFrame.TFrame")
DECFrame.pack()
TitleLabel=ttk.Label(DECFrame, text="Test for Decimals", style="TitleLabel.TLabel")
TitleLabel.grid(column=1, row=0, pady=(50,0), sticky=N)
DEC=Decimals()
button=ttk.Button(DECFrame, text="Start", command=DEC.Question1)
button.grid(column=2, row=2, pady=(200,0), sticky=N)
DECmaster.mainloop()
stackoverflowprogram.py
from tkinter import *
from tkinter import ttk
import time
import stackoverflowmodule
root = Tk()
width=str(1000)
height=str(800)
x1=str(0)
y1=str(0)
##width=str(1228)
##height=str(690)
##x1=str(-1)
##y1=str(-22)
root.geometry(width+"x"+height+"+"+x1+"+"+y1)
root.configure(bg="#8afff0")
s=ttk.Style()
s.configure("NewFrame.TFrame", background="#8afff0")
s.configure("TitleLabel.TLabel", foreground= "blue", background="#8afff0")
Testframe=ttk.Frame(root, height=height, width=width, style="NewFrame.TFrame")
Testframe.pack()
Titlelabel=ttk.Label(Testframe, text="Start Test:", style="TitleLabel.TLabel")
Titlelabel.grid(column=0, row=0, pady=(50,0))
def StartTest():
stackoverflowmodule.Start()
button=ttk.Button(Testframe, text="Start", command=StartTest)
button.grid(column=0, row=1, pady=(100,0))
root.mainloop()
I realise there's an awful lot there, but I couldn't really demonstrate my point without it all. Thanks again.
The root of your problem is that you're creating more than once instance of Tk. A Tkinter app can only have a single instance of of the Tk class, and you must call mainloop exactly once. If you need additional windows you should create instances of Toplevel (http://effbot.org/tkinterbook/toplevel.htm).
If you want to create modules with reusable code, have your modules create subclasses of Frame (or Toplevel if you're creating dialos). Then, your main script will create an instance of Tk, and place these frames in the main window or in subwindows.
If you want to sometimes use your module as a reusable component and sometimes as a runnable program, put the "runnable program" part inside a special if statement:
# module1.py
import Tkinter as tk
class Module1(tk.Frame):
def __init__(self, *args, **kwargs):
label = tk.Label(self, text="I am module 1")
label.pack(side="top", fill="both", expand=True)
# this code will not run if this module is imported
if __name__ == "__main__":
root = tk.Tk()
m1 = Module1(root)
m1.pack(side="top", fill="both", expand=True)
In the above code, if you run it like python module1.py, the code in that final if statement will run. It will create a root window, create an instance of your frame, and make that frame fill the main window.
If, however, you import the above code into another program, the code in the if statement will not run, so you don't get more than one instance of Tk.
Let's assume you have two modules like the above, and want to write a program that uses them, and each should go in a separate window. You can do that by writing a third script that uses them both:
# main.py
import Tkinter as tk
from module1 import Module1
from module2 import Module2
# create the main window; every Tkinter app needs
# exactly one instance of this class
root = tk.Tk()
m1 = Module1(root)
m1.pack(side="top", fill="both", expand=True)
# create a second window
second = tk.Toplevel(root)
m2 = Module2(second)
m2.pack(side="top", fill="both", expand=True)
# run the event loop
root.mainloop()
With the above, you have code in two modules that can be used in three ways: as standalone programs, as separate frames within a single window, or as separate frames within separate windows.
You can't create two instances of tkinter.Tk. If you do, one of two things will happen.
Most of the code in the script may just not run, because it's waiting for the module's mainloop to finish, which doesn't happen until you quit.
If you structure things differently, you'll end up with two Tk instances, only one of which is actually running. Some of the code in your script will happen to find the right Tk instance (or the right actual Tk objects under the covers), because there's a lot of shared global stuff that just assumes there's one Tk "somewhere or other" and manages to find. But other code will find the wrong one, and just have no effect. Or, occasionally, things will have the wrong effect, or cause a crash, or who knows what.
You need to put the top-level application in one place, either the module or the script that uses it, and have the other place access it from there.
One way to do this is to write the module in such a way that its code can be called with a Tk instance. Then, use the __main__ trick so that, if you run the module directly as a script (rather than importing it from another script), it creates a Tk instance and calls that code. Here's a really simple example.
tkmodule.py:
from tkinter import *
def say_hi():
print("Hello, world!")
def create_interface(window):
hi = Button(window, text='Hello', command=say_hi)
hi.pack()
if __name__ == '__main__':
root = Tk()
create_interface(root)
root.mainloop()
tkscript.py:
from tkinter import *
import tkmodule
i = 0
def count():
global i
i += 1
print(i)
def create_interface(window):
countbtn = Button(window, text='Count', command=count)
countbtn.pack()
root = Tk()
create_interface(root)
window = Toplevel(root)
tkmodule.create_interface(window)
root.mainloop()
Now, when you run tkscript.py, it owns one Tk instance, and passes it to its own create_frame and to tkmodule.create_frame. But if you just run tkmodule.py, it owns a Tk instance, which it passes to its own create_frame. Either way, there's exactly one Tk instance, and one main loop, and everyone gets to use it.
Notice that if you want two top-level windows, you have to explicitly create a Toplevel somewhere. (And you don't want to always create one in tkmodule.py, or when you run the module itself, it'll create a new window and leave the default window sitting around empty.)
Of course an even simpler way to do this is to put all of your GUI stuff into modules that never create their own Tk instance, and write scripts that import the appropriate modules and drive them.

Python code problem, application has been destroyed Tcl error

I am making a Tkinter GUI to do nothing except call images - and of course, I have struggled to find decent tkinter documentation all along.
There is a line of my code which cannot seem to do as asked - I want to call up all the values in a dictionary and individually print and pull an image by the same name for each one before the next value is called up. I have tried dict.itervalues() and dict.values() and can't seem to figure anything out altogether...
Anyway, here is the snippet:
for key in ansDict.iterkeys(): #using the iterkeys function... kind of
x=key
root = tk.Tk() # root window created (is this in the right place?)
root.title('C H E M I S T R Y A B C\'s')
frameAns=tk.Frame(root)
frameAns.grid(row=0, column=0, sticky=tk.NW)
for i in range(len(ansDict[x])):
print '-->' + ansDict[x][i]
for value in ansDict.itervalues(): #This is the most important part
for i in range(len(value)): #pulls value list from dictionary named ansDict
picRef1 = Image.open(value[i] + '.jpg') #calls image file by the same name using PIL
photo1 = ImageTk.PhotoImage(picRef1, master=root)
button1 = tk.Button(frameAns, compound=tk.TOP, image=photo1, text=str(value[i]) + '\nClose me!', bg='white') #pulls up button onto which the image is pasted
button1.grid(sticky=tk.NW, padx=2, pady=2) #places button on grid
button1.image=photo1
root.mainloop()
Finally, at the end, it pulls up one or two images and then I get the following error:
TclError: can't invoke "image" command: application has been destroyed
and I can't figure out what is wrong. I can't move the image command, and somehow I need to "save" it so it isn't destroyed. I know there are other code errors here, but I think that if I figure out the TclError that I am getting that I can set everything else straight.
If there is an easier way to do all this please do tell!
I have looked around for a good solution to this but have yet to find the proper solution. Looking at the Tkinter.py class it looks like the Image del value is:
def __del__(self):
if self.name:
try:
self.tk.call('image', 'delete', self.name)
except TclError:
# May happen if the root was destroyed
pass
This means if you wanted to do a BRUTAL hack you could setup a PhotoImage as described in jtp's link.
photo = tk.PhotoImage(file="C:/myimage.gif")
widget["image"] = photo
widget.image = photo
Then you could just before the program exited do the following hack:
photo.name = None
This would prevent it from trying to clean itself up in the PhotoImage delete and prevent the exception from being called in the del method. I do not really recommend you do this unless your back is up against the wall, and you have no alternative.
I will continue to look into this and if I find a better solution will edit this post with a better one (hopefully someone will give the correct solution before then).
Here is one possibility, although it is structured differently than your example. It stacks the four 100 pixel square images on top of one another. I believe you need to keep a separate reference to each Image object, so I tucked them away in the images dictionary.
from Tkinter import *
import os
from PIL import Image, ImageTk
image_names = { '1':'one','2':'two','3':'three','4':'four' }
images = {}
root = Tk()
root.title("HELLO")
frm = Frame(root)
for v in image_names.itervalues():
images[v] = {}
images[v]['i'] = Image.open("%s%s.jpg" % (os.path.dirname(__file__), v))
images[v]['pi'] = ImageTk.PhotoImage(images[v]['i'])
images[v]['b'] = Button(frm, image=images[v]['pi'])
images[v]['b'].pack()
frm.pack()
mainloop()
Here is a good link discussing the PhotoImage class.
http://effbot.org/tkinterbook/photoimage.htm
It seems that you did not get the idea of Event-driven programming. You should create whole GUI once, fill it with widgets, setup the events and then enter infinite loop. The GUI should call callback functions based on your event to function binding. So those parts of your program should definitely be called just once: root = tk.Tk(), root.mainloop().
Edit: Added Event-driven programming "idea example".
from Tkinter import *
master = Tk()
def callback():
print "click!"
b = Button(master, text="OK", command=callback)
b.pack()
mainloop()

Categories