Tkinter bind inconsistency - python-3.x

This problem appears to be hard to duplicate -- as I am able to correctly do it in a shorter program. What I'm hoping for is maybe some guidance on what could be going wrong. I have posted the version where it works correctly:
import tkinter as tk
from tkinter import *
from tkinter import ttk
class DIS(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self, "program")
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.usernameVar = StringVar()
self.frames = {}
for F in (StartPage, contactQues, nowTry, next):
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)
button2 = ttk.Button(self, text = "Here's a Button", command= lambda: controller.show_frame(nowTry))
button2.pack()
class nowTry(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.entry1 = Entry(self)
self.entry1.pack()
self.button1 = ttk.Button(self, text = "Yes", command = self.go)
self.button1.pack()
self.entry1.bind("<Return>", self.go)
def go(self, event=None):
print (self.entry1.get())
self.controller.show_frame(contactQues)
class contactQues(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.entry1 = Entry(self)
self.entry1.pack(pady=10, padx=10)
self.button1 = ttk.Button(self, text = "Submit", command= self.UsernameSubmit)
self.button1.pack()
self.entry1.bind("<Return>", self.UsernameSubmit)
def UsernameSubmit(self, event=None):
UsernameEntryGet = self.entry1.get()
self.controller.usernameVar.set(UsernameEntryGet)
self.controller.show_frame(next)
class next(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, textvariable = self.controller.usernameVar)
label.pack() ###Label is posted with input correctly
The issue I'm having with my main program is that the self.controller.usernameVar label does not post like it does in this example (nothing shows up at all) when the Return key is pressed to submit the input. However, when the submit button is clicked with the mouse, the label appears properly.
So, given this information, I feel as if my bind("<Enter>"... command is being managed wrong. I've tried self.bind..., self.controller.bind..., self.entryX.bind... without success.
Any ideas with this framework what could be wrong?

I believe I figured it out. The issue was that in my full program, I had multiple bind commands. While I was trying to solve this issue, I had some entry prompts bound to the controller and others to the entry itself (e.g,. self.controller.bind in a few classes and self.entry.bind in others).
I changed them all to self.entry.bind and it apparently fixed it -- which is why this code snippet worked as expected.

Related

How to pass variables from one class to another after a class has been instantiated?

I have created a tkinter application which uses a framework very similar to the one shown in this video https://www.youtube.com/watch?v=jBUpjijYtCk
Essentially there is a main class which uses other classes below it as pages. These pages are created as soon as the program is ran.
My problem is that I need data from one of these pages to be sent to another page so that it can display the appropriate data depending on the data that is sent from the previous page.
I have simplified my program down to showcase the issue
from tkinter import *
class TkinterApp(Tk):
def __init__(self):
Tk.__init__(self)
container = Frame(self)
container.grid(row=1, column=0, sticky="nesw")
self.frames = {}
for p in (ClassA, ClassB):
frame = p(parent = container, controller = self)
self.frames[p] = frame
frame.grid(row=0, column=0, sticky="nesw")
self.ShowFrame(ClassA)
def ShowFrame(self, page_name):
page = self.frames[page_name]
page.tkraise()
def PassText(self, text):
self.frames[ClassB].GetData(text)
class ClassA(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.controller = controller
button1 = Button(self, text="button1", command=lambda: self.SubmitInfo("button1"))
button1.grid(row=0, column=0)
def SubmitInfo(self, data):
self.controller.ShowFrame(ClassB)
self.controller.PassText(data)
class ClassB(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.controller = controller
self.label = Label(self, text=self.data)
self.label.grid(column=0, row=0)
def GetData(self, data):
self.data = data[0]
app = TkinterApp()
app.mainloop()
Desired outcome:
the text shown on the ClassB page being "button1"
Current outcome:
AttributeError: 'ClassB' object has no attribute 'data'
to my knowledge, this is happening as the class TkinterApp is causing ClassB to be ran before the value of data exists.
Any help would be greatly appreciated
In line 44, remove Label widget parameter text=self.data
Add .configure in line 49, self.label.configure(text=self.data)
snippet:
class ClassB(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.controller = controller
self.label = Label(self) #<-- line 44
self.label.grid(column=0, row=0)
def GetData(self, data):
self.data = data[0]
self.label.configure(text=self.data) #Add in <---line 49

Unable to Display a Variable on a Label

I am using the popular tkinter approach where the original author tried an "pseudo MVC" pattern. Here is the code:
class Main(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 (PageOne, PageTwo):
frame = f(container, self)
self.frames[f] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(PageOne)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
def get_page(self, page_class):
return self.frames[page_class]
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
def inputs():
r=rq.get('https://swapi.co/api/people/1')
page2 = self.controller.get_page(PageTwo)
page2.resp.set(r.json()['name'])
self.controller.show_frame(PageTwo)
b = tk.Button(station_frame, text="Submit",command=inputs)
b.grid(row=2, column=2, pady=5, padx=5)
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.resp = tk.StringVar()
lbl = tk.Label(self, text=self.resp.get())
lbl.pack(pady=10, padx=10)
while doing this my lbl comes empty and when i set text=self.resp it print PY_VAR0.
Now Here is the part which confuses me. when I change my lbl (Label) with a Button and calls a functions which just prints self.resp.get() it prints the value. Here is the code:
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.resp = tk.StringVar()
def show():
print(self.resp.get())
btm = tk.Button(self, text='call', command=show)
btm.pack(pady=10, padx=10)
I want to show the resp value on a Label on my PageTwo.

Passing entry box from classA to a label in class B tkinter Python

Hello everyone im new to tkinter and ive tried all of the examples for passing value from one class to another class in tkinter but doesnt seem to work. I want the entry box in my login page to display as a label in my home page. Would really appreaciate any help received!!
class LoginPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
MyFrontend.configure(self, background="lightblue")
logo = tk.PhotoImage(file="logo.png")
tk.Label(self, image=logo).pack()
labelNric = tk.Label(self, text="NRIC:", font=LARGE_FONT_NOBOLD, background="lightblue", foreground="darkslategray")
labelNric.place(x=80,y=220)
# entry box for user input
entryNric = tk.Entry(self, highlightbackground="lightblue")
# entryNric.bind("<Return>", getNric)
entryNric.place(x=280,y=223)
labelEgnric = tk.Label(self, text="E.g. SXXXX123A", font=SMALL_FONT, background="lightblue", foreground="darkslategray")
labelEgnric.place(x=285,y=253)
def error_popupmsg(msg):
popup = tk.Tk()
popup.wm_title("Error")
popup.geometry("400x200")
popup.configure(background="darkred")
labelError = tk.Label(popup, text=msg, font=MED_FONT, background="darkred", foreground="white")
labelError.place(x=140, y=60)
buttonOk = tk.Button(popup, text="Got it!", highlightbackground ="#8B0000", command=popup.destroy)
buttonOk.place(x=170, y=100)
popup.mainloop()
def checkNric(arg=None):
nric_check = entryNric.get()
if nric_check == 'S1234567B' or nric_check == 'S1234567C':
controller.show_frame(HomePage)
else:
error_popupmsg("Invalid NRIC!")
buttonSubmit = ttk.Button(self, text="Submit", command=lambda: checkNric())
buttonSubmit.place(x=380,y=290)
class HomePage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
MyFrontend.configure(self, background="lightblue")
labelNricInfo = tk.Label(self, text="Patient's NRIC/FIN:", font=SMALL_FONT, background="lightblue", foreground="darkslategray")
labelNricInfo.place(x=50,y=40)
# to display NRIC from entry box in login page here
labelNricDisplay = tk.Label(self, text="", font=SMALL_FONT, foreground="darkslategray")
labelNricDisplay.place(x=170,y=40)
buttonBack = ttk.Button(self, text="Back", command=lambda: controller.show_frame(LoginPage))
buttonBack.place(x=370,y=420)
buttonSubmit = ttk.Button(self, text="Submit")
buttonSubmit.place(x=460,y=420)
Define a variable for your class LoginPage, and make your entry as an attribute of the class.
import tkinter as tk
root = tk.Tk()
class LoginPage(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.entryNric = tk.Entry(self, highlightbackground="lightblue")
self.entryNric.pack()
self.entryNric.insert(0,"Hi i am an entry")
self.pack()
login = LoginPage(root)
print (login.entryNric.get()) #access the class attribute
root.mainloop()

Tkinter window does not update correctly when running

I'm trying to make a Tkinter window show updated data but it only pops up after 13 seconds with just the last value. I want it to pop up and change the values on screen. Mind you, the big goal of this code is to take data from a database (which updates every 3 seconds) and show the data onscreen, while running continuously, so if the answer could include some pointers on the "after" or "update" functions it would be greatly appreciated!
Here is what I have so far.
from tkinter import *
import time
class GUI(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
Tk.wm_title(self, "Main Window")
self.container = Frame(self)
self.container.pack(side=TOP, fill=BOTH, expand=TRUE)
self.container.grid_rowconfigure(0, weight=1)
self.container.grid_columnconfigure(0, weight=1)
self.frames = {}
self.frame = StartPage(self.container, self)
self.frames[StartPage] = self.frame
self.frame.grid(row=0, column=0, sticky=NSEW)
self.show_frame(StartPage)
def show_frame(self, controller):
frame = self.frames[controller]
frame.tkraise()
class StartPage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.label = Label(self, text="Current ID:\n")
self.label.pack(padx=10, pady=10)
self.data_label = Label(self)
self.data_label.pack()
self.update_data()
def update_data(self):
var1 = StringVar()
for i in range(10):
var1.set(str(i))
self.data_label.config(text=str(i))
time.sleep(1)
main = GUI()
main.mainloop()
I can give you a partial answer. The reason you don't see any updates is that time.sleep() suspends the process and does not allow for tkinter to repaint the window.
You don't use the label textvariable correctly. Specify it in the label and the label will change as you change the textvariable.
You use both pack and grid at the same time which may cause problems.
I have not used after() in a class before so I don't know how to work it, but this example should give you some pointers. I'm keeping console printouts in the code so I can see what it does.
from tkinter import *
import time
class GUI(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
Tk.wm_title(self, "Main Window")
self.container = Frame(self)
self.container.pack(side=TOP, fill=BOTH, expand=TRUE)
self.frames = {}
self.frame = StartPage(self.container, self)
self.frames[StartPage] = self.frame
self.frame.pack(side=TOP, fill=BOTH, expand=TRUE)
self.show_frame(StartPage)
def show_frame(self, controller):
frame = self.frames[controller]
frame.tkraise()
frame.update_data()
print('Done')
class StartPage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.parent = parent
self.label = Label(self, text="Current ID:\n")
self.label.pack(padx=10, pady=10)
self.var1 = StringVar()
self.data_label = Label(self, textvariable=self.var1)
self.data_label.pack()
self.update_idletasks() # Calls all pending idle tasks
def update_data(self):
if not self.var1.get(): self.var1.set('0')
iteration = int(self.var1.get())
print('iteration', iteration)
if iteration < 3:
iteration = iteration + 1
self.var1.set(str(iteration))
self.update_idletasks() # Calls all pending idle tasks
time.sleep(1)
self.update_data()
main = GUI()
main.mainloop()
You will have to research after() yourself as I can't help you there.

switching windows in tkinter with classes

in a [former question][1] received a perfect script from #acw1668 for creating popup-windows (see below).
How can this be rewritten in a form that the new windows are not popups but just a switch from one page to the next (the listboxes/candvas are not necessarily needed here)?
Edit: tried to amend the code according to #Bryan Oakley's suggestions.
My issue here: I do not manage to pass the list lst from the GUI class to the other page classes without an error message:
File "/.spyder-py3/temp.py", line 25, in __init__
frame = F(parent=container, controller=self)
TypeError: __init__() missing 1 required positional argument: 'lst'
What am I missing here?
And I do not understand what's happening here:
for F in (StartPage, PageOne, PageTwo):
page_name = F.__name__
frame = F(parent=container, controller=self,)
self.frames[page_name] = frame
If somebody could explain, please?
import tkinter as tk
from tkinter import ttk
class GUI(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.lst = ['a', 'b', 'c']
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, PageTwo):
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", self.lst)
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
def show_popup(self, page, lst):
win = page(self, lst)
win.grab_set() # make window modal
self.wait_window(win) # make window modal
class StartPage(tk.Frame):
def __init__(self, parent, controller, lst):
tk.Frame.__init__(self, parent)
self.controller = controller
self.lst = lst
# ------------------------------------------------------------------- #
label = tk.Label(self, text="Check this out")
label.pack(pady=10,padx=10)
# ------------------- create buttons ---------------------------------
button1 = ttk.Button(self, text="show all",
width = 25, command=lambda:
controller.show_popup(App, self.lst))
button1.pack(pady=10, padx=10)
button2 = ttk.Button(self, text="show page one",
width = 25, command=lambda:
controller.show_frame(PageOne))
button2.pack(pady=10, padx=10)
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 1")
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()
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 2")
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()
class App(tk.Toplevel):
def __init__(self, parent, lst):
tk.Toplevel.__init__(self, parent)
self.lst = lst
self.title('This is the pop up window')
self.geometry('400x200')
label = tk.Label(self, text=self.lst)
label.pack(side="top", fill="x", pady=10)
parent.grid_rowconfigure(0, weight = 1)
parent.grid_columnconfigure(0, weight = 1)
if __name__ == '__main__':
app = GUI()
app.mainloop()
[1]: http://stackoverflow.com/questions/41181809/how-to-open-and-close-another-window-with-scrollbar-in-tkinter-for-python-3-5/41182843?noredirect=1#comment69580999_41182843
Your class initializers are defined like this:
class StartPage(tk.Frame):
def __init__(self, parent, controller, lst):
In order to create an instance of this class it requires three arguments (plus self): parent, controller, and lst.
Now, let's look at how you're creating the instance:
frame = F(parent=container, controller=self,)
Notice how you have the parent and you have the controller, but you haven't passed in anything for lst. That is why the error states "missing 1 required positional argument: 'lst'" -- because you are literally missing one required argument named "lst".
To fix this problem, you simply need to provide this extra argument. For example:
frame = F(parent=container, controller=self, lst=self.lst)
HOWEVER, you probably shouldn't do that. The architecture of this little block of code you copied makes it possible to access values on the GUI class from any of the "page" classes without having to do any extra work.
Because this variable is an attribute of the GUI class, and you are passing a reference to the instance of the GUI class to each "page" (the controller attribute), you can access this data any time you want without having to pass it in at construction time. You can remove it from __init__ and from where you're creating the pages (ie: go back to the original code before your modifications), and then just use self.controller.lst whenever you need the value.
For example:
class SomePage(tk.Frame):
def __init__(self, parent, controller):
self.controller = controller
...
def some_function(self):
print("The value of 'lst' is:", self.controller.lst)

Resources