Python Object Oriented Tkinter - Calling Function Error - python

I have a login button on the Login frame, I want the login button to only take me to the AdminHome frame if "valid" is entered in the username entry. I can't get the show_frame() function to run in the validate() function (located in the Login Class).
I get the error:
NameError: global name 'show_frame' is not defined
How would I overcome this error and get it to run?
import tkinter as tk
from tkinter import ttk
class Program(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 (Login, AdminHome):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row = 0, column = 0, sticky = "nsew")
self.show_frame(Login)
def show_frame(self,cont):
frame = self.frames[cont]
frame.tkraise()
class Login(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
innerFrame = tk.Frame(self)
innerFrame.place(relx=.5, rely=.5, anchor="c")
pagename = tk.Label(innerFrame, text = "iDea Academy Progress Tracker Login")
pagename.grid(row=0, columnspan=5, sticky="W"+"E")
username = tk.Label(innerFrame, text="Username: ")
username.grid(row=1, columnspan=2, sticky="W")
self.user = ttk.Entry(innerFrame, text ="", width=45)
self.user.grid(row=1, column=2 ,columnspan=3, sticky="w")
password = tk.Label(innerFrame, text="Password: ")
password.grid(row=2, columnspan=2, sticky="W")
self.passentry = ttk.Entry(innerFrame, text ="", width=45, show="*")
self.passentry.grid(row=2, column=2 ,columnspan=3, sticky="W")
login = ttk.Button(innerFrame, text = "Login", command = self.validate)
login.grid(row=3, columnspan=5, sticky="W"+"E")
def validate(self):
if self.user.get()=="valid":
show_frame(AdminHome)
class AdminHome(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
fetch = ttk.Button(self, text = "Fetch Data", command = lambda: controller.show_frame(AdminHome))
fetch.grid(row=2, columnspan=6, sticky="W"+"E")
app = Program()
app.state('zoomed')
app.mainloop()

Here is the way I would do it: I would get rid of the validate method in your login frame, and seperately define a function for validation. This would look like this:
def validate(user):
global app
print(user)
if user=="valid":
app.show_frame(AdminHome)
Then, in your command for the login button, simply change it to be: lambda: validate(self.user.get()).

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.

Pass ssh_client object from Login class to MainWindow class

I am building a GUI that requires me to log on to a remote computer via ssh. I am using paramiko to do this.
What I want to achieve is that a log in window is showed when the application is launched. The user has to put in some credentials. If the login is successful, then display the main window of the application. If the login fails, then remain at the login window.
If login succeeds, I want the ssh_client object to be passed on to the MainWindow class, so that the established connection can be used to perform tasks on the remote computer. However, how can I pass the ssh_client object to MainWindow?
The following code runs, but makes no attempt to use the established ssh_client. What could I do to be able to use the ssh_client from Login in MainWindow?
Perhaps I should just reestablish the connection in MainWindow - bu then I need to pass the credentials to MainWindow, which seems like the same kind of problem I am having right now.
import Tkinter as tk
import paramiko
import time
class Application(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.grid(row=0, column=0, sticky="nsew")
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (Login, MainWindow):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(Login)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class Login(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.ssh_client = paramiko.SSHClient()
self.ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self.parent = parent
self.controller = controller
self.grid(row=0, column=0)
self.user = tk.StringVar()
self.user.set("my_username") # Default user
self.host_options = ["host1", "host2"]
self.host = tk.StringVar()
self.host.set(self.host_options[0]) # Default hostname
l_user = tk.Label(self, text="Username: ")
l_user.grid(row=0, column=0, sticky=tk.E)
self.entry_user = tk.Entry(self)
self.entry_user.grid(row=0, column=1, sticky=tk.W)
self.entry_user.insert(0, self.user.get())
l_pwd = tk.Label(self, text="Password: ")
l_pwd.grid(row=1, column=0, sticky=tk.E)
self.entry_pwd = tk.Entry(self, show="*")
self.entry_pwd.grid(row=1, column=1, sticky=tk.W)
l_host = tk.Label(self, text="Hostname: ")
l_host.grid(row=2, column=0, sticky=tk.E)
optionmenu_host = tk.OptionMenu(self, self.host, *self.host_options)
optionmenu_host.grid(row=2, column=1, sticky=tk.W)
b_login = tk.Button(self, text="Log in", command=self.authorize)
b_login.grid(row=3, column=0, sticky=tk.W)
b_quit = tk.Button(self, text="Quit", command=self.parent.destroy)
b_quit.grid(row=4, column=0, sticky=tk.W)
def authorize(self):
try:
self.ssh_client.connect(hostname=self.host.get(), username=self.entry_user.get(), password=self.entry_pwd.get())
self.controller.show_frame(MainWindow)
except paramiko.AuthenticationException:
l_error = tk.Label(self, text="Login failed...", fg="red")
l_error.grid(row=4, column=1, sticky=tk.W)
l_error.after(2000, l_error.destroy)
class MainWindow(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.grid(row=0, column=0)
l = tk.Label(self, text="Log in was successful!")
l.grid(row=0, column=0, sticky=tk.W)
###################################
# run application
if __name__ == "__main__":
app = Application()
app.mainloop()
###################################
Here's what I mean, plus a few other improvements:
tkinter never uses *args; you don't need that when you subclass.
tkinter uses **kwargs a lot; don't leave that off when you subclass!
there's no point to the container Frame ... it does nothing but add complexity
if you grid both frames than your window will always be the size of the biggest. To resize dynamically you need to RE-grid.
Using a frames dictionary is nice when you have a few dozen frames, but for only 2 I would just use a normal method.
Tkinter calls what you call "parent" the "master" and it sets self.master = master automatically, so you don't need that line.
I can't test this so let me know how it works.
import Tkinter as tk
import paramiko
import time
class Application(tk.Tk):
def __init__(self, **kwargs):
tk.Tk.__init__(self, **kwargs)
self.ssh_client = paramiko.SSHClient()
self.ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self.login_window = Login(self)
self.main_window = MainWindow(self)
self.show_login() # set starting point
def show_login(self):
self.main_window.pack_forget()
self.login_window.pack()
def show_main(self):
self.login_window.pack_forget()
self.main_window.pack()
class Login(tk.Frame):
def __init__(self, parent, **kwargs):
tk.Frame.__init__(self, parent, **kwargs)
self.user = tk.StringVar()
self.user.set("my_username") # Default user
self.host_options = ["host1", "host2"]
self.host = tk.StringVar()
self.host.set(self.host_options[0]) # Default hostname
l_user = tk.Label(self, text="Username: ")
l_user.grid(row=0, column=0, sticky=tk.E)
self.entry_user = tk.Entry(self)
self.entry_user.grid(row=0, column=1, sticky=tk.W)
self.entry_user.insert(0, self.user.get())
l_pwd = tk.Label(self, text="Password: ")
l_pwd.grid(row=1, column=0, sticky=tk.E)
self.entry_pwd = tk.Entry(self, show="*")
self.entry_pwd.grid(row=1, column=1, sticky=tk.W)
l_host = tk.Label(self, text="Hostname: ")
l_host.grid(row=2, column=0, sticky=tk.E)
optionmenu_host = tk.OptionMenu(self, self.host, *self.host_options)
optionmenu_host.grid(row=2, column=1, sticky=tk.W)
b_login = tk.Button(self, text="Log in", command=self.authorize)
b_login.grid(row=3, column=0, sticky=tk.W)
b_quit = tk.Button(self, text="Quit", command=self.quit)
b_quit.grid(row=4, column=0, sticky=tk.W)
def authorize(self):
try:
self.master.ssh_client.connect(hostname=self.host.get(), username=self.entry_user.get(), password=self.entry_pwd.get())
self.master.show_main()
except paramiko.AuthenticationException:
l_error = tk.Label(self, text="Login failed...", fg="red")
l_error.grid(row=4, column=1, sticky=tk.W)
l_error.after(2000, l_error.destroy)
class MainWindow(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
l = tk.Label(self, text="Log in was successful!")
l.grid(row=0, column=0, sticky=tk.W)
# you can use the ssh client like this:
print(self.master.ssh_client)
# or like this:
self.ssh_client = self.master.ssh_client
print(self.ssh_client)
###################################
# run application
if __name__ == "__main__":
app = Application()
app.mainloop()
###################################

form bug when switching frames

I'm designing a login screen for my chat application. As I don't have much information about switching frames, I'm looking at the code here and edit for my project accordingly. For now, I want the "Page 2" to be shown when the credentials are entered correctly. However, when I enter the nickname and password correctly, nickname form remains in the second screen and it looks messed up like in the screenshoot I uploaded. Screenshot
How can I solve this problem? The code is below:
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 (StartPage, PageOne, PageTwo):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
F.grid_propagate(self)
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name):
frame = self.frames[page_name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent, width=500, height=500)
self.controller = controller
db = MySQLdb.connect(host="localhost", user="root", passwd="", db="members")
cursor = db.cursor()
label = tk.Label(self, text="Nickname:")
label.place(x=140, y=150)
user_id = tk.Entry()
user_id.place(x=210, y=150)
label_2 = tk.Label(self, text="Password:")
label_2.place(x=140, y=200)
password = tk.Entry(self, show="*")
password.place(x=210, y=200)
def auth_login(event=None):
id = user_id.get()
pswd = password.get()
cursor.execute(
"SELECT nickname, password FROM chatmembers WHERE nickname='{}' AND password='{}' ".format(id, pswd))
if cursor.fetchone():
controller.show_frame("PageTwo")
else:
messagebox.showerror("Wrong credentials", "Either nickname or password is wrong. Please try again.")
login_button = tk.Button(self, text="Log In", command=auth_login, height=2, width=20)
login_button.place(x=195, y=250)
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent, width=500, height=500)
self.controller = controller
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.place(x=100, y=200)
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", 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: 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)

Python tkinter- getting user input and checking it

I'm fairly new to Python but I'm trying to learn as I go along. I'm using a raspberry Pi and I'm using Python version 3.2.3. My question is I have a text widget in my frame which lets users add text. I've got a button below called "run code" and when this is clicked it calls a function which should check the input from the text box. If the character is alphabetical it then prints to a label a welcome message and my score should increase by 5 points, if the input is not alphabetical it shows an error message. I've tried to use an if statement but only the else section is performing. Any suggestions? My code is below:
import tkinter as tk
from tkinter import ttk
score = 0
class Game(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self, "Game")
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)
filemenu = tk.Menu(menubar, tearoff=0)
filemenu.add_command(label="Exit", command=quit)
menubar.add_cascade(label="File", menu=filemenu)
tk.Tk.config(self, menu=menubar)
self.frames = {}
for F in (StartPage, Py2Page1):
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)
self.columnconfigure(0,weight=1)
self.columnconfigure(1,weight=1)
label = tk.Label(self, text="Title")
label.grid(row=0,column=0, columnspan=2)
label1 = tk.Label(self, text ="Add text here")
label1.grid(row=1,column=0, columnspan=2, pady=10)
button1 = ttk.Button(self, text="Start", command=lambda: controller.show_frame(Py2Page1))
button1.grid(row=2,column=0, columnspan=2,pady=10)
class Py2Page1(tk.Frame):
def __init__(self, parent,controller):
tk.Frame.__init__(self, parent)
result = tk.StringVar()
self.columnconfigure(0,weight=1,minsize=640)
self.columnconfigure(1,weight=1,minsize=640)
label = tk.Label(self, text="Title!")
label.grid(row=0,column=0,columnspan=2)
self.status = tk.Label(self)
self.status.grid(row=0,column=1,sticky="e",padx=30)
label2 = tk.Label(self, text ="""SubTitle""")
label2.grid(row=1,column=0)
label3 = tk.Label(self, text ="""
Add text here....
""", justify="left")
label3.grid(row=2,column=0,sticky="n")
self.user_input = tk.Text(self,wrap="word")
self.user_input.grid(column=1,row=2)
button4 = ttk.Button(self,text="Run Code", command = self.checking)
button4.grid(column=1,row=3)
button5 = ttk.Button(self,text="Clean", command = self.clear)
button5.grid(column=1,row=4)
self.output = tk.Label(self)
self.output.grid(column=1,row=5,sticky='w', pady=10,padx=10)
def checking(self):
answer = self.user_input.get('1.0',tk.END)
if answer.isalpha():
result = "Hello " + answer
scorechanged= score =+ 5
else:
result = "Please enter your name again"
self.output.config(text=result)
self.status.config(text=scorechanged)
def clear(self):
self.user_input.delete('1.0',tk.END)
app = Game()
app.geometry("1280x720")
app.mainloop()
Thanks
Addressing the initial question:
userInput = stringVar()
userEntry = Entry(master, variable=userInput) #creates an entry box
userInput.get() # returns what is in the entry box
I could imagine with this code you can accomplish what you intend to. You can use if statements to see what is in the entry box and determine weather it is a number.
try:
int(userInput.get()) # try and convert to integer
except ValueError: # if the conversion fails due to it containing letters
print('userInput is not a number')

Categories