Accessing variables from different frames tkinter using 'shared_data' method - python

So this is a continuation of the question that I asked here: How to access user selected file across frames, tkinter
Where I was directed towards the this solution: How to access variables from different classes in tkinter python 3
This is how my code looks currently, there are two functions on page two in my attempts to get the chosen file onto the second page.
import pandas as pd
import tkinter as tk
from tkinter import filedialog
class GUI(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self, "GUI")
self.shared_data = {
"filename": tk.StringVar(),
}
container = tk.Frame(self)
container.pack(side="top", fill="both", expand = True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (Page1, Page2):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(Page1)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class Page1(tk.Frame):
def __init__(self, parent, controller):
self.controller = controller
tk.Frame.__init__(self,parent)
ftypes = [
('CSV files','*.csv')
]
def browsefunc2():
filename = tk.filedialog.askopenfilename(filetypes=ftypes)
pathlabel.config(text=filename)
print(filename)
browsebutton = tk.Button(self, text="Browse", command=browsefunc2, height=1, width=10)
browsebutton.pack()
pathlabel = tk.Label(self)
pathlabel.pack()
button = tk.Button(self, text="Page 2",
command=lambda: controller.show_frame(Page2))
button.pack()
class Page2(tk.Frame):
def __init__(self, parent, controller):
self.controller = controller
tk.Frame.__init__(self,parent)
#method one
def print_df():
#print(self.controller.shared_data["filename"].get())
df = pd.read_csv(self.controller.shared_data["filename"].get())
print(df)
#method two
filename__ = tk.Entry(self, textvariable=self.controller.shared_data["filename"])
def print_df2():
#print(filename__)
df = pd.read_csv(filename__)
print(df)
button1 = tk.Button(self, text="Print DF 1", command=print_df)
button1.pack()
button2 = tk.Button(self, text="Print DF 2", command=print_df2)
button2.pack()
button3 = tk.Button(self, text="Page 1",
command=lambda: controller.show_frame(Page1))
button3.pack()
app = GUI()
app.mainloop()
However when trying to use the 'tk.Entry' method to get the variable "filename" in Page2, I am faced with the error ".!frame.!page2.!entry" for 'print' and "ValueError: Invalid file path or buffer object type: " for 'read_csv()'.
Alternatively, when trying to use the '.get()' method, I am faced with the error "FileNotFoundError: File b'' does not exist" for both instances
I appreciate that these are several errors for a single question but all I need is one method to work. I have no idea why the solution mentioned above hasn't worked with my code considering it's an almost identical problem.
Any tips would be useful.

Related

tkinter passing data among classes

I was reading this tutorial of StackOverflow on how to get variables among classes, but I'm not able to make it work. Basically what i want to do is to pass an entry value from StartPage to PageOne where i need it. I also tried "the wrong way" using global variables, but I need an int and I can't covert the string entry to an int. Down below there's my code, can you help me please?
import tkinter as tk # python 3
from tkinter import font as tkfont
from typing_extensions import IntVar # python 3
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title_font = tkfont.Font(family='Helvetica', size=18, weight="bold", slant="italic")
# the container is where we'll stack a bunch of frames
# on top of each other, then the one we want visible
# will be raised above the others
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, PageOne):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
# put all of the pages in the same location;
# the one on the top of the stacking order
# will be the one that is visible.
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
def get_page(self, classname):
'''Returns an instance of a page given it's class name as a string'''
for page in self.frames.values():
if str(page.__class__.__name__) == classname:
return page
return None
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="Start Page: insert value", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
self.valore = tk.IntVar()
entry = tk.Entry(self, textvariable=self.valore).pack()
button1 = tk.Button(self, text="Go to Page One",
command=lambda: controller.show_frame("PageOne"))
button2 = tk.Button(self, text="Go to Page Two",
command=lambda: controller.show_frame("PageTwo"))
button1.pack()
button2.pack()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
start_page = self.controller.get_page("StartPage")
value = start_page.entry.get()
print ('The value stored in StartPage entry = %s' % value)
label = tk.Label(self, text="This is page 1", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()

Tkinter unable to update label text

I am trying to use GPS sensor with RPI and make its GUI using Tkinter. I want to update my GPS value and shows in the GUI. But in GUI it shows only initial value. In backend GPS value is updating but unable to show in GUI.
import tkinter as tk
import serial
import time
import webbrowser as wb
ser = serial.Serial('/dev/ttyUSB0', 9600)
class IotCar(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
width=tk.Tk.winfo_screenwidth(self)
height=tk.Tk.winfo_screenheight(self)
tk.Tk.geometry(self, '{}x{}'.format(width, height))
container=tk.Frame(self)
container.pack(side='top', fill='both', expand='True')
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for each_frame in (StartPage, HomePage):
frame = each_frame(container, self)
self.frames[each_frame]=frame
frame.grid(row=0, column=0, sticky='nsew')
self.show_page(StartPage)
def show_page(self, cont):
frame=self.frames[cont]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
next_button = tk.Button(self, text='Next', command=lambda:
controller.show_page(HomePage)).pack(side=tk.TOP,padx=5, pady=5)
class HomePage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.gps_pos()
self.lat1 = tk.StringVar()
self.lon1=tk.StringVar(self)
self.label_lat = tk.Label(self.HomePage, text='Latitude', justify=tk.LEFT, padx=10).pack()
self.label_lat1 = tk.Label(self.HomePage, textvariable=self.lat1, justify=tk.LEFT,
borderwidth=2, relief='ridge').pack()
self.label_lon = tk.Label(self, text='Longitude', justify=tk.LEFT, padx=10).pack()
self.label_lon1 = tk.Label(self, text=self.lon1, justify=tk.LEFT,
borderwidth=2, relief='ridge').pack()
def gps_pos(self):
print('Entered into gps')
ser.flushInput()
ser.flushOutput()
ser.write(b'AT+CGPSPWR=1\r')
ser.write(b'AT+CGPSOUT=32\r')
while True:
gps=str(ser.readline())
gps=gps[2:].split(',')
if gps[0]=='$GPRMC' and gps[3]!='':
lat=gps[1]
lon=gps[1]
break;
self.after(3000,self.gps_pos)
print(lat, lon)
self.lat1.set(lat1)
app = IotCar()
app.mainloop()
Please help me to understand what is wrong in it. Thank you in advance.
I am unable to run this script as I don't have the serial module however I have modified a few things and this populates the label with text (comments in code). You need a function that will assign the "text" parameters in the labels with a value.
import tkinter as tk
import time
import webbrowser as wb
class IotCar(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
width=tk.Tk.winfo_screenwidth(self)
height=tk.Tk.winfo_screenheight(self)
tk.Tk.geometry(self, '{}x{}'.format(width, height))
container=tk.Frame(self)
container.pack(side='top', fill='both', expand='True')
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for each_frame in (StartPage, HomePage):
frame = each_frame(container, self)
self.frames[each_frame]=frame
frame.grid(row=0, column=0, sticky='nsew')
self.show_page(StartPage)
def show_page(self, cont):
frame=self.frames[cont]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
next_button = tk.Button(self, text='Next', command=lambda:
controller.show_page(HomePage)).pack(side=tk.TOP,padx=5, pady=5)
class HomePage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.lat1 = tk.StringVar()
self.lon1 = tk.StringVar(self)
self.label_lat = tk.Label(self, text='Latitude', justify=tk.LEFT, padx=10)
self.label_lat.pack()
self.label_lat1 = tk.Label(self, justify=tk.LEFT,
borderwidth=2, relief='ridge') # I have removed the "text" parameter because my function will update it.
self.label_lat1.pack()
self.label_lon = tk.Label(self, text='Longitude', justify=tk.LEFT, padx=10)
self.label_lon.pack()
self.label_lon1 = tk.Label(self, justify=tk.LEFT,
borderwidth=2, relief='ridge') # I have removed the "text" parameter because my function will update it.
self.label_lon1.pack()
button = tk.Button(self, text="update label", bg= "#aaaaaa", fg ="#483F44",
font=("Arial", 10, "bold"), relief="groove", command = lambda: helloworld()) # This button will run the helloworld function
button.pack()
def helloworld():
self.label_lat1["text"] = "Hello" #This will update the text parameter in label_lat1 with "Hello"
self.label_lon1["text"] = "World" #This will update the text parameter in label_lon1 with "World"
app = IotCar()
app.mainloop()
A few things to note is to pack/place/grid your labels separately from when you define its parameters i believe that the way you have currently set up the widget is giving it a None type which is why you can't assign text to it:
#You write
self.label_lat1 = tk.Label(self.HomePage, textvariable=self.lat1, justify=tk.LEFT,
borderwidth=2, relief='ridge').pack()
#My example
self.label_lat1 = tk.Label(self, justify=tk.LEFT,
borderwidth=2, relief='ridge')
self.label_lat1.pack()
You then need a command that will run a function and in that function will have a variable which will assign a text value to the label (i.e. the helloworld function in my code).
Looking at your code i believe you will need to amend the way you are packing your widgets and then in your function do something like
def gps_pos(self):
print(lat, lon)
self.label_lat1["text"] = lat
self.label_lon1["text"] = lon
Below is a very useful answer on how to layout your tkinter gui and various other FAQ. I myself found it extremely useful
Switch between two frames in tkinter

(tkinter) Why aren't my variables being displayed using labels?

I'm trying to write a program which takes the users information, like their name and stuff, on one page, and then displays these entries on another. I'm using Tkinter and I can't get their entries to display on the other page. Here's the program:
import tkinter as tk
from tkinter import ttk
#PROFILE VARS
FirstName = ('none')
#INITIALIZING
class MegaQuiz(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self, "THE MEGA POP QUIZ")
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (ProfilePage, MainPage):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("ProfilePage")
def show_frame(self, page_name):
frame = self.frames[page_name]
frame.tkraise()
#PROFILE PAGE
class ProfilePage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
global FirstName
#Profile Title
profile_title = tk.Label(self, text="Create A Profile Yo")
profile_title.grid(column=0, row=2)
#FIRST NAME
Q1_title = tk.Label(self, text="First Name:")
Q1_title.grid(column=0, row=1)
FirstNameEntry = tk.Entry(self)
FirstNameEntry.grid(column=2, row=4)
FirstName = str(FirstNameEntry.get())
#NEXT BUTTON
Button1 = tk.Button(self, text="NEXT",
command = lambda: controller.show_frame("MainPage"))
Button1.grid(column=10, row=10)
#MAIN MENU PAGE
class MainPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
#Play BUTTON
PlayBTN = tk.Button(self, text="PLAY", width=40)
PlayBTN.grid(column=0, row=20, sticky="nesw")
#ProfileDisplay
FirstNameDis = tk.Label(self, text=('FirstName: ' + FirstName))
FirstNameDis.grid(column=0, row=0, sticky="w")
#RUNNING PROGRAM
app = MegaQuiz()
app.mainloop()
The problem is that it displays the "FirstName: ", but doesn't display the variables FirstName, just blankness, help.
The quickest way to fix this is to move your FirstName variable to the MegaQuiz as a class attribute and then use it from there. We can change your NEXT button command to call a class method and then from that method update the FirstName variable and then also set the FirstNameDis label text from that same method.
First move the FirstName variable into your MegaQuiz class by putting this line in the __init__ section.
self.FirstName = 'none'
Then change this:
#NEXT BUTTON
Button1 = tk.Button(self, text="NEXT", command = lambda: controller.show_frame("MainPage"))
To this:
#NEXT BUTTON
Button1 = tk.Button(self, text="NEXT", command=self.next_button)
Button1.grid(column=10, row=10)
def next_button(self):
self.controller.FirstName = self.FirstNameEntry.get()
self.controller.frames["MainPage"].FirstNameDis.config(text='FirstName: ' + self.controller.FirstName)
self.controller.show_frame("MainPage")
In your MainPage change this:
self.FirstNameDis = tk.Label(self, text='FirstName: ' + FirstName)
To this:
self.FirstNameDis = tk.Label(self, text='FirstName: ' + self.controller.FirstName)
That should be all you need to fix this.
I did notice a few PEP8 issues so here is your code rewritten to provide a fix to your problem and also to rewrite some things to better follow the PEP8 guidelines.
Not sure why you are adding () to some of your string variables but it is not needed.
variable and class attribute names should be all lower case with underscores to between words.
You do not need to provide a variable name for everything. If you have a button or a label you know you will not be updating later then you can write them without the variable names.
Code:
import tkinter as tk
class MegaQuiz(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self)
self.title("THE MEGA POP QUIZ")
self.first_name = 'none'
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (ProfilePage, MainPage):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("ProfilePage")
def show_frame(self, page_name):
frame = self.frames[page_name]
frame.tkraise()
class ProfilePage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
tk.Label(self, text="Create A Profile Yo").grid(column=0, row=2)
tk.Label(self, text="First Name: ").grid(column=0, row=1)
self.fn_entry = tk.Entry(self)
self.fn_entry.grid(column=2, row=4)
tk.Button(self, text="NEXT", command=self.next_button).grid(column=10, row=10)
def next_button(self):
self.controller.first_name = self.fn_entry.get()
self.controller.frames["MainPage"].fn_dis.config(text='FirstName: {}'.format(self.controller.first_name))
self.controller.show_frame("MainPage")
class MainPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
tk.Button(self, text="PLAY", width=40).grid(column=0, row=20, sticky="nesw")
self.fn_dis = tk.Label(self)
self.fn_dis.grid(column=0, row=0, sticky="w")
app = MegaQuiz()
app.mainloop()

Notebook with Multiple Frames

I am trying to make a tkinter widow with multiple frames, but also the functions of notebook, like multiple widows. The problem is I am kind of unfamiliar with tkinter and am not sure how to do that. This is my current code, and it doesn't work, and would love to know what I should do to make it work. Again, the dream end result would be that I would have a first widow, which says the test text, and then the 2nd window which has multiple tabs.
from tkinter import ttk
import tkinter as tk
Font= ("Verdana", 8)
LargeFont = ("Verdana", 12)
class App(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side="top", fill="both", expand = True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, PageOne):
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 = tk.Label(self, text="Info", font=LargeFont)
label.pack(pady=2,padx=10)
text = tk.Label(self, text="testtestestetetqwegfegeg\ntestwegwegwegweg", font=Font)
text.pack(pady=2,padx=2)
button = tk.Button(self, text="Go to the Card",
command=lambda: controller.show_frame(PageOne))
button.pack(fill="x")
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
nb = ttk.Notebook(ttk.Frame())
nb.grid(row=1, column=0, columnspan = 50, rowspan=49, sticky='nesw')
p1 = (nb)
nb.add(p1, text='test')
label = tk.Label(self, text="", font=LargeFont)
label.pack(pady=10,padx=10)
button1 = tk.Button(self, text="Back to Home",
command=lambda: controller.show_frame(StartPage))
button1.pack()
app = App()
app.mainloop()
The error that I eventually get is that it creates a third frame that is displayed with the test tab. Everything else works.
Thanks for your help
I know exactly what you mean because I'm trying the same. To me
nb = ttk.Notebook(self)
worked.
best
Pkanda
Taubate Brazil

Assigning a function to button does not work

So I have this code, running multiple GUI windows in tkinter, I also have the second code that contains a certain function assigned to a button, that seems not to work. Im extremely tired and can't find the solution, I'm sure it's some basic thing. The function I mean is the toggle_text1 command in the PageOne class. I'll appreciate every help, thanks!
import Tkinter as tk
TITLE_FONT = ("Helvetica", 18, "bold")
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# the container is where we'll stack a bunch of frames
# on top of each other, then the one we want visible
# will be raised above the others
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=False)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, PageOne, PageTwo, Page3, Page4):
frame = F(container, self)
self.frames[F] = frame
# put all of the pages in the same location;
# the one on the top of the stacking order
# will be the one that is visible.
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, c):
'''Show a frame for the given class'''
frame = self.frames[c]
frame.tkraise()
def toggle_text1():
if button1["text"] == "WL":
button1["text"] = "WYL"
label1["bg"] = "green"
#wiringpi.pinMode(91,0)
#wiringpi.digitalWrite(91,0)
else:
button1["text"] = "WL"
label1["bg"] = "red"
#wiringpi.pinMode(91,1)
#wiringpi.digitalWrite(91,0)
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="This is the start page", font=TITLE_FONT)
label.pack(side="left", fill="x", pady=10)
button1 = tk.Button(self, text="Go to Page One",
command=lambda: controller.show_frame(PageOne))
button2 = tk.Button(self, text="Go to Page Two",
command=lambda: controller.show_frame(PageTwo))
button3 = tk.Button(self, text="Go to Page 3",
command=lambda: controller.show_frame(Page3))
button4 = tk.Button(self, text="Go to Page 4",
command=lambda: controller.show_frame(Page4))
button1.pack(pady=10)
button2.pack(pady=10)
button3.pack(pady=10)
button4.pack(pady=10)
Oh crap, I've deleted the widget earlier and forgot to paste it again, my bad. How about now?
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="This is page 1", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame(StartPage))
button1 = tk.Button(self, text='WL', command=toggle_text1)
button.pack()
button1.pack()
label1.pack()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
Your function and button are in different classes if you want to use a function from one class in another, then you need to pass it into your constructor. Which is what you have done.
for F in (StartPage, PageOne, PageTwo, Page3, Page4):
frame = F(container, self)
You have passed it in as controller
class PageOne(tk.Frame):
def __init__(self, parent, controller):
Therefore when assigning the function you need to prefix it with controller
button1 = tk.Button(self, text='WL', command=controller.toggle_text1)
Which you have done for all your lambda functions. Also you need to add self as an argument to your function

Categories

Resources