Tkinter destroying an object in a different function isn't working - python

I'm trying to make a button that saves your username but then goes away after you set it.
this is my code:
def printValue():
User = Name.player_name.get()
label.config(text=f'Hi, {User}')
Name.button.destroy()
Name.player_name.destroy()
def Name():
label.config(text="What's your name?")
Name.player_name = Entry(root)
Name.player_name.pack(pady=15)
Name.button = Button(text="Change", command=printValue)
Name.button.pack()

The code below, with some minor changes like enabling change with [Return] and some layout cosmetics works OK (also with un-commented lines in printValue) . If you want the [Change] button and the entry area to go away un-comment the two lines turned into comments in the printValue function:
# https://stackoverflow.com/questions/72671126/tkinter-destroying-an-object-in-a-different-function-isnt-working
from tkinter import Tk, mainloop, Entry, Button, Label
root = Tk()
label = Label(root, font=('',12), padx=15, pady=5)
label.pack()
def Name():
label.config(text="What's your name?")
Name.player_name = Entry(root, font=('',12))
Name.player_name.pack(padx=15, pady=15)
Name.player_name.focus()
Name.button = Button(text="Change", command=printValue)
Name.button.pack()
def printValue(event=None):
User = Name.player_name.get()
# Name.player_name.destroy()
# Name.button.destroy()
label.config(text=f'Hi, {User}')
Name()
root.bind("<Return>", printValue)
mainloop()
By the way: The in the question provided code demonstrates an interesting approach of making names of variables global by setting function attributes in the function itself. This way it is possible to assign values in one function and retrieve them in another without passing return values or declaring variables global. I am not aware of already having seen such approach used in Python code here on stackoverflow. How does it come you use such code?

Related

How to make a clear function in python tkinter?

I want to add a clear button in tkinter which clears the text I have entered already and the output. How can I do that?
Here is my code which gets input and provide output using Label.
from tkinter import *
root = Tk()
def myFunction():
k = myInput.get()
labl = Label(root,text=k)
labl.pack()
myInput = Entry(root,width=50, bg="#d2ebd5",borderwidth=5)
myInput.pack()
myButton = Button(root,text="Click me!",command=myFunction)
myButton.pack()
root.mainloop()
Use myInput.set("") to clear Entry var, move labl from function to main level, create another function to clear Entry var and labl text and button to run it.

Tkinter GUI: Adding new entry boxes using .grid() when button is pressed

I'm trying to create new entry boxes when the "ADD entry" is used for my program. I do realize that the 'pack codes' works when I run it individually, but when I combine with existing program which is in grid(), the window is not showing when I run my program.
I also understand that we should not to use both .pack() when I have other things using .grid() in the same program. Hence, my question is, how do I create new entry boxes in grid.
I have tried finding elsewhere but they all suggested pack. For instance: I have looked here here, etc etc, to name a few; but couldn't find anything similar to mine. I would like to add entry boxes below the current entry boxes which is at row 3.
I'm kind of new to Python; (am using Python 2.7 and tkinter module in this program. Thank you very much for the help!
My simplified error codes are as follows:
from Tkinter import *
import tkFileDialog
import tkMessageBox
import Tkinter
import os
class Window:
def __init__(self, master):
self.filename3=""
csvfile=Label(root, text="NUMBERS").grid(row=3, column=0)
bar=Entry(master).grid(row=3, column=3)
self.entryText3 = StringVar()
self.bar = Entry(root, textvariable=self.entryText3).grid(row=3, column=3)
#BUTTONS
self.cbutton= Button(root, text="OK", command=self.process_csv)
self.cbutton.grid(row=15, column=6, sticky = W + E)
####PROBLEM FROM HERE#####
all_entries = []
addboxButton = Button(root, text='ADD', fg="BLUE", command=self.addBox)
addboxButton.pack()
#I have also tried replacing the last 2 lines with the following 2 lines instead but to no avail:
#self.addboxButton = Button(root, text='ADD THA-ID', fg="BLUE", command=self.addBox)
#self.addboxButton.grid(row=3, column=6)
frame_for_boxes = Frame(root)
frame_for_boxes.pack()
def addBox(self):
print "ADD"
next_row = len(all_entries)
lab = Label(frame_for_boxes, text=str(next_row+1))
lab.grid(row=next_row, column=0)
ent = Entry(frame_for_boxes)
ent.grid(row=next_row, column=0)
all_entries.append( ent )
def process_csv(self):
#own program here
print "program"
root = Tk()
window=Window(root)
root.mainloop()
There are few issues with your program other than the one you stated:
Inside the initializer (__init__()) you attached the widgets to root which is not defined within the scope of your Window class. The reasonable way to fix this issue is when you use an instance of Tk(), id est root id est master in Window class, make it as an instance variable. This means the first thing you have to do in the inititializer is this : self.master = master. This will result in you having to replace all root occurrences within __init__() by self.master
The second issue to fix is the one you specified in your question's title: you can not mix the grid() and pack() layout managers for the same widget container. You have to decide which one. Since you placed most of the widgets using grid(), I suggest you to get rid of pack(). This means:
addboxButton.pack() becomes, for example, addboxButton.grid(row=0, column=1)
frame_for_boxes.pack() becomes, for example, frame_for_boxes.grid(row=0, column=0)
The previous list item fixes the problem but it will make you discover other issues within your program which are related:
NameError: global name 'all_entries' is not defined
NameError: global name 'frame_for_boxes' is not defined
This is because those widget variables are not reachable within the scope of addBox() function. To resolve this issue, you have to make those 2 elements as instance variables. This means:
all_entries = [] becomes self.all_entries = []
frame_for_boxes = Frame(root) becomes self.frame_for_boxes = Frame(self.master) (remember we replaced root by self.master in 1.)
The consequence of this error fixing is that you have to use all over inside your program:
self.all_entries instead all_entries
self.frame_for_boxes instead of frame_for_boxes
For scalability reasons, I think you will have at least to make the rest of widgets as instance variables (i.d. prefix them with the self keyword)
As your real project is more complicated than what you show in this MCVE, I encourage you to adopt the SaYa idiom when creating and placing widget elements. This means you will need to replace:
csvfile=Label(root, text="NUMBERS").grid(row=3, column=0)
by
self.csvfile = Label(self.master, text="NUMBERS")
self.csvfile.grid(row=3, column=0)
To avoid unexpected bugs in your program, you must do the same for the remaining widgets you declared in the inititialzer.
There are also other things I would like to mention, but most of them are available on PEP8
What you have to do it to create a command which creates the entries and stores the new entries inside of a variable.
In my case, I use Entry_i and store in Entries but you can use self.Entries to make communication easier. (python 3.5)
def Make_Entry(self, root, Entries, x, y):
Entry_i = Entry(root, bd = 5)
Entry_i.grid(row = x, column = y, sticky = W+E+N+S)
Entries.append(Entry_i)
return(Entries, x+1, y+1)

Python, Tkinter - Making get() dynamic

Below is some code that I'm testing with. In this code, I am able to create a window and have a Label on top and a Entry field on bottom. When I type in that entry field, I can dynamically change what's in the label. Now, I have included a function that is trying to evaluate a variable assigned to "tex", by storing what is predefined in the Entry widget. Which is "cat". This is picked up by:
tex = e.get()
I understand that get() is not changing dynamically as I change the text in the entry widget. So it cannot change to "dog" when I change the string in the entry widget. Is this possible? Here is the code:
from Tkinter import *
import time
root = Tk()
def change():
if tex == ("cat"):
time.sleep(0.5)
pass
else:
time.sleep(0.5)
e.delete(0, END)
e.insert(0, "dog")
v = StringVar()
e = Entry(root, textvariable=v)
e.insert(0, "cat")
e.pack(side=BOTTOM)
tex = e.get() #When text is defined with get(), it does not change
#dynamically with the entry widget
l = Label(root, textvariable=v)
l.pack(side=TOP)
change()
root.mainloop()
Any help would be appreciated.
To answer your specific question, no, there is no way for tex to magically keep updated when the entry widget changes. That feature is exactly why the textvariable attribute exists -- it causes the variable to always be updated with the value in the entry widget. If you need the data in another variable, you will have to call the .get() method.
That being said, you can set a trace on the variable, or a binding on the entry widget, and have it call code that can update tex to whatever you want when the entry widget changes.
For example, the following code shows how to have callback called whenever the value in the entry widget changes:
def callback(*args):
global tex
tex = e.get()
v.trace("w", callback)
For more information about what arguments are passed to the callback, see What are the arguments to Tkinter variable trace method callbacks?
All that being said, you have a couple of critical flaws in your code. First, you are creating the StringVar incorrectly. It needs to be v = StringVar() (note the trailing ()).
Also, you should never call sleep in the main thread of a GUI program. Read up on the after method if you want to execute some code after some time has elapsed.

Removing Tkinter Objects (created in a function) in a separate function

I need to be able to clear my tkinter window of all objects (with a function), and create the objects again with a function. However, I cannot access the objects created with my first function with the second function. I recreated my problem below.
import tkinter
window = tkinter.Tk()
def create():
test = tkinter.Button(window, text="Example", command=delete)
test.place(x=75, y=100)
def delete():
test.place_forget()
create()
window.mainloop()
This returns the error - NameError: name 'test' is not defined
Here's a quick sample of how your code might look, using an object oriented structure:
import tkinter as tk
class MyApp: # No need to inherit 'object' in Python 3
def __init__(self, root):
self.root = root
def create_button(self):
self.test_button = tk.Button(self.root,
text="Example",
command=self.delete_button)
self.test_button.place(x=75, y=100)
def delete_button(self):
self.test_button.place_forget()
def run(self):
self.create_button()
self.root.mainloop()
if __name__=='__main__':
root = tk.Tk()
app = MyApp(root)
app.run()
You create a MyApp object that 'owns' the button, and has methods that explicitly act on the things that it owns. Any method of the MyApp object has a reference to various widgets, via the self argument that automatically gets sent in.
This is a lot more code than you had before, and to be honest, for what your code does right now, it's an overkill. Malik's solution of using global is probably fine. However, if you want to add more widgets, layer them out, have them interact in more complex ways etc, then using global can introduce hard-to-find bugs, and makes it incredibly hard to wrap your head around what's going on.
Any non-trivial use of Tkinter that I have seen has used an object-oriented style similar to the above example.
As an aside, I wouldn't create the delete function - using the .config method to set the command after you create the button would be better:
def create_button(self):
self.test_button = tk.Button(self.root, text="Example")
self.test_button.config(command=self.test_button.place_forget)
self.test_button.place(x=75, y=100)
Using .config allows you to set commands that are methods of the button you just created, which you can't do when you set the command as a part of the button instantiation.
Well if you're using two different functions, you're going to need global variables:
import tkinter
window = tkinter.Tk()
test = None
def create():
global test
test = tkinter.Button(window, text="Example", command=delete)
test.place(x=75, y=100)
def delete():
global test
test.destroy() # or place_forget if you want
window.after(5000, create) # button reappears after 5 seconds
create()
window.mainloop()
Your delete function could not destroy the button as it was only defined in the create function. The workaround is to create a global variable that can be accessed by both.

Checking if the user presses 'Return' while selected in an Entry box Tkinter

I'm using Tkinter to create a GUI for a simple geometry calculator I'm creating.
Basically, what I have is an Entry box. What I want is for the program/GUI/system to detect when the user of the program hits the 'Enter' or 'return' key WHILE they are in the Entry box. When this is detected, I want the contents of the Entry box to be appended to a list I have defined earlier. I also want a simple label to be created on the GUI that displays the contents of the list (including the appended item(s)). Note that the list begins with nothing in it.
Here is my code so far:
from tkinter import *
#Window setup(ignore this)
app = Tk()
app.title('Geometry Calculator')
app.geometry('384x192+491+216')
app.iconbitmap('Geo.ico')
app.minsize(width=256, height=96)
app.maxsize(width=384, height=192)
app.configure(bg='WhiteSmoke')
#This is the emtry list...
PointList = []
#Here is where I define the variable that I will be appending to the list (which is the object of the Entry box below)
StrPoint = StringVar()
def list_add(event):
#I don't really know how the bind-checking works and how I would implement it; I want to check if the user hits enter while in the Entry box here
if event.char == '':
PointList.append(StrPoint)
e1 = Entry(textvariable=StrPoint).grid(row=0, column=0)
app.bind('<Return>', list_add)
mainloop()
I don't really know the proper way to check for 'Return' and then use it in an if statement.
I hope you understand what I'm trying to get help with, and I've looked all around for an explanation that I could understand with no success.
Instead of binding with the app just bind it with the Entry widget object,i.e,e1
from tkinter import *
#Window setup(ignore this)
app = Tk()
app.title('Geometry Calculator')
app.geometry('384x192+491+216')
app.iconbitmap('Geo.ico')
app.minsize(width=256, height=96)
app.maxsize(width=384, height=192)
app.configure(bg='WhiteSmoke')
#This is the emtry list...
PointList = []
#Here is where I define the variable that I will be appending to the list (which is the object of the Entry box below)
StrPoint = StringVar()
def list_add(event):
print ("hello")
#I don't really know how the bind-checking works and how I would implement it; I want to check if the user hits enter while in the Entry box here
if event.char == '':
PointList.append(StrPoint)
e1 = Entry(textvariable=StrPoint)
e1.grid(row=0, column=0)#use grid in next line,else it would return None
e1.bind('<Return>', list_add)# bind Entry
mainloop()
The solution is to set the binding on the widget itself. That way, the binding will only apply while focus is on that widget. And since you're binding on a specific key, you don't need to check for the value later. You know the user pressed return, because that's the only thing that will cause the binding to fire.
...
e1.bind('<Return>', list_add)
...
You have another problem in that your list_add function needs to call the get method of the variable rather than accessing the variable directly. However, since you aren't using any of the special features of a StringVar, you really don't need it -- it's just one more thing you have to manage.
Here's how to do it without the StringVar:
def list_add(event):
PointLit.append(e1.get())
...
e1 = Entry(app)
e1.grid(row=0, column=0)
e1.bind('<Return>', list_add)
Note that you need to create the widget and lay out the widget in two steps. Doing it the way you did it (e1=Entry(...).grid(...) will cause e1 to be None since that is what .grid(...) returns.

Categories

Resources