I want my program to run in a way that once the user presses the Info button, a label called GameInfoLabel is displayed due to a command from the Info button. Within the same condition (if the Info button is pressed), I want to add a Back button that deletes/destroys the GameInfoLabel.
I have attempted to implement this in the code below, but I'm getting the message
NameError: name 'GameInfoLabel' is not defined.
from tkinter import *
root = Tk()
root.title("Game Menu")
root.geometry("1920x1080")
root.resizable(True, True)
def QuitGameInfo():
GameInfoLabel.destroy()
BackInfoButton['state'] = NORMAL
def GameInfo():
RulesNotepad = open("GameInfo.txt",'r')
Rules = RulesNotepad.read()
GameInfoLabel = Label(root, text = Rules, fg = "blue", bg = "red", height = "14", width = "140").pack()
BackInfoButton = Button(root, text = "Back", command = QuitGameInfo).pack()
RulesNotepad.close()
button3 = Button(root, text = "Info", command = GameInfo, width = "20", height = "3").pack()
root.mainloop()
The error is due to that GameInfoLabel is a local variable inside GameInfo() and it is not accessible inside QuitGameInfo().
You can fix this error by either declaring GameInfoLabel as global or pass it to QuitGameInfo() via argument. Same apply on BackInfoButton as well.
However you need to fix another issue: both GameInfoLabel and BackInfoButton are None because they are result of pack().
Below is the modified code using global solution:
from tkinter import *
root = Tk()
root.title("Game Menu")
root.geometry("1920x1080")
root.resizable(True, True)
def QuitGameInfo():
GameInfoLabel.destroy()
#BackInfoButton['state'] = NORMAL # why ??? Should it be destroyed as well?
BackInfoButton.destroy()
def GameInfo():
global GameInfoLabel, BackInfoButton
with open("GameInfo.txt",'r') as RulesNotepad:
Rules = RulesNotepad.read()
GameInfoLabel = Label(root, text = Rules, fg = "blue", bg = "red", height = "14", width = "140")
GameInfoLabel.pack()
BackInfoButton = Button(root, text = "Back", command = QuitGameInfo)
BackInfoButton.pack()
Button(root, text = "Info", command = GameInfo, width = "20", height = "3").pack()
root.mainloop()
However I would suggest to use a frame to hold the GameInfoLabel and BackInfoButton and the frame is hidden initially. When Info button is clicked, show the frame. When Back button is clicked, hide the frame.
from tkinter import *
root = Tk()
root.title("Game Menu")
root.geometry("1920x1080")
root.resizable(True, True)
def GameInfo():
with open("GameInfo.txt",'r') as RulesNotepad:
Rules = RulesNotepad.read()
GameInfoLabel.config(text=Rules)
info_frame.pack() # show the game info frame
Button(root, text="Info", command=GameInfo, width="20", height="3").pack()
# create the game info frame but don't show it initially
info_frame = Frame(root)
GameInfoLabel = Label(info_frame, fg="blue", bg="red", height="14", width="140")
GameInfoLabel.pack()
Button(info_frame, text="Back", command=info_frame.pack_forget).pack()
root.mainloop()
As mentioned in acw1668's comment GameInfoLabel is local to the GameInfo() method. This means once this method has finished running anything declared in it ceases to exist.
The usual solution to this is passing/returning variables to functions to get results for instance your game info could return the label, however since you want to call these functions automatically when an event occurs, e.g. your button is pressed, this is not so easy.
I believe the easiest solution to your problem would be to declare the GameInfoLabel variable globally (in the global scope), this is not always the best coding practice but I'm not certain of tkinter's ability to pass variables parameters to an event handler and this can be complicated.
Also as mentioned by acw1668 you call .pack() immediately on the new label which is returned from the initialization Label(...). Pack then does not return the label so we do that separately.
This should work, give it a careful read.
from tkinter import *
root = Tk()
root.title("Game Menu")
root.geometry("1920x1080")
root.resizable(True, True)
# Declare any global UI Components
GameInfoLabel = None # Dont set a Label yet
def QuitGameInfo():
GameInfoLabel.destroy()
BackInfoButton['state'] = NORMAL
def GameInfo():
RulesNotepad = open("GameInfo.txt",'r')
Rules = RulesNotepad.read()
GameInfoLabel = Label(root, text = Rules, fg = "blue", bg = "red", height = "14", width = "140")
GameInfoLabel.pack()
BackInfoButton = Button(root, text = "Back", command = QuitGameInfo).pack()
RulesNotepad.close()
button3 = Button(root, text = "Info", command = GameInfo, width = "20", height = "3")
button3.pack()
root.mainloop()
Related
I am trying to create a Tkinter window with a button which when clicked will provide with a new window. The new window has a checkbox and I want some actions to be done based on the checkbox value.
from tkinter import *
from tkinter import messagebox
def my_command():
def run():
pass
def cb_command():
f1 = fname1.get()
messagebox.showinfo("First Name", f1)
if cbVar.get() == 1:
messagebox.showinfo(cbVar.get())
my_button['state'] = 'active'
else:
messagebox.showinfo("Not found!")
my_button['state'] = 'disabled'
root = Tk()
root.geometry("200x200")
fname = Label(root, text="First Name")
fname.grid(row= 0, column = 0, sticky = "news", padx=5, pady=5)
fname1 = Entry(root, width = 10)
fname1.grid(row =0, column = 1, sticky = "news", padx=5, pady=5)
cbVar = IntVar()
cb1 = Checkbutton(root, text="Please check this", variable=cbVar, onvalue=1, offvalue=0, command=cb_command)
cb1.grid(row = 1, column = 0)
my_button = Button(root, text = "Run", bg = '#333333', fg='#ffffff', font = 'Helvetica', command = run, state='disable')
my_button.grid(row = 2, column = 0)
root.mainloop()
window = Tk()
window.geometry("200x200")
button1 = Button(window, text = "Run", command = my_command)
button1.pack()
window.mainloop()
I wrote this simple code which works fine with all other entry widgets. However, the checkbutton in the new window does not work. Can someone suggest any alternative?
Update:
Sorry, that I didn't clarify what actions to be done. I want the checkbox when clicked impact the state of the "Run" button in the toplevel window. The actual actions are based on the "Run" button.
Thank you Thingamabobs for suggesting a very simple solution. Just replaced one instance of Tk with Toplevel and it works.
from tkinter import *
def new_window():
second_window = Toplevel()
def checkbutton_checked():
# If you just want to take some action, once the checkbutton has been checked, you could do this here
# Alternatively you could also add a button to the toplevel and on click check the value of
# the checkbutton and perform actions based on that...
cb1.configure(text="Checkbutton checked")
cb1 = Checkbutton(second_window, text="Check here", command=checkbutton_checked)
cb1.pack()
window = Tk()
b1 = Button(window, text="Open new window", command=new_window)
b1.pack()
window.mainloop()
I hope this provides some help and you can solve your problem, if not let me know please.
Further details about the purpose of the checkbutton would also help me.
Okay so what I'm trying to figure out is how to control the location of output.
Currently when you run the code a window comes up and allows you to type into a label "manual". with the option to press enter or click the 'submit' button. The typed entry is then put in a new pop-up window every time I.e. Input1(test) and input2(testing) will show up in separate windows. How do I set it so it shows up in the same pop-up window preferably spaced on the y axis so it isn't cluttered. As this is a manual entry there is no telling how many entries will be submitted and it may be possible once so many entries have been entered it will need to move over on the X axis and start again at the top of the Y axis.
I'd also like to know how to pick where the pop-up ends up on the screen I tried using
win.place(x=200, y=50) but the .Toplevel() doesn't have an attribute of .place()
import tkinter as tk
#function that prints the entry
def entry_function(e=None):
name = entry.get()
win = tk.Toplevel()
tk.Label(win, text=name).pack()
win.geometry('100x100')
#Setup for the window
window = tk.Tk()
window.title("Title_Name")
window.geometry("500x500")
window.bind('<Return>', entry_function)
#widgets
label = tk.Label(window, text = "Manual:", bg = "dark grey", fg = "white")
entry = tk.Entry()
button = tk.Button(text = "submit",
command = entry_function)
#widget placement
label.place(x=0,y=20)
entry.place(x=52,y=21)
button.place(x=177, y=18)
window.mainloop()
For displaying the entry on same popup, you can use the below code for entry function
import tkinter as tk
flag = False
win=""
def setflag(event):
global flag
flag = False
#function that prints the entry
def entry_function(e=None):
global flag,win
name = entry.get()
if not flag:
win = tk.Toplevel()
win.geometry('100x100')
win.bind('<Destroy>', setflag)
tk.Label(win, text=name).pack()
flag = True
win.geometry("+%d+%d" % (x + 200, y + 50))
else:
tk.Label(win, text=name).pack()
#Setup for the window
window = tk.Tk()
window.title("Title_Name")
window.geometry("500x500")
window.bind('<Return>', entry_function)
x = window.winfo_x()
y = window.winfo_y()
sh = window.winfo_screenheight()
sw = window.winfo_screenwidth()
x_cord = int((sw/2)-550)
y_cord = int((sh/2)-300)
wh = 550
ww = 1100
window.geometry("{}x{}+{}+{}".format(ww, wh, x_cord, y_cord))
#widgets
label = tk.Label(window, text = "Manual:", bg = "dark grey", fg = "white")
entry = tk.Entry(window)
button = tk.Button(window,text = "submit",
command = entry_function)
#widget placement
label.place(x=0,y=20)
entry.place(x=52,y=21)
button.place(x=177, y=18)
window.mainloop()
You need to create the popup once and add label to it inside entry_function().
You can make the popup hidden initially and show it inside entry_function() and you can specify the position of the popup using geometry().
def entry_function(e=None):
MAX_ROWS = 20 # adjust this value to suit your case
name = entry.get().strip()
if name:
n = len(popup.winfo_children())
tk.Label(popup, text=name).grid(row=n%MAX_ROWS, column=n//MAX_ROWS, sticky='nsew', ipadx=2, ipady=2)
popup.deiconify() # show the popup
window.focus_force() # give focus back to main window
...
# create the popup
popup = tk.Toplevel()
popup.withdraw() # hidden initially
popup.geometry('+100+100') # place the popup to where ever you want
popup.title("Log Window")
popup.protocol('WM_DELETE_WINDOW', popup.withdraw) # hide popup instead of destroying it
...
I have two functions the top_signup_click and the top_login_click. The top_signup_click function is below the top_login_click function. The top_login_click function doesn't work because the_top_signup_click function is below it. To fix that issue I'll have to put the top_signup_click above the top_login_click function, which does solve the issue but it also creates another issue, now the top_signup_click function doesn't work. For both functions to work they both have to be below each other, but I don't think that's possible. If you know any other ways to do this please help. I tried using global functions to fix it but it didn't work.
import tkinter as tk
import tkinter.font as font
import tkinter.messagebox
signup_button = None
root = tk.Tk()
root.geometry('360x460')
#login stuff
def login_click():
readfile = open("emails.txt", "r")
lines = readfile.readlines()
isIN = False
for line in lines:
if f'{entry.get()}, {entry1.get()}' in line:
isIN = True
if not isIN:
tk.messagebox.showinfo(message ="You got your email/password wrong, are you sure you signed in?")
else:
tk.messagebox.showinfo(message ="You hacker, the email/password is correct")
def signup_click():
file = open("emails.txt","a")
file.write (entry.get())
file.write(f', {entry1.get()}\n')
def top_login_click():
global signup_button
signup_button.destroy()
login_button = tk.Button(root, text="Login", bg='royalblue', fg='white', command = login_click, width = 15, height = 2)
login_button['font'] = button_font
login_button.place(x=88,y=330)
#when the top sign up button is pressed
def top_signup_click():
global login_button
login_button.destroy()
signup_button = tk.Button(root, text="Sign-Up", bg='royalblue', fg='white', command = login_click, width = 15, height = 2)
signup_button['font'] = button_font
signup_button.place(x=88,y=330)
top_font = font.Font(family='Helvetica', size=14, weight='bold')
label_font = font.Font(family='Helvetica', size=20)
button_font = font.Font(family='Helvetica', size=14, weight='bold')
top_login_button = tk.Button(root,text = 'Login',width = 15, height = 3, command = top_login_click)
top_login_button.place(x=0,y=0)
top_login_button['font'] = top_font
top_signup_button = tk.Button(root,text = 'Sign-Up',width = 15, height = 3, command = top_signup_click)
top_signup_button.place(x=180,y=0)
top_signup_button['font'] = top_font
username_label = tk.Label(root, text = 'Username: ' )
username_label['font'] =label_font
username_label.place(x=120,y=120)
entry = tk.Entry(root, width = 40)
entry.place(x=65,y=170, height = 30)
password_label = tk.Label(root, text = 'Password: ' )
password_label['font'] =label_font
password_label.place(x=120,y=230)
entry1 = tk.Entry(root, width = 40)
entry1.place(x=65,y=280, height = 30)
login_button = tk.Button(root, text="Login", bg='royalblue', fg='white', command = login_click, width = 15, height = 2)
login_button['font'] = button_font
login_button.place(x=88,y=330)
root.mainloop()
The top_login_click function is on line 29-34 and the top_signup_click function is on line 37-42. Thanks in advance.
I wanted to comment this but seems like I don't have enough reputation but here is what I think.
You have set the variable signup_button = None right below the import statements so signup_button is not really a tk.Button object, instead it's a NoneType variable.
So just remove that statement and create
signup_button = tk.Button(<args here>)
and You should be good.
and try using place_forget() instead of destroy() IFF destroy doesn't work for you.
Edit:
What I also think in your case that you may not really need to destroy the signup_up button because you haven't created one yet. Think about it this way (based on the GUI that comes up on running your code) - on the landing screen of your app (the first screen that comes up), you have a login form with two entry boxes for email & pword and a login button, along with two more buttons called top_login_button and top_signup_button. So You DO NOT have a signup_button as of now (and according to your code signup_button = None it's not a button). So if a user now clicks on the top_login_button, the statement signup_button.destroy() will look for widget with the name signup_button (which is set to None by you) so it raises the error.
You code will run fine in a case where user clicks on top_signup_button before clicking on top_login_button because in that case the signup_button will be actually set to be a tk.Button object.
why does this code fail
im trying to summon a window when the settings button is pressed that allows users to change the forground and background colours
i get the radio buttons but recive an error every time i submit the colours even though their value is not 0
how do i fix this and why does it happen?
from tkinter import *
import os
from tkinter import messagebox as mgb
import tkinter.ttk as tk2
global colour_1, colour_2
colour_1 = "pink"
colour_2 = "purple" #background
def sayclick() :
btn.configure(text = "saying")
text = txt.get()
os.system("""PowerShell -Command "Add-Type –AssemblyName System.Speech; (New-Object System.Speech.Synthesis.SpeechSynthesizer).Speak('""" + text + """ ');" """)
btn.configure(text = "say")
def colourmenu():
window = Tk()
window.title("colour menu")
selected = IntVar()
selected2 = IntVar()
rad11 = tk2.Radiobutton(window,text='blue ', value=1, variable=selected)# set 1
rad21 = tk2.Radiobutton(window,text='green', value=2, variable=selected)#
rad31 = tk2.Radiobutton(window,text='red ', value=3, variable=selected)#
rad41 = tk2.Radiobutton(window,text='black', value=4, variable=selected)#
rad12 = tk2.Radiobutton(window,text='blue ', value=1, variable=selected2)
rad22 = tk2.Radiobutton(window,text='green', value=2, variable=selected2)
rad32 = tk2.Radiobutton(window,text='red ', value=3, variable=selected2)
rad42 = tk2.Radiobutton(window,text='black', value=4, variable=selected2)
def submitbut():
if selected.get() == 0 :
mgb.showinfo("error", "error: invalid colour choise")
elif selected2.get() == 0 :
mgb.showinfo("error", "error: invalid colour choise")
else :
for n in range(1,3):
if n == 1 :
val = selected.get()
rep = 1
else :
val = selected2.get()
rep = 2
if val == 1:
colour = "blue"
elif val == 2 :
colour = "green"
elif val == 3 :
colour = "red"
else :
colour = "black"
if rep == 1 :
bg = colour
else :
fg = colour
submit = Button(window, text="submit", command=submitbut)
txt1 = Label(window, text = "background")
txt1.grid(column = 0, row = 0)
txt2 = Label(window, text = "forground")
txt2.grid(column = 1, row = 0)
rad11.grid(column=0, row=1)#set 1
rad21.grid(column=0, row=2)#
rad31.grid(column=0, row=3)#
rad41.grid(column=0, row=4)#
rad12.grid(column=1, row=1)
rad22.grid(column=1, row=2)
rad32.grid(column=1, row=3)
rad42.grid(column=1, row=4)
submit.grid(column=0, row=5)
window.mainloop()
root = Tk()
root.geometry("170x120")
root.title("tts entry box")
root.configure(background = colour_2)
lab = Label(root, text = "enter text and than press\n\"say\" to say the text", font = ("Arial Bold", 10), fg = colour_2, bg = colour_1)
lab.grid(column = 0, row = 0)
btn = Button(root, text = " say. ", font = ("Arial", 8), bg = colour_1, fg = colour_2, command = sayclick)
btn.grid(column = 0, row = 3)
btn2 = Button(root, text = "settings", font = ("Arial", 8), bg = colour_1, fg = colour_2, command = colourmenu)
btn2.grid(column = 0, row = 4)
txt = Entry(root, width = 10, bg = colour_1, fg = colour_2)
txt.grid(column = 0, row = 2)
root.mainloop()
The problem is that you have two separate Tk objects in your program. This is almost always a bad idea, and the reason it's a bad idea is that it means that any code that relies on the "default Tk object" is likely to do the wrong thing.
In particular, when you do this:
selected = IntVar()
… you're creating an IntVar that's part of the first Tk that you created.1,2 But the Radiobutton widgets are attached to a different Tk. So, they can't read those variables (that's why you start off with all of the radio buttons in indeterminate state), and they can't write to them either.3
If you want to create a new top-level window, you don't need to create a whole new Tk environment, just use a Toplevel widget:
def colormenu():
window = Toplevel()
# the rest of your code can be the same
And then, the IntVars end up as part of the same Tk as the window.
While we're at it, do you really want to put a mainloop inside the other mainloop? This is legal, but I don't think it's what you want. It's almost certainly better to just return from colormenu after the submit.grid and let the main mainloop loop. (If you were trying to make colormenu a modal dialog, that blocks interaction with the main window, this isn't the way to do it.)
1. You can explicitly specify a parent when creating an IntVar, the same way you do for a widget. For example, selected = IntVar(window) would make this problem go away. But there are additional problems with having a separate Tk here, so it's better to solve them all at once by just not having one.
2. If you ever need to have multiple Tk instances, and need to know which one a variable, widget, etc. is attached to, you can look at its _root attribute. For example, selected._root is window._root will tell you whether they're part of the same Tk or not—in this case, it's False with your code as written, but True with the change in the next paragraph.
3. If you're curious why this isn't giving you a useful and easily-debuggable error, you have to understand how tkinter actually works. Tkinter is a wrapper around the Tk library, written in a completely separate scripting language called Tcl. Each Tk root instance has its own entirely independent Tcl interpreter, with its own independent global variables. So, your selected = IntVar() is creating a Tcl global variable named PY_VAR0 inside your first Tcl interpreter. Then, your variable=selected tells Tcl to store changes to the global variable PY_VAR0 in your second Tcl interpreter. So, whenever there's a change, Tcl writes to the global variable PY_VAR0 in the second interpreter, which is perfectly legal, but perfectly useless, because your selected looks up PY_VAR0 in the first interpreter.
from tkinter import *
import tkinter as tk
class dashboard(Frame):
def __init__(self, master):
super(dashboard, self).__init__(master)
self.grid()
self.buttons()
def buttons(self):
#student dashboard button
bttn1 = Button(self, text = "Student",
command=self.student, height = 2, width= 15)
bttn1.grid()
#teacher dashboard button
bttn2 = Button(self, text = "Teacher", height = 2, width= 15)
bttn2.grid()
#exit button
bttn3 = Button(self, text = "Exit",
command=root.destroy, height = 2, width= 15)
bttn3.grid()
def student(self):
#view highscores button
bttn1 = Button(self, text = "Highscores", height = 2, width= 15)
bttn1.grid()
#print score button
bttn2 = Button(self, text = "Print Score", height = 2, width= 15)
bttn2.grid()
#exit button
bttn3 = Button(self, text = "Main Menu",
command=root.destroy, height = 2, width= 15)
bttn3.grid()
#main
root = Tk()
root.title("Dashboard")
root.geometry("300x170")
app = dashboard(root)
root.mainloop()
Wondered if someone could help me basically, with this GUI I am creating I want to be able to access a new page on the same frame but the buttons from the main menu stay once I go to another page, does anyone know how I can hide/forget the buttons and go back to them at a later stage? Thanks.
Updated to use sub-Frames
You could do it using the universal grid_remove() method (here's some documentation). One way to use it would be to keep references to each of the Button widgets created so you can call this method on them as needed.
However that can be simplified slightly—even though it takes about the same amount of code—by putting all the Buttonss for each page into a separate sub-Frame and just showing or hiding it which will automatically propagate do to all the widgets it contains. This approach also provides a better foundation for the rest of your program.
I've implemented this by adding a main_button_frame attribute to your class, as well as one named student_button_frame to hold those you have on the student page (since you'll probably need it to hide them too).
One nice thing about grid_remove() is that if you later call grid() on the same widget, it will remember all the settings it (and its sub-widgets) had before it was removed, so you don't need to create and maintain a list of every single one of them yourself.
Also note I also made some general modifications to your code so it conforms to the PEP 8 - Style Guide for Python Code recommendations better. I highly recommend you read and start following it.
from tkinter import *
import tkinter as tk
class Dashboard(Frame):
def __init__(self, master):
super().__init__(master)
self.grid()
self.main_button_frame = None
self.student_button_frame = None
self.create_main_buttons()
def create_main_buttons(self):
if self.student_button_frame: # Exists?
self.student_button_frame.grid_remove() # Make it invisible.
if self.main_button_frame: # Exists?
self.main_button_frame.grid() # Just make it visible.
else: # Otherwise create it.
button_frame = self.main_button_frame = Frame(self)
button_frame.grid()
# Student Dashboard button
bttn1 = Button(button_frame, text="Student",
command=self.create_student_buttons, height=2,
width=15)
bttn1.grid()
# Teacher Dashboard button
bttn2 = Button(button_frame, text="Teacher", height=2, width=15)
bttn2.grid()
# Dashboard Exit button
bttn3 = Button(button_frame, text="Exit", command=root.destroy,
height=2, width=15)
bttn3.grid()
def create_student_buttons(self):
if self.main_button_frame: # Exists?
self.main_button_frame.grid_remove() # Make it invisible.
if self.student_button_frame: # Exists?
student_button_frame.grid() # Just make it visible.
else: # Otherwise create it.
button_frame = self.student_button_frame = Frame(self)
button_frame.grid()
# Highscores button
bttn1 = Button(button_frame, text="Highscores", height=2, width=15)
bttn1.grid()
# Print Score button
bttn2 = Button(button_frame, text="Print Score", height=2, width=15)
bttn2.grid()
# Student Exit button
bttn3 = Button(button_frame, text="Exit", command=root.destroy,
height=2, width=15)
bttn3.grid()
# main
root = Tk()
root.title("Dashboard")
root.geometry("300x170")
app = Dashboard(root)
root.mainloop()