Using Tkinter filedialog within a frame and accessing the output globally - python

I am building my first GUI using tkinter and have come up against some problems. To make the code more modular, I am using an object-oriented approach, as seen in the code below. The basic idea is that I have defined classes for the DataFrame, MetaFrame and SaveFrame, which are all instantiated within the OptionsFrame, which then is instantiated within the MainWindow.
import tkinter as tk
from tkinter import ttk
class DataFrame(ttk.Frame):
def __init__(self, main, *args, **kwargs):
super().__init__(main, *args, **kwargs)
# data frame elements
self.data_label = ttk.Label(self, text="Add Data:")
self.labelled_tweets_label = ttk.Label(self, text="Labelled-Tweets: ")
self.labelled_tweets_button = ttk.Button(self, text="Browse")
self.places_label = ttk.Label(self, text="Places: ")
self.places_button = ttk.Button(self, text="Browse")
self.plots_label = ttk.Label(self, text="Plots Path: ")
self.plots_button = ttk.Button(self, text="Browse")
self.submit_button = ttk.Button(self, text="Submit")
# data frame layout
self.data_label.grid(row=0, column=0, columnspan=2, pady=10)
self.labelled_tweets_label.grid(row=1, column=0)
self.labelled_tweets_button.grid(row=1, column=1)
self.places_label.grid(row=2, column=0)
self.places_button.grid(row=2, column=1)
self.plots_label.grid(row=3, column=0)
self.plots_button.grid(row=3, column=1)
self.submit_button.grid(row=4, column=0, columnspan=2, pady=10)
class MetaFrame(ttk.Frame):
...
class SaveFrame(ttk.Frame):
...
class OptionsFrame(ttk.Frame):
def __init__(self, main, *args, **kwargs):
super().__init__(main, *args, **kwargs)
# options frame components
self.data_frame = DataFrame(self)
self.horiz1 = ttk.Separator(self, orient="horizontal")
self.meta_frame = MetaFrame(self)
self.horiz2 = ttk.Separator(self, orient="horizontal")
self.save_frame = SaveFrame(self)
# options frame layout
self.data_frame.grid(row=0, column=0)
self.horiz1.grid(row=1, column=0, sticky="ew", pady=30)
self.meta_frame.grid(row=2, column=0)
self.horiz2.grid(row=3, column=0, sticky="ew", pady=30)
self.save_frame.grid(row=4, column=0, sticky="s")
class MainWindow(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.geometry("800x600")
self.resizable(False, False)
# configuration
self.columnconfigure(index=0, weight=1)
self.columnconfigure(index=1, weight=2)
# main frames
self.options_frame = OptionsFrame(self, width=400, height=600, borderwidth=1)
self.vert = ttk.Separator(self, orient="vertical")
# main layout
self.options_frame.grid(row=0, column=0)
self.vert.grid(row=0, column=1, sticky="ns")
def main():
root = MainWindow()
root.mainloop()
The layout can be seen in the following image.
This is the basic layout I want within the OptionsFrame. My confusion lies with creating filedialog methods for the three file browsing buttons within the DataFrame. I understand how to use the filedialog class to return the path to a given file, but then this value is restricted to be in the scope of the DataFrame.
I have a back-end that is already developed which requires these file paths, so ideally I would like to access them from the main() function. How is this possible?
Thanks

Related

tkinter problem in displaying two frames using tkraise

I'm trying to display two frames in a way such that one frame has a button to display the other frame and vice versa. I'm trying to use tkinter frame function of tkraise(). But they are getting simultaneously displayed over each other. Please you can ignore the import board file because it has some helper functions that I'm using to display some data on the GUI.
Here is my code:
import tkinter as tk
import tkinter.scrolledtext as st
from board import *
FONT = ("Helvetica", 20)
attributes = {'padx': 5, 'pady': 5}
class App(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.title('DAS')
self.geometry('500x325')
container = tk.Frame(self)
container.pack(fill=tk.BOTH, expand=True)
self.frames = {}
self.frames[0] = Display(container, self)
self.frames[0].grid(row=0, column=0)
self.frames[1] = DataStored(container, self)
self.frames[1].grid(row=0, column=0)
self.show_frame(1)
def show_frame(self, frame):
frame = self.frames[frame]
frame.tkraise()
class Display(tk.Frame):
def __init__(self, container, controller):
super().__init__(container)
name = tk.Label(self, text='Data Acquistion System', font=FONT)
name.pack(fill='x')
left_wrapper = tk.Frame(self)
left_wrapper.pack(side='left')
right_wrapper = tk.Frame(self)
right_wrapper.pack(side='left')
port_label = tk.Label(left_wrapper, text='Port:', font=FONT)
port_label.grid(row=0, column=0)
port_option = tk.StringVar(left_wrapper, value='None')
ports_availiable = get_arduino_ports()
port_value = tk.OptionMenu(left_wrapper, port_option, *ports_availiable)
port_value.grid(row=0, column=1)
baud_label = tk.Label(left_wrapper, text='Baudrate:', font=FONT)
baud_label.grid(row=1, column=0)
baud_value = tk.Entry(left_wrapper)
baud_value.grid(row=1, column=1)
baud_value.insert(0, 9600)
connect_button = tk.Button(left_wrapper, text='Connect', command=lambda: connect_to_port(port_option.get(), baud_value.get()))
connect_button.grid(row=2, column=1)
playback_button = tk.Button(left_wrapper, text='Playback', command=lambda: controller.show_frame(1))
playback_button.grid(row=3, column=1)
data_display = st.ScrolledText(right_wrapper)
data_display.pack()
class DataStored(tk.Frame):
def __init__(self, container, controller):
super().__init__(container)
tk.Label(self, text='Playback Data').pack(fill=tk.X)
top_frame = tk.Frame(self)
top_frame.pack(fill=tk.X)
bottom_frame = tk.Frame(self)
bottom_frame.pack(fill=tk.X)
canvas = st.ScrolledText(top_frame)
canvas.pack()
update = tk.Button(bottom_frame, text='Update', command=self.update_playback)
update.grid(row=0, column=0)
back = tk.Button(bottom_frame, text='Back', command=lambda: controller.show_frame(0))
back.grid(row=0, column=1)
def update_playback(self):
with open("data.txt", "r") as file:
data = file.read()
self.data.insert(tk.END, data)
self.data.config(state="disabled")
if __name__ == "__main__":
window = App()
window.mainloop()
There is a simple solution by changing the way in which you use your show_frame function:
def show_frame(self, frame_num):
for frame in self.frames.values():
frame.grid_remove()
frame = self.frames[frame_num]
frame.grid()
The reason tkraise does not work is that your two frames are of different sizes, if they were the same rise you would be entirely right with your code. Because they are of different sizes you can still see the bigger frame when looking at the smaller one.
grid_remove removes a frame from being loaded on the window, and then the following grid command places the frame back where it was before, removing any issues with overlap and such.
As the two frames have different sizes, you need to add sticky="nsew" in .grid(...) to make both frames to occupy the available space:
self.frames[0] = Display(container, self)
self.frames[0].grid(row=0, column=0, sticky="nsew")
self.frames[1] = DataStored(container, self)
self.frames[1].grid(row=0, column=0, sticky="nsew")

Tkinter / Python: Frames aren't lifting

Hi I was wondering why my DisplayPage wasn't lifting when the ok button was pressed in MainPage. I've cut off most of my code (to keep it more to the point of the error, so its a bit segmented but basically I'm creating 2 frames (MainPage and DisplayPage) and want to basically let the user input data into MainPage and then press the next button to show/output the input data in DisplayPage.
Here's a section of the code that's creating the unexpected result.
from Tkinter import *
import Tkinter as tk
import os
class page(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
def show(self):
self.lift()
class DisplayPage(page):
def __init__(self, *args, **kwargs):
page.__init__(self, *args, **kwargs)
# deleted - however will upload if needed more information about my code
class MainPage(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
p1 = DisplayPage(self)
# create all of the main containers
frame_A = Frame(self, width=930, height=780)
frame_B = Frame(self, width=465, height=280)
frame_C = Frame(self, width=465, height=280)
frame_D = Frame(self, width=465, height=140)
frame_E = Frame(self, width=465, height=70)
# layout all of the main containers
frame_A.grid(row=0, column=0, columnspan=2, rowspan=3)
frame_B.grid(row=0, column=3)
frame_C.grid(row=1, column=3)
frame_D.grid(row=2, column=3)
frame_E.grid(row=3, column=3)
# next ok button
content4= Frame(frame_E)
ok = tk.Button(content4, text="Locate", font =('Roboto Thin', 30), command= p1.lift)
ok.pack()
# layout all widgets
content1.grid(column=0, row=0)
content2.grid(column=3, row=1)
content3.grid(column=3, row=2)
content4.grid(column=3, row=3)
namelbl.grid(column=3, row=1)
name.grid(column=3, row=6)
namelbl2.grid(column=3, row=5)
name2.grid(column=3, row=8)
namelbl3.grid(column=3, row=7)
name3.grid(column=3, row=10)
one.grid(column=3, row=2)
two.grid(column=3, row=3)
three.grid(column=3, row=4)
ok.grid(column=3, row=11)
namelbl4.grid(column=3, row=9)
p1.show()
if __name__ == "__main__":
root = tk.Tk()
main = MainPage(root)
main.pack(side="top", fill="both", expand=True)
root.wm_title('MobilePark Simulator')
root.wm_geometry("1300x830")
root.mainloop()
You must create a toplevel window. I have modified your code, but only put the most necessary into it. You can place widgets in a toplevel window, too (see the label).
import Tkinter as tk
import os
class MainPage(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.p1flag=0 # EDIT1
self.p1=tk.Toplevel(self)
self.p1.title("Hello")
self.p1label=tk.Label(self.p1,text="FYI "*20)
self.p1label.pack()
# create all of the main containers
frame_A = tk.Frame(self, width=930, height=780)
frame_B = tk.Frame(self, width=465, height=280)
frame_C = tk.Frame(self, width=465, height=280)
frame_D = tk.Frame(self, width=465, height=140)
frame_E = tk.Frame(self, width=465, height=70)
frame_A.grid(row=0, column=0, columnspan=2, rowspan=3)
frame_B.grid(row=0, column=3)
frame_C.grid(row=1, column=3)
frame_D.grid(row=2, column=3)
frame_E.grid(row=3, column=3)
content4= tk.Frame(frame_E)
self.ok = tk.Button(content4, text="Locate", font =('Roboto Thin', 30), command= self.show_p1) # EDIT2
self.ok.pack()
# layout all widgets
content4.grid(column=3, row=3)
def show_p1(self): # EDIT3
if not self.p1flag%2:
self.p1.lift()
else:
self.p1.lower()
self.p1flag+=1
if __name__ == "__main__":
root = tk.Tk()
main = MainPage(root)
main.pack(side="top", fill="both", expand=True)
root.wm_title('MobilePark Simulator')
root.wm_geometry("1200x900")
root.mainloop()

Tkinter OptionMenu widget is not displaying values

At the moment I am working on a project using Python 3.6 Tkinter. At the moment, I am trying to make a user access rights OptionMenu for me to put either "User" or "Admin". I have tried various methods, but cannot seem to either fix it myself or to find helpful documentation online.
The problem isn't in making the OptionMenu and displaying it, nor is it that the value of the StringVar variable isn't changing. The problem is that the text inside of the OptionMenu isn't changing when any new option is selected.
class UsersDetailsEditPage(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
self.title("Edit User Details")
self.option_add("*Font", 'TkDefaultFont')
self.noteBook = ttk.Notebook(self)
for i in range(len(users)):
self.noteBook.add(self.getUserViewFrame(users[i]), text=users[i][2])
self.noteBook.pack()
self.resizable(width=False, height=False)
def getUserViewFrame(self, user):
frame = Frame(self)
frame.grid_rowconfigure(1, weight=1)
frame.grid_columnconfigure(1, weight=1)
Label(frame, text="User's name:").grid(row=0, column=0, sticky=W)
nameText = Text(frame, height=1, width=20)
nameText.insert("1.0", user[2])
nameText.edit_reset()
nameText.grid(row=0, column=1, sticky=E)
Label(frame, text="Username:").grid(row=1, column=0, sticky=W)
usernameText = Text(frame, height=1, width=20)
usernameText.insert("1.0", user[0])
usernameText.edit_reset()
usernameText.grid(row=1, column=1, sticky=E)
Label(frame, text="Password:").grid(row=2, column=0, sticky=W)
passwordText = Text(frame, height=1, width=20)
passwordText.insert("1.0", user[1])
passwordText.edit_reset()
passwordText.grid(row=2, column=1, sticky=E)
# the constructor syntax is:
# OptionMenu(master, variable, *values)
Label(frame, text="User Access:").grid(row=3, column=0, sticky=W)
self.options = StringVar()
self.options.set("User")
self.userAccessDrop = ttk.OptionMenu(frame, self.options, "User", *("User", "Admin"))
self.userAccessDrop.config(width=10)
self.userAccessDrop.grid(row=3, column=1, sticky=E)
return frame
This is the output of the code
I have all the library imports that are needed (I think):
from tkinter import *
from tkinter import messagebox
import tkinter.ttk as ttk
import csv
import os
If anyone can work out how to make this work it would be much appreciated.
Thanks
I tried your code and it works for me. I can only guess that this is a garbage collection problem. Try assigning the option menu to frame rather than self, so that it doesn't get overwritten.
frame.options = StringVar()
frame.options.set("User")
frame.userAccessDrop = ttk.OptionMenu(frame, frame.options, "User", *("User", "Admin"))
frame.userAccessDrop.config(width=10)
frame.userAccessDrop.grid(row=3, column=1, sticky=E)
You really should rewrite this so that you subclass a Frame to make the user instances.
Edit: for example:
import tkinter as tk
from tkinter import ttk
class UserFrame(tk.Frame):
def __init__(self, master=None, data=None, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
data = data[2], data[0], data[1] # rearrange data
labels = ("User's name:", "Username:", "Password:")
for row, (label, value) in enumerate(zip(labels, data)):
lbl = tk.Label(self, text=label)
lbl.grid(row=row, column=0, sticky=tk.W)
ent = tk.Entry(self)
ent.insert(0, value)
ent.grid(row=row, column=1, sticky=tk.E)
lbl = tk.Label(self, text="User Access:")
lbl.grid(row=3, column=0, sticky=tk.W)
self.options = tk.StringVar(self, "User")
self.userAccessDrop = ttk.OptionMenu(self,
self.options,
"User", # starting value
"User", "Admin", # options
)
self.userAccessDrop.config(width=10)
self.userAccessDrop.grid(row=len(labels), column=1, sticky=tk.E)
class UsersDetailsEditPage(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title("Edit User Details")
self.option_add("*Font", 'TkDefaultFont')
self.noteBook = ttk.Notebook(self)
for user in users:
self.noteBook.add(UserFrame(self, user), text=user[2])
self.noteBook.pack()
self.resizable(width=False, height=False)
It's really not functionally any different from what you have, just neater code (which actually makes a huge difference in coding time). Note I also got rid of the evil wildcard import and condensed your code a bit. Remember, if you are copying and pasting code blocks you are doing the computer's job. I also moved you to Entry widgets, which are a single line Text widget. Also, try to avoid the "convenience" initializing and laying out a widget on the same line. It's ugly and it leads to bugs.
I used 'self' in for the "self.options" and "self.userAccessDrop", but since we subclassed the Frame, "self" now refers to the Frame instance. In other words it's the same as my above block where I used "frame.options".

Chemspider returns no value when run within a class

I'm trying to create a chemistry moles calculator using Python and the Chemspider API. I got this code working, which searches the database by name:
===THIS WORKS FINE===
import tkinter as tk
from tkinter import ttk
import chemspipy
from chemspipy import ChemSpider
cs = ##MY CHEMSPIDER ID
def NameSearch(*args, **kwargs):
namesearch = tk.Tk()
namesearch.title("Search via Name")
def ChemSearch(*args, **kwargs):
try:
ChemName = ChemicalName.get()
ChemName = cs.search(ChemName)
if len(ChemName) >= 1:
ChemicalID.set(ChemName[0])
print(ChemicalID)
else:
popupmsg("Sorry, we could not find that chemical. Please try again")
except ValueError:
pass
searchframe = ttk.Frame(namesearch)
searchframe.grid(column=0, row=0, sticky="nsew")
searchframe.columnconfigure(0, weight=1)
searchframe.rowconfigure(0, weight=1)
ChemicalName = tk.StringVar()
ChemicalID = tk.StringVar()
Chemical_entry = ttk.Entry(searchframe, width=7, textvariable=ChemicalName)
Chemical_entry.grid(column=3, row=1, sticky="we")
ttk.Label(searchframe, textvariable=ChemicalID).grid(column=2, row=3, sticky=("ew"))
ttk.Button(searchframe, text="Search", command=ChemSearch).grid(column=3, row=4, sticky="w")
ttk.Label(searchframe, text="Chemical Name").grid(column=1, row=1, sticky="w")
ttk.Label(searchframe, text="The ID of this chemical is: ").grid(column=1, row=3, sticky="e")
for child in searchframe.winfo_children(): child.grid_configure(padx=5, pady=5)
Chemical_entry.focus()
namesearch.bind("<Return>", ChemSearch)
NameSearch()
I then decided to implement a menu for my program using tkinter like this:
cs = ##MY CHEMSPIDER ID
def popupmsg(msg):
popup = tk.Tk()
def leavemini():
popup.destroy()
popup.wm_title("!")
label = ttk.Label(popup, text=msg)
label.pack(side="top", fill="x", pady=10)
B1 = ttk.Button(popup, text="Okay", command = leavemini)
B1.pack()
def NameSearch(cs, *args, **kwargs):
namesearch = tk.Tk()
namesearch.title("Search via Name")
def ChemSearch(*args, **kwargs):
try:
ChemName = ChemicalName.get()
ChemName = cs.search(ChemName)
if len(ChemName) >= 1:
ChemicalID.set(ChemName[0])
else:
popupmsg("Sorry, we could not find that chemical. Please try again")
print(ChemicalID)
except ValueError:
print("a")
pass
searchframe = ttk.Frame(namesearch)
searchframe.grid(column=0, row=0, sticky="nsew")
searchframe.columnconfigure(0, weight=1)
searchframe.rowconfigure(0, weight=1)
ChemicalName = tk.StringVar()
ChemicalID = tk.StringVar()
Chemical_entry = ttk.Entry(searchframe, width=7, textvariable=ChemicalName)
Chemical_entry.grid(column=3, row=1, sticky="we")
ttk.Label(searchframe, textvariable=ChemicalID).grid(column=2, row=3, sticky=("ew"))
ttk.Button(searchframe, text="Search", command=ChemSearch).grid(column=3, row=4, sticky="w")
ttk.Label(searchframe, text="Chemical Name").grid(column=1, row=1, sticky="w")
ttk.Label(searchframe, text="The ID of this chemical is: ").grid(column=1, row=3, sticky="e")
for child in searchframe.winfo_children(): child.grid_configure(padx=5, pady=5)
Chemical_entry.focus()
namesearch.bind("<Return>", ChemSearch)
class ChemApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self, "Chemistry App")
container = tk.Frame(self)
container.pack(side="top", fill="both", expand = True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
menubar = tk.Menu(container)
searchmenu = tk.Menu(menubar, tearoff=0)
searchmenu.add_command(label="Search by Name", command=lambda: NameSearch(cs))
searchmenu.add_separator()
searchmenu.add_command(label="Exit", command=quit)
menubar.add_cascade(label="Search", menu=searchmenu)
tk.Tk.config(self, menu=menubar)
self.frames = {}
for F in (StartPage,):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
label = ttk.Label(self, text="Start Page")
label.pack(pady=10,padx=10)
button = ttk.Button(self, text="Search for a Chemical",
command=lambda: controller.show_frame(NameSearch))
button.pack()
app = ChemApp()
app.geometry("1280x720")
app.mainloop()
However when run through a function triggered by the pressing of a tkinter menu button (under search in the GUI), it refuses to return a correct value from the database. As far as I'm aware, I've passed all the required variables, and it is running in an identical environment to before, it just won't work.
Any help would be greatly appreciated as it has been troubling
You can't have more than once instance of Tk in an application. You need to call Tk() exactly once. If you need more windows, create instances of Toplevel.

Tkinter custom frame not working

I'm trying to create a custom frame in tkinter, Python v2.7. I have done this just fine once (a frame with a scrollbar), but my second attempt isn't working. I compare it to the Frame that does work, and I can't understand what I have done differently.
What I want is a frame that has a little separator line underneath it, so I'm creating a "normal" frame, a thin frame to use as a separator under it, and a bigFrame to hold it.
Everything I create in the class works, except the frame itself. Hopefully my comments explain what is and isn't showing.
from Tkinter import *
class FunFrame(Frame):
def __init__(self, master, lbl, **kwargs):
self.bigFrame = Frame(master)
Frame.__init__(self, self.bigFrame, width=280, height=200, bg="red", **kwargs)
self.grid(row=0, column=0, pady=3) #this is in bigFrame, and doesn't display
#however the padding is still respected
self.separator = Frame(self.bigFrame, height=2, bd=1, width=280, relief = SUNKEN)
self.separator.grid(row=1, column=0) #this is in bigFrame, and displays
self.l = Label(self, text=lbl) #this is in self and doesn't display
self.l.grid(row=0, column=0)
def grid(self, **kwargs):
self.bigFrame.grid(**kwargs)
if __name__ == "__main__":
root=Tk()
Frame1=FunFrame(root, "hello")
Frame2=FunFrame(root, "world")
Frame1.grid(row=0, column=0)
Frame2.grid(row=1, column=0)
root.mainloop()
If you call self.grid in __init__, it calls your own grid, not Tkinter's version.
Try following (renamed grid to grid_):
from Tkinter import *
class FunFrame(Frame):
def __init__(self, master, lbl, **kwargs):
self.bigFrame = Frame(master)
Frame.__init__(self, self.bigFrame, width=280, height=200, bg="red", **kwargs)
self.grid(row=0, column=0, pady=3)
self.separator = Frame(self.bigFrame, height=2, bd=1, width=280, relief=SUNKEN)
self.separator.grid(row=1, column=0)
self.l = Label(self, text=lbl)
self.l.grid(row=0, column=0)
def grid_(self, **kwargs): ######## grid -> grid_
self.bigFrame.grid(**kwargs)
if __name__ == "__main__":
root=Tk()
Frame1 = FunFrame(root, "hello")
Frame2 = FunFrame(root, "world")
Frame1.grid_(row=0, column=0) ######## grid -> grid_
Frame2.grid_(row=1, column=0) ######## grid -> grid_
root.mainloop()
I'd rather code as follow (if '....' was used to represent hierarchy visually):
from Tkinter import *
class FunFrame(Frame):
def __init__(self, master, lbl, **kwargs):
Frame.__init__(self, master)
if 'inside outer frame (self)':
innerFrame = Frame(self, width=280, height=200, bg="red", **kwargs)
innerFrame.grid(row=0, column=0, pady=3)
if 'inside inner frame':
self.l = Label(innerFrame, text=lbl)
self.l.grid(row=0, column=0)
separator = Frame(self, height=2, bd=1, width=280, relief=SUNKEN)
separator.grid(row=1, column=0)
if __name__ == "__main__":
root = Tk()
Frame1 = FunFrame(root, "hello")
Frame2 = FunFrame(root, "world")
Frame1.grid(row=0, column=0)
Frame2.grid(row=1, column=0)
root.mainloop()

Categories