Failed to access the variable from another class in Tkinter - python

I am trying to develop a Tkinter GUI which contains two pages, the first one to input stock name (counter_selection) and the second one to plot stock price data. However, when I tried to use the data input from the first class using controller.get_page function it does not return the value input by Entry. The code is as below:
class Application(Tk.Tk):
'''A GUI Application for FCN Demo'''
def __init__(self):
'''Initialization of frame'''
Tk.Tk.__init__(self)
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, counter_selection, plot_counter):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row= 0, column = 0, sticky = "nsew")
self.show_frame(StartPage)
def get_page(self,classname):
'''Returns an instace of 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
def show_frame(self,cont):
frame = self.frames[cont]
frame.tkraise()
class counter_selection(Tk.Frame):
def __init__(self, parent, controller):
'''This is to create the widgets to input stock'''
Tk.Frame.__init__(self,parent)
self.controller = controller
label1 = Tk.Label(self, text = "Please Enter 3 counter names")
label1.pack(padx = 10, pady = 10)
self.entry1 = Tk.Entry(self)
self.entry1.pack()
self.entry2 = Tk.Entry(self)
self.entry2.pack()
self.entry3 = Tk.Entry(self)
self.entry3.pack()
button1 = Tk.Button (self, text = 'Confirm', command = lambda: controller.show_frame(plot_counter))
button1.pack()
class plot_counter(Tk.Frame):
'''This is to plot the graph of three selected counters'''
def __init__(self,parent,controller):
Tk.Frame.__init__(self,parent)
self.controller = controller
counterPage = self.controller.get_page('counter_selection')
self.counter1 = counterPage.entry1.get()
self.counter2 = counterPage.entry2.get()
self.counter3 = counterPage.entry3.get()
label1 = Tk.Label(self, text = self.counter11)
label1.pack()
The label1 does not show anything on the Frame, suggesting that it seems failed to get the value from the class. What's my mistake?
(PS: I didn't put the StartPage in since it is irrelevant for the question)

Why is Tkinter Entry's get function returning nothing?
TL;DR: your call to entry1.get() is done only once while instancing the plot_counter object, and never called again. Try putting it in a function to be called when displaying the Frame.

Related

How to execute after() method in the second page when it actually gets open using frame in tkinter?

Following is the code that by default shows the StartPage (first Frame) when the app runs, as soon as the user clicks on the button, it redirects to another page i.e., Page1 (second Frame). I want to execute self.after(3000, lambda: (canvas.create_image(50,50,image=self.character1, anchor=NW))) after 3 seconds when the user clicks on the button when the second page (frame) opens. But what I'm getting currently is the after() method executes after 3 seconds but on the initiation of the app. i.e., when the StartPage appears. Please help me to achieve this in my code. I'm new in tkinter! Thanks.
from tkinter import *
from PIL import Image, ImageTk
class tkinterApp(Tk):
# __init__ function for class tkinterApp
def __init__(self, *args, **kwargs):
# __init__ function for class Tk
Tk.__init__(self, *args, **kwargs)
# creating a container
container = Frame(self)
container.pack(side = "top", fill = "both", expand = True)
# initializing frames to an empty array
self.frames = {}
# iterating through a tuple consisting
# of the different page layouts
for F in (StartPage, Page1):
frame = F(container, self)
# initializing frame of that object from
# startpage, page1, page2 respectively with
# for loop
self.frames[F] = frame
frame.grid(row = 0, column = 0, sticky ="nsew")
self.update() #edited this
self.show_frame(StartPage)
# to display the current frame passed as
# parameter
def show_frame(self, cont): # cont = page_name
if cont not in self.frames:
self.frames[cont] = cont(container, self)
frame = self.frames[cont]
frame.tkraise()
frame.event_generate("<<ShowFrame>>")
# first window frame startpage
class StartPage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.bind("<<ShowFrame>>", self.myStartPage)
def myStartPage(self,controller):
super(StartPage).__init__()
print(controller)
canvas = Canvas(self,width=300, height=300, bd=0, highlightthickness=0, relief='ridge')
canvas.pack()
self.background = PhotoImage(file="background.png")
canvas.create_image(300,300,image=self.background, tags="B")
self.character1 = PhotoImage(file="bg-e-butd.png")
obj1 = canvas.create_image(50,50,image=self.character1, anchor=NW)
canvas.tag_bind(obj1, '<1>', lambda event=None : controller.show_frame(Page1))
# second window frame page1
class Page1(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
canvas = Canvas(self,width=300, height=300, bd=0, highlightthickness=0, relief='ridge')
canvas.pack()
self.background = PhotoImage(file="background.png")
canvas.create_image(300,300,image=self.background, tags="B")
self.character1 = PhotoImage(file="bg-e-butd.png")
self.after(1000, lambda: (canvas.create_image(50,50,image=self.character1, anchor=NW)))
# Driver Code
app = tkinterApp()
app.title("Title")
app.mainloop()

Menu item in Tkinter is not working properly (in MacOS)

I am making a tkinter app with multiple pages. The simple version of the codes I used is shown below. I added the menu to the root of the app and gave one option of File with sub-option of "Reload Defaults" and "Exit".
When I run this code then I do get the menu but not on the GUI but the main menu bar of Mac. And the menu is unresponsive. I am not sure what I am doing wrong!
import tkinter as tk
from tkinter import ttk
from tkinter import RAISED
LARGEFONT =("Verdana", 35)
class tkinterApp(tk.Tk):
# __init__ function for class tkinterApp
def __init__(self, *args, **kwargs):
# __init__ function for class Tk
tk.Tk.__init__(self, *args, **kwargs)
# creating a container
container = tk.Frame(self)
container.pack(side = "top", fill = "both", expand = True)
container.grid_rowconfigure(0, weight = 1)
container.grid_columnconfigure(0, weight = 1)
container2 = tk.Frame(self, relief=RAISED, borderwidth=2)
container2.pack(side="bottom",fill="both", expand=True)
container2.grid_rowconfigure(0, weight = 1)
container2.grid_columnconfigure(0, weight = 1)
def runProg():
print("response from runProg")
#get all entry value from page 2
runButton = ttk.Button(container2, text="Run", command=runProg)
runButton.pack(side="left", padx=5, pady=5)
######################################################
## Menu items
progMenu = tk.Menu(self,tearoff=0)
self.config(menu=progMenu)
#create File menu
def reload_command():
pass
fileMenu = tk.Menu(progMenu)
progMenu.add_cascade(label="File", menu=fileMenu)
fileMenu.add_command(label="Reload Defaults", command=reload_command)
fileMenu.add_command(label="Exit", command=reload_command)
######################################################
# initializing frames to an empty array
self.frames = {}
frame = StartPage(container, self)
self.frames[StartPage] = frame
frame.grid(row = 0, column = 0, sticky ="nsew")
self.show_frame(StartPage)
# to display the current frame passed as
# parameter
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
# first window frame startpage
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
# label of frame Layout 2
label = ttk.Label(self, text ="Startpage", font = LARGEFONT)
# putting the grid in its place by using
# grid
label.grid(row = 0, column = 2, padx = 10, pady = 10)
app = tkinterApp()
app.mainloop()
Any help would be greatly appreciated!
You might want to change the menu to be buttons at the top of the grid as on a Mac you will get this bug, but on windows it works perfectly fine.

How to create and display multiple widgets using a loop (Tkinter)

I am creating a program with two frames in one window. The first has input fields, the second will create a graph.
I found a way to create input fields dynamically from a list and get their values accordingly, but I can't get them to show on the window. When I run the program it shows an empty window.
What should I do to get the label and input widgets to show on the first frame (InputPage)? I tried changing 'parent' to 'self' but it made no difference. I don't really understand the structure of widgets in multiple frame applications.
Here is my code:
from tkinter import *
namesInput = ["first", "second", "third", "fourth", "fifth"]
entryInput = {}
labelInput = {}
root = Tk()
class ZorgplanGrafiek(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
container = 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 (InputPage, GraphPage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(InputPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class InputPage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self,parent)
label = Label(self, text="Zorgplan input")
label.pack(pady=10,padx=10)
i = 0
for name in namesInput:
e = Entry(self)
entryInput[name] = e
lb = Label(self, text=name)
labelInput[name] = lb
i += 1
#def print_all_entries():
# for name in namesInput:
# print( entryInput[name].get())
class GraphPage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
label = Label(self, text="The graph will show here")
label.pack(pady=10,padx=10)
button = Button(self, text="Back to Home",
command=lambda: controller.show_frame(InputPage))
button.pack()
app = ZorgplanGrafiek()
app.mainloop()
Firstly delete root = Tk() at the top of your code you are creating 2 windows. Secondly, your loop to create the entry and label widgets is not correct therefore they are not displayed on the frame, so that is your answer for why they wont show.
Try this:
class InputPage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self,parent)
label = Label(self, text="Zorgplan input")
label.grid(row=0, column=0, sticky ='n', columnspan =2)
# i brought your variable in the class for example sake
namesInput = ["First:", "second:", "Third:", "Fourth:", "Fifth:"]
self.entryWidgets = [] # we want to call this in another function so we assign it as self.variableName
labelWidgets = []
#LOOP TO CREATE WIDGETS
for i in range(0, len(namesInput)):
labelWidgets.append(Label(self, text = namesInput[i]))
self.entryWidgets.append(Entry(self))
labelWidgets[-1].grid(row= i+1, column =0, sticky='e')
self.entryWidgets[-1].grid(row= i+1, column = 1, sticky='w')
submit = Button(self, text = "Submit", command = self.getEntries)
submit.grid(row = 6, column =0, columnspan =2)
def getEntries(self):
results = []
for x in self.entryWidgets: # i.e for each widget in entryWidget list
results.append(x.get())
print(results)
Code explanation:
We are iteratively creating widgets to the number of elements within namesInput list. Each time we create a widget we add it to their respective list. E.g for entry widgets we created a list called entryWidgets. We append them to a list so that we can reference them individually later on when we want to do something with them.
Furthermore, i changed pack() to grid(). The grid method is much cleaner and gives us more control over the layout of our window in my opinion.
Note - If you're struggling to understand how i 'grided' the widgets in the way i did, i just drew up a quick sketch of the widgets with co-ordinates representing their row and column and then from there its fairly easy to see how to manipulate the grid settings in the for loop.
Screenshot:

Double-lambda Python

Is it feasible to execute multiple statements in a lambda? The app_data dictionary in my controller has a property "listbox" that stores a selected listbox value. A button on SelectPage has a lambda command that changes frames to NextPage.
Can the frame switch and app_data set statements both occur in a double-lambda? If so, what might this look like? Or do these operations have to occur separately (i.e. in a button click event)?
Update #1:
Issue isolated to lambda expression since just putting a constant (i.e. 1) in like .set(1) does not save either.
Update #2:
Using a lambda with doStuff(controller) solves the problem somewhat, but the listbox reference throws an error:
value = self.listbox.get(self.listbox.curselection())
AttributeError: 'SelectPage' object has no attribute 'listbox'
button:
button1 = ttk.Button(self, text='Next Page',
command=lambda:self.doStuff(controller))
doStuff():
def doStuff(self,controller):
controller.show_frame(NextPage)
controller.app_data["listbox"].set(1)
value = self.listbox.get(self.listbox.curselection())
print(value)
print("do Stuff successful")
Full Code:
from tkinter import *
from tkinter import ttk
class MyApp(Tk):
# Controller class
def __init__(self):
Tk.__init__(self)
# App data in controller
self.app_data = {"listbox": StringVar(),
"entry": StringVar(),
}
container = ttk.Frame(self)
container.pack(side="top", fill="both", expand = True)
self.frames = {}
for F in (SelectPage, NextPage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky = NSEW)
self.show_frame(SelectPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
def get_page(self,classname):
for page in self.frames.values():
if str(page.__class__.__name__) == classname:
return page
return None
class SelectPage(ttk.Frame):
def __init__(self, parent, controller):
self.controller = controller
ttk.Frame.__init__(self, parent)
ttk.Label(self, text='Select Page').grid(padx=(20,20), pady=(20,20))
listbox = Listbox(self,exportselection=0)
listbox.grid()
for item in [0,1,2,3,4,5]:
listbox.insert(END, item)
print (item)
entry1 = ttk.Entry(self, textvariable=self.controller.app_data["entry"], width=8)
entry1.grid()
button1 = ttk.Button(self, text='Next Page',
command=lambda:self.doStuff(controller)) # something like this lambda concept
button1.grid()
def doStuff(self,controller):
controller.show_frame(NextPage)
controller.app_data["listbox"].set(1)
value = self.listbox.get(self.listbox.curselection())
print(value)
print("do Stuff successful")
class NextPage(ttk.Frame):
def __init__(self, parent, controller):
self.controller = controller
ttk.Frame.__init__(self, parent)
ttk.Label(self, text='Next Page').grid(padx=(20,20), pady=(20,20))
button1 = ttk.Button(self, text='Select Page',
command=lambda: controller.show_frame(SelectPage))
button1.grid()
button2 = ttk.Button(self, text='press to print', command=self.print_it)
button2.grid()
def print_it(self):
value = self.controller.app_data["listbox"].get()
print ('The value stored in StartPage some_entry = ' + str(value))
value = self.controller.app_data["entry"].get()
print ('The value stored in StartPage some_entry = ' + str(value))
app = MyApp()
app.title('Multi-Page Test App')
app.mainloop()
Your original lambda didn't work because show_frame(...) returns None, which short-circuits the and so the remaining expression doesn't execute. Just change it to an or. Since the event caller doesn't care what you return, you could also create a two item tuple for the two subexpressions. There are other problems, such as self.listbox doesn't exist, which I fixed, but others remain.
As a pedantic aside, lambda only accepts an expression, which is a subset of a statement.
self.listbox = listbox
button1 = ttk.Button(self, text='Next Page',
command=lambda: controller.show_frame(NextPage) or self.controller
.app_data["listbox"]
.set(self.listbox.get(self.listbox.curselection()))) # something like this lam
Is it feasible to execute multiple statements in a lambda?
No. In general, you should avoid using lambda at all, and when you must use it you need to keep it as brief as possible. If you need to call more than a single function, create a new function that calls the other functions.
Also, if you're passing in an argument that is just an attribute of the object, you don't need lambda at all. I think the following code is much easier to read, much easier to understand, much easier to write, and thus much easier to maintain:
button1 = ttk.Button(self, text='Next Page',command=self.doStuff)
...
def doStuff(self):
self.controller.show_frame(NextPage)
self.controller.app_data["listbox"].set(1)

How to start the execution of a function (and/or) class only when a button is clicked in Tkinter

I am new to Python and am writing a code to automate certain measurement equipment. I am going to mention only a small part of my code to keep it concise.
The first class VSWR is used to select different frames. Since I need to change frames and go back and forth in them, I made a class VSWR. This class calls Start Page , which has a button "Path Loss". After clicking this button, the user needs to enter certain parameters, and in this case "Start and Stop Frequencies". Clicking the OK button will show what the user has entered and then asks the user to confirm it. After confirming a text window opens in a new frame(which is the RunModuleTests class). I will write all my remaining automation code in this class. But for now I want the power supply to turn on(for now I am using insert command message to show that my power supply is turned on) after 4 secs after I hit the confirm button. But what is happening is the RunModuleTests class executes as soon as I run the whole code and by the time I reach to my text window after entering the parameters, power supply will already be turned on.
What I think is happening is that as soon as I hit run on my whole code, the mainloop starts the execution of all the frames. (Please correct me if I am wrong here), whereas I want my frames(or classes and their functions) to execute only when those classes are called by clicking the button and not when I hit Run for the whole code. Is there any work around to this ??
Please let me know if someone needs me to elaborate my question or need more details of the issue I am facing here.
Thanks
import Tkinter as tk
from Tkinter import DoubleVar, IntVar, StringVar
import ttk
from numpy import arange
LARGE_FONT= ("Verdana", 12)
class VSWR(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self, "VSWR")
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, PathLoss, RunModuleTests):
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="This is the start page", font=LARGE_FONT)
label.pack(pady=10,padx=10)
button3 = ttk.Button(self, text="Path Loss",
command=lambda: controller.show_frame(PathLoss))
button3.pack()
class PathLoss(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
labelPathLoss = ttk.Label(self, text = 'Path Loss Measurement:', font=LARGE_FONT)
labelPathLoss.pack(pady=10,padx=10)
global X, Y
X = 20
Y = 100
#Define Variables with Type
self.startFreq = DoubleVar()
self.stopFreq = DoubleVar()
self.freqInc = IntVar()
labelenterStartFreq = ttk.Label(self, text = 'Enter the Start Frequency (in MHz):')
labelenterStartFreq.place(x = X, y = Y+20)
labelenterStopFreq = ttk.Label(self, text = 'Enter the Stop Frequency (in MHz):')
labelenterStopFreq.place(x = X, y = Y+40)
entryStartFreq = ttk.Entry(self, textvariable = self.startFreq)
entryStartFreq.place(x = X+240, y = Y+20)
entryStopFreq = ttk.Entry(self, textvariable = self.stopFreq)
entryStopFreq.place(x = X+240, y = Y+40)
buttonOK = ttk.Button(self, text = 'OK', command = lambda: self.getValues(X,Y, controller))
buttonOK.place(x = X+240, y = Y+270)
def getValues(self,X,Y, controller):
getStartFreq = self.startFreq.get()
getStopFreq = self.stopFreq.get()
ttk.Label(self, text = 'You entered the following values:').place(x = X+580, y = Y)
ttk.Label(self, text = 'Start Frequency : %5.2f' %getStartFreq).place(x = X+580, y = Y+20)
ttk.Label(self, text = 'Stop Frequency : %5.2f' %getStopFreq).place(x = X+580, y = Y+40)
buttonConfirmPL = ttk.Button(self, text = 'Confirm', command = lambda: controller.show_frame(RunModuleTests))
buttonConfirmPL.place(x = X+580, y = Y+300)
class RunModuleTests(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.rootText = tk.Text(self)
self.rootText.place(x = 200, y = 100)
self.rootText.tkraise()
opening = '\nProceeding to measure TRX path loss for FWD/REV....\n'
self.rootText.insert("insert", opening )
self.rootText.after(4000, self.temp)
def temp(self):
self.rootText.insert("insert", '\nTurning Power supply ON...\n')
app = VSWR()
app.geometry('1000x600+150+100')
app.mainloop()
The reason the power supply turn-on message appears immediately is this: At the very start of your program you create an instance of StartPage, PathLoss and RunModuleTests. In the __init__ method of RunModuleTests you're calling self.rootText.after(4000, self.temp), and self.temp writes that message. Thus, four seconds after your program starts, that message appears.
There are many solutions, but to do it properly involves a lot of rewriting. The structure of your code makes a simple solution difficult. The most common things you can try is to either a) not create an instance of RunModuleTests until you actually need it, or b) move the call to after into some other function, and then call that function only after you make that frame visible.
Personally I would do the latter -- you need to decouple the creation of the frame from the functions that can be done when the frame is visible. That means you'll need to add some extra logic to show_frame, or replace the call to show_frame with something else.

Categories