Why is my program crashing when using tkinter's after function? - python

import socket
import time
import tkinter as tk
from tkinter import StringVar
from functools import partial
import Client
import json
LARGE_FONT= ("Verdana", 8)
class MainGUI(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 (ConnectionPage, SystemPage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(ConnectionPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
def show_error(self, cont, error):
frame = self.frames[cont]
ErrorLabel = tk.Label(frame,text=error,font=LARGE_FONT)
ErrorLabel.pack()
ErrorLabel.after(2500,lambda :ErrorLabel.destroy())
def show_json_info(self,cont,sock):
frame = self.frames[cont]
packet = sock.recv(2000).decode()
JsonDict = json.loads(packet)
SysInfoLabel = tk.Label(frame, text="System Info", font=LARGE_FONT)
SysInfoLabel.pack(pady=10, padx=10)
SysInfoText = tk.Text(frame)
SysInfoText.pack()
SysInfoText.config(font=LARGE_FONT)
SysInfoText.insert("end","System: "+JsonDict["System"]+'\n')
SysInfoText.insert("end","NodeName: " + JsonDict["NodeName"]+'\n')
SysInfoText.insert("end","Release: "+JsonDict["Release"]+'\n')
SysInfoText.insert("end","Version: "+JsonDict["Version"]+'\n')
SysInfoText.insert("end","Machine: "+JsonDict["Machine"]+'\n')
SysInfoText.insert("end","Processor: "+JsonDict["Processor"]+'\n')
frame.after(2500,self.show_json_info,SystemPage,sock)
class ConnectionPage(tk.Frame):
def InitiateConnection(self,controller, Ip,Port):
sock = Client.CheckAddress(Ip,Port)
if sock:
controller.show_frame(SystemPage)
controller.show_json_info(SystemPage, sock)
else:
controller.show_error(ConnectionPage, "Wrong Ip or port")
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Connection Page", font=LARGE_FONT)
label.pack(pady=10,padx=10)
IpLabel = tk.Label(self, text="Ip address")
IpLabel.pack()
Ip = StringVar()
IpEntry = tk.Entry(self, textvariable=Ip)
IpEntry.pack()
PortLabel = tk.Label(self, text="Port")
PortLabel.pack()
Port = StringVar()
PortEntry = tk.Entry(self, textvariable=Port)
PortEntry.pack()
cmd = partial(self.InitiateConnection,controller,Ip,Port)
ConnectButton = tk.Button(self, text="Connect", command=cmd)
ConnectButton.pack()
class SystemPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
BackToConnectionButton = tk.Button(self, text="Back to Connection Page",command=lambda: controller.show_frame(ConnectionPage))
BackToConnectionButton.pack()
if __name__ == "__main__":
app = MainGUI()
app.mainloop()
The function show_json_info() is supposed to update the current window every time it runs, when trying to use a regular loop the program crashed, so ive used tkinter's after function. but the same thing happen and the program crashes.
any idea why is this happening? any alternatives I can use to fix the problem?
p.s the function Client.CheckAddress() returns a socket which I receive input from.

Related

Accessing variables outside class scope Python for tkinter app

I'm making an application using tkinter, and I want to access global variable that I assign a value to from a tkinter Entry by a user. I'm following an MVC pattern for the creation of the application, and every window is in it's own class and I'm struggling basically with using data from one class to the next. In this context, I want to print the username entered by the user into the label in the class PageThree I've made a minimum reproduceable version of the code here:
import tkinter as tk
from tkinter import font as tkfont
from tkinter import messagebox
stored_username = "Tessa"
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")
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 (PageTwo, PageThree):
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("PageTwo")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.label_username_signup = tk.Label(self, text="Username")
self.label_password_signup = tk.Label(self, text="Password")
self.entry_username_signup = tk.Entry(self)
self.entry_password_signup = tk.Entry(self, show="*")
self.label_username_signup.grid(row=0, sticky=tk.E)
self.label_password_signup.grid(row=1, sticky=tk.E)
self.entry_username_signup.grid(row=0, column=1)
self.entry_password_signup.grid(row=1, column=1)
self.rgbtn = tk.Button(self, text="Register", command=self.log_details)
self.rgbtn.grid(columnspan=3)
button = tk.Button(self, text="Back",
command=lambda: controller.show_frame("StartPage"))
button.grid()
def log_details(self):
username = self.entry_username_signup.get()
global stored_username
stored_username = username
if username:
self.controller.show_frame("PageThree")
else:
tk.messagebox.showerror("Login Failure", "Incorrect username or password, please try again")
class PageThree(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
self.controller = controller
global stored_username
lbl = tk.Label(self,text=name_entry)
lbl.grid(row=0,column=0)
def name_entry():
global stored_username
string = stored_username.get()
return string
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
I think the closest thing to a related question I can find is: Taking input from the user in Tkinter
However they are not dealing with modular classes.

Automatically updating a tkinter label that isn't directly accessible

I want to create a simple GUI for a constant data that comes through Serial. I decided to use tkinter. The value reading is updated and should be shown in a label. I created separate classes for the container and the other pages. I defined the container as such:
class Gui(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)
self.frames={}
for F in (StartPage, PageOne):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row = 0, column = 0, sticky = "nsew")
frame.UpdateMe()
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
And the page showing the label:
class PageOne(Frame):
def __init__(self, parent, controller):
Frame.__init__(self,parent)
global reading
self.label1text = StringVar()
self.label1 = Label(self, textvariable = label1text)
self.label1.pack()
button1 = Button (self, text = "Show Start Page", command = lambda: controller.show_frame(StartPage))
button1.pack()
self.label1text.set(reading)
def UpdateMe(self):
global reading
self.lable1text.set(reading)
Now, to initialize the GUI:
root = Gui()
root.mainloop()
However, since mainloop() is blocking, any argument coming after that wouldn't be executed; I could get around that with update and update_idletasks. However, I still don't know how I could call the function UpdateMe() inside PageOne()when I only created an instantiation of the Gui(). Is there a way for me to solve this or remediate my understanding of classes and object programming?
Since you cannot create StringVar without initializing Tk() (for your case, it is Gui()), so you need to create the reading variable inside Gui() and PageOne.label1 uses it as its textvariable. Below is an example based on your code:
from tkinter import *
from random import randint
class Gui(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.reading = StringVar() # create the StringVar for PageOne
self.frames = {}
for F in (StartPage, PageOne):
frame = F(container, self)
frame.grid(row=0, column=0, sticky="nsew")
self.frames[F] = frame
self.show_frame(StartPage)
def show_frame(self, cont):
self.frames[cont].tkraise()
class StartPage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
button1 = Button (self, text="Show Page 1", command=lambda: controller.show_frame(PageOne))
button1.pack(fill="both", expand=True)
class PageOne(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.label1 = Label(self, textvariable=controller.reading) # refer to Gui.reading StringVar
self.label1.pack(fill='x')
button1 = Button (self, text="Show Start Page", command=lambda: controller.show_frame(StartPage))
button1.pack(fill='x')
# use .after() to simulate the update of reading variable periodically
def update_reading():
app.reading.set(randint(0, 10000))
print('reading:', app.reading.get())
app.after(1000, update_reading)
app = Gui()
update_reading() # start the simulation task of updating reading variable
app.mainloop()
Note that I have created a function update_reading() to simulate the update of reading variable periodically using after() function.

Python call multiple commands in a funktion by pressing a button

I have a problem with my code:
I want that the OK-button (tkinter) in a popup-window (exp_Name) makes three things when it gets pressed:
First it should store a string from the Entry (in the popup-window) in a global variable
exp_name = entry.get()
Second it should open another window
app.show_frame(PageOne)
Third it should close the popup-window
popup.destroy
I tried to call a function, but I have no idea how I should close the popup window in a separate function. And also how I should store the Entry in a global variable in this separate function.
Thanks a lot!!
Here the whole code:
import tkinter as tk
from tkinter import ttk
global exp_name
exp_name = "Hello"
def exp_Name():
popup = tk.Tk()
popup.geometry("800x400")
label=ttk.Label(popup, text="Enter Something:")
label.pack(side="top", fill="x", pady=10)
entry = ttk.Entry(popup)
entry.pack()
##Here is the problem...
OK_btn=ttk.Button(popup, text="OK", command= lambda: [app.show_frame(PageOne),exp_name = entry.get(), popup.destroy])
OK_btn.pack()
Close_btn=ttk.Button(popup, text="Close",command = popup.destroy)
Close_btn.pack()
popup.mainloop()
class Steppermovementsapp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.iconbitmap(self, default="images/Icon_fly.ico")
tk.Tk.wm_title(self, "Drosophila ADF apparatus")
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)
exp_btn = ttk.Button(self, text="Start experiment",command=exp_Name)
exp_btn.pack()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label =ttk.Label(self, text=exp_name)
label.pack()
app = Steppermovementsapp()
app.geometry("{0}x{1}".format(app.winfo_screenwidth(),app.winfo_screenheight()))
app.mainloop()
This is how I tried it with a function:
import tkinter as tk
from tkinter import ttk
global exp_name
exp_name = "Hello"
class sidefunctions(tk.Tk):
def func_three(self):
app.show_frame(PageOne)
exp_name = self.entry.get()
popup.destroy
def exp_Name(self):
self.popup = tk.Tk()
self.popup.geometry("800x400")
self.label=ttk.Label(self,text="Enter Something:")
self.label.pack(side="top", fill="x", pady=10)
self.entry = ttk.Entry(self)
self.entry.pack()
self.OK_btn=ttk.Button(self,text="OK", command= self.func_three)
self.OK_btn.pack()
self.Close_btn=ttk.Button(self,text="Close",command =
self.popup.destroy)
self.Close_btn.pack()
self.popup.mainloop()
class Steppermovementsapp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.iconbitmap(self, default="images/Icon_fly.ico")
tk.Tk.wm_title(self, "Drosophila ADF apparatus")
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)
exp_btn = ttk.Button(self, text="Start experiment",command=sidefunctions().exp_Name)
exp_btn.pack()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label =ttk.Label(self, text=exp_name)
label.pack()
app = Steppermovementsapp()
app.geometry("{0}x{1}".format(app.winfo_screenwidth(),app.winfo_screenheight()))
app.mainloop()

tkinter: function not called when button pressed

This is a minimal example I made to show the problem, and it is extracted from a large project so please forgive the naming. So basically I have an GUI that looks like this:
the connect button and BE\RP... buttons belongs to a frame (control_container), which is like a navigator or tab selector that should always show up, and the info button belongs to another frame (container), which, when you click on BE\RP... buttons, should change to those corresponding frame class, and it does. what confused me is that when clicking the connect button, it should call function connect and do a print. However, it doesn't work: when you click on it, simply nothing happened. But I do know that the program recognize the connect function since it would complain if you delete the function. For contrast, if you click on info on StartPage, it works just fine and print. This is really strange to me.
import tkinter as tk
from tkinter import ttk
from tkinter import *
class emcAutoApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.control_container = tk.Frame(self)
self.control_container.pack(side="top", fill="both", expand = True)
container = tk.Frame(self, width=768, height=576, bg="")
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, BE, RP, PreScan, RSE):
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()
def get_page(self, page_class):
return self.frames[page_class]
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
self.frame_controller = controller
#control frame starts here
control_frame = ttk.Frame(self.frame_controller.control_container)
control_frame.pack(side='top')
chamber_frame = Frame(control_frame,
borderwidth=5,
relief=RIDGE,
width=200
)
chamber_frame.pack(side=TOP, expand=YES, fill=X)
chamber_frame_1 = Frame(chamber_frame,
borderwidth=1,
relief=RIDGE,
width=100
)
chamber_frame_1.pack(side=LEFT, expand=YES, fill=X)
chamber_frame_2 = Frame(chamber_frame,
borderwidth=1,
relief=RIDGE,
width=100
)
chamber_frame_2.pack(side=LEFT, expand=YES, fill=X)
connect_button = ttk.Button(chamber_frame_2, text="connect", command=lambda: self.connect)
connect_button.pack()
tab_frame = Frame(control_frame,
borderwidth=5,
relief=RIDGE,
width=500
)
tab_frame.pack(side=TOP, expand=YES, fill=X)
tab_frame_1 = Frame(tab_frame,
borderwidth=1,
relief=RIDGE,
width=100
)
tab_frame_1.pack(side=LEFT, expand=YES, fill=X)
tab_frame_2 = Frame(tab_frame,
borderwidth=1,
relief=RIDGE,
width=100
)
tab_frame_2.pack(side=LEFT, expand=YES, fill=X)
tab_frame_3 = Frame(tab_frame,
borderwidth=1,
relief=RIDGE,
width=100
)
tab_frame_3.pack(side=LEFT, expand=YES, fill=X)
tab_frame_4 = Frame(tab_frame,
borderwidth=1,
relief=RIDGE,
width=100
)
tab_frame_4.pack(side=LEFT, expand=YES, fill=X)
BE_button = ttk.Button(tab_frame_1, text="BE",
command=lambda: self.frame_controller.show_frame(BE))
BE_button.pack()
RP_button = ttk.Button(tab_frame_2, text="RP",
command=lambda: self.frame_controller.show_frame(RP))
RP_button.pack()
PreScan_button = ttk.Button(tab_frame_3, text="PreScan",
command=lambda: self.frame_controller.show_frame(PreScan))
PreScan_button.pack()
RSE_button = ttk.Button(tab_frame_4, text="RSE",
command=lambda: self.frame_controller.show_frame(RSE))
RSE_button.pack()
infobutton = ttk.Button(self, text = "info", command = self.info)
infobutton.pack()
def info(self):
print("info")
def connect(self):
print("connected")
class BE(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
self.frame_controller = controller
class RP(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
self.frame_controller = controller
class PreScan(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
self.frame_controller = controller
class RSE(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
self.frame_controller = controller
if __name__ == "__main__":
#=== GUI ===#
LARGE_FONT = ("Verdana", 12)
NORM_FRONT = ("Verdana", 10)
app = emcAutoApp()
app.mainloop()
lambda: self.connect doesn't call connect. In this case there is no need for lambda, just directly reference the function. As a general rule of thumb, buttons should always be tied directly to functions rather than using lambda
connect_button = ttk.Button(..., command=self.connect)

Updating frames on Python's Tkinter

I need some help updating frames. I did some research (update() and update_idletasks()) but so far nothing has worked. I could be implementing these methods incorrectly. This is what i have so far...
import tkinter as tk
LARGE_FONT = ("Verdana", 12)
class controller(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
self.minsize(width=300, height=300)
container.grid()
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {} # Store all the frames
for F in (StartPage, CustomerLocation, CustomerPickDate):
frame = F(container, self) # F is the classes
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage) # Make Start Page on the top
def show_frame(self, cont): # Used to bring the given frame to the top/ show frame
frame = self.frames[cont] # Access the dic of all the frames
frame.tkraise()
def get_page(self, page_class): # Get access to the page and its attributes
return self.frames[page_class]
def combine_funcs(self, *funcs): # Run multi funcs at one time (attach to a button!)
def combined_func(*args, **kwargs):
for f in funcs:
f(*args, **kwargs)
return combined_func
def updateFrame(self,frame):
selectedFrame = self.frames[frame]
selectedFrame.update_idletasks()
#frame.update_idletasks(self)
print("hit")
class CustomerLocation(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.myparent = parent
self.controller = controller
self.configure(background='#ED7D3B')
# ________________________________________________________________
# self.variableLocation is what I want in the next frame
# ________________________________________________________________
self.variableLocation = tk.StringVar(self)
self.variableLocation.set("Select")
alist = ["Blackwood", "Camden", "Philadelphia"]
locationOptionMenu = tk.OptionMenu(self, self.variableLocation, *alist)
locationOptionMenu.pack()
#print(self.variableLocation.get())
nextButton = tk.Button(self, text="Next",
command=lambda: controller.combine_funcs(controller.updateFrame(CustomerPickDate),
controller.show_frame(CustomerPickDate)
))
nextButton.configure(highlightbackground='#ED7D3B')
nextButton.pack()
backButton = tk.Button(self, text="Back",
command=lambda: controller.show_frame(StartPage))
backButton.configure(highlightbackground='#ED7D3B')
backButton.pack()
At this point self.variableLocation should be one of the alist variables. I used the controller.get_page(FRAME) to grab that value. I want to use this value as a label on the next frame.
class CustomerPickDate(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller # Help Access the controller + its methods
self.CustomerLocation = self.controller.get_page(CustomerLocation) # Access to Frame CustomerLocation
self.variableLocation = self.CustomerLocation.variableLocation
print(self.variableLocation.get())
label = tk.Label(self, text="Select Date", font=LARGE_FONT)
label.pack(pady=10, padx=10)
# _______________________________________________________
# I want the self.variableLocation from the pervious frame
# to be here!___________________________________________
self.label2 = tk.Label(self, text="TEST %s" % self.variableLocation.get()) # NEED FIXING/ ABLE TO UPDATE
self.label2.pack()
self.label2.update()
NextButton = tk.Button(self, text="Next")
NextButton.pack()
BackButton = tk.Button(self, text="Back",
command=lambda: controller.show_frame(CustomerLocation))
BackButton.pack()
I am very new to Tkinter, so any feed back would be great!

Categories