I'm just starting to learn to use tkinter with python and it seems counterintuitive that this attempt at a simple script to print whatever is entered into the entry box, doesn't work:
class App:
def __init__(self, master):
frame = Frame(master)
frame.pack()
text_write = Entry(frame)
text_write.pack()
self.button = Button(frame, text="quit", fg="red", command=frame.quit)
self.button.pack(side=LEFT)
self.hi_there = Button(frame, text='hello', fg='black', command=self.say_hi(text_write.get()))
self.hi_there.pack(side=RIGHT)
def say_hi(self, text):
print(text)
root = Tk()
app = App(root)
root.mainloop()
This does nothing and outputs no errors, but if I change it to this:
class App:
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.text_write = Entry(frame)
self.text_write.pack()
self.button = Button(frame, text="quit", fg="red", command=frame.quit)
self.button.pack(side=LEFT)
self.hi_there = Button(frame, text='hello', fg='black', command=self.say_hi)
self.hi_there.pack(side=RIGHT)
def say_hi(self):
print(self.text_write.get())
then it calls the function and prints the value. Why does the 'self' need to be declared there? And why can't you pass the value of text_write as an argument to say_hi (as in the first example) and have it display? Or can you and I'm just doing it wrong?
When you do this:
self.button = Button(..., command=func())
then python will call func(), and the result of that will be assigned to the command attribute. Since your func doesn't return anything, command will be None. That's why, when you push the button, nothing happens.
Your second version seems fine. The reason self is required is that without it, text_write is a local variable only visible to to the init function. By using self, it becomes an attribute of the object and thus accessible to all of the methods of the object.
If you want to learn how to pass arguments similar to your first attempt, search this site for uses of lambda and functools.partial. This sort of question has been asked and answered several times.
Related
Learning to use Tkinter and following an online tutorial. This is an example given where text is entered and then label will update accordingly to the input text field.
I'm trying it in Python3 on Mac and on Raspberry Pi and I don't see the effect of trace, hence the label doesn't get modified by the Entry. Any help would be appreciate (or any other simple example of how to use Entry and Trace together)
Thanks.
from tkinter import *
class HelloWorld:
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.button = Button(
frame, text="Hello", command=self.button_pressed
)
self.button.pack(side=LEFT, padx=5)
self.label = Label(frame, text="This is a label")
self.label.pack()
a_var = StringVar()
a_var.trace("w", self.var_changed)
self.entry = Entry(frame,textvariable=a_var)
self.entry.pack()
def button_pressed(self):
self.label.config(text="I've been pressed!")
def var_changed(self, a, b, c):
self.label.config(text=self.entry.get())
def main():
root = Tk()
root.geometry("250x150+300+300")
ex = HelloWorld(root)
root.mainloop()
if __name__ == '__main__':
main()
The problem is that you are using a local variable for a_var, and on the Mac it is getting garbage-collected. Save a reference to the variable (eg: self.a_var rather than just a_var).
self.a_var = StringVar()
self.a_var.trace("w", self.var_changed)
self.entry = Entry(frame,textvariable=self.a_var)
self.entry.pack()
Note: if all you want is to keep a label and entry in sync, you don't need to use a trace. You can link them by giving them both the same textvariable:
self.entry = Entry(frame, textvariable=self.a_var)
self.label = Label(frame, textvariable=self.a_var)
I'm building a Tkinter application and I came across an issue with setting a default value to a combobox. I managed to fix the problem, but I am curious to know why it worked and I would like to know if there is a better way to do it.
I have a tk.Toplevel() window pop up with a combobox using the fowling code:
class add_equation():
def __init__(self):
self.add_window = tk.Toplevel()
self.add_window.title("Add Equation Set")
self.add_window.resizable(width=False, height=False)
self.name_entry_var = tk.StringVar()
self.name_entry = ttk.Entry(self.add_window, textvariable=self.name_entry_var, width=30)
self.name_entry.grid(row=1, columnspan=2, stick="w")
self.equation_type_var = tk.StringVar()
self.equation_type = ttk.Combobox(self.add_window, textvariable=self.equation_type_var, values=("System", "Exact", "Import Point List..."), state="readonly", width=28, postcommand =lambda: self.add_window.update())
self.equation_type.current(0)
self.equation_type.grid(row=2, columnspan=2, sticky="w")
self.add_window.update()
The class add_quation() is called in the following bit of code:
import tkinter as tk
from tkinter import ttk
class Solver_App(tk.Tk, ttk.Frame):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
list_frame = ttk.Frame(self, height=50)
list_frame.pack(side="top", fill="y", expand=True, anchor="w")
# Button that will call the class add_equation that creates a new window.
add_button = ttk.Button(list_frame, text="Add Equation Set", command=add_equation)
add_button.pack(side="top", expand=False, fill="x", padx=10, pady=5, anchor="n")
app = Solver_App()
app.mainloop()
Looking back at the add_equation() class, if you remove self.equation_type.current(0) or postcommand =lambda: self.add_window.update(), the default value will no longer show, but with both, it works just fine. Why is it working like this instead of only having self.equation_type.current(0)?
I've tried to find a more elegant way of doing this, and I found something related over here, but I had no luck implementing that method and I assume calling add_equation() from a button command may have something to do with that.
*I'm using Python 3.4 on Mac OS X Yosemite.
I think this is probably because you're creating a window by calling the add_equation constructor, and that window is immediately garbage collected (or atleast the python handle to it is) so never gets properly refreshed.
I'd rewrite it as something like this:
class Equation_Window(tk.Toplevel):
def __init__(self):
tk.Toplevel.__init__(self)
self.title("Add Equation Set")
self.resizable(width=False, height=False)
self.name_entry_var = tk.StringVar()
self.name_entry = ttk.Entry(self, textvariable=self.name_entry_var, width=30)
self.name_entry.grid(row=1, columnspan=2, stick="w")
self.equation_type_var = tk.StringVar()
self.equation_type = ttk.Combobox(self, textvariable=self.equation_type_var, values=("System", "Exact", "Import Point List..."), state="readonly", width=28)
self.equation_type.current(0)
self.equation_type.grid(row=2, columnspan=2, sticky="w")
def add_equation():
w = Equation_Window()
w.wait_window()
(everything else remains the same)
I've changed the your add_equation class to something derived from a tk.Toplevel (and renamed it), which I think makes more sense. I've then made add_equation a function, and called wait_window (which acts like mainloop but just for one window). wait_window will keep w alive until the window is closed, and so everything gets refreshed properly.
I am making a program using Tkinter that will create a list of events. I used a class to make an event. If I click the Add Event button (second script shown), it creates a new instance of the class, lengthening the list. However, I also want to be able to remove events from the list. I am trying to attach a remove button to each class that, when clicked, will delete the class. This is my code from the class script (classes.py):
from Tkinter import *
class agendaEvent:
def __init__(self, master):
self.frame = Frame(master, padx=10, pady=10)
self.frame.pack(side=TOP)
self.name = Entry(self.frame)
self.name.grid(row=1, column=0)
self.time = Entry(self.frame, width=10)
self.time.grid(row=1, column=1, padx=5)
self.label1 = Label(self.frame, text="Event Name")
self.label1.grid(row=0, column=0)
self.label2 = Label(self.frame, text="Minutes")
self.label2.grid(row=0, column=1)
self.remove = Button(self.frame, text="Remove", command=agendaEvent.remove)
self.remove.grid(row=1, column=3)
def remove(agendaEvent):
del agendaEvent
When I press the remove button, I get the error
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python27\lib\lib-tk\Tkinter.py", line 1532, in __call__
return self.func(*args)
TypeError: unbound method remove() must be called with agendaEvent instance as first argument (got nothing instead)
How can I call the instance of agendaEvent? Or is there a better way of going about this? This is the code for the main script (main.py):
from Tkinter import *
import classes
def addEvent():
classes.agendaEvent(root)
root = Tk()
addEventButton = Button(root, text="Add Event", command=addEvent)
addEventButton.pack(side=BOTTOM)
root.mainloop()
You can remove labels and other widgets (not sure all) with .destroy(). If you store the instances of the class in some way you can forget or destroy them, but if the instance is just an instance then I don't see your problem (read, I can't help you). If you want to learn how to build GUIs with Tkinter you should check out sentdex's videos on the subject on youtube.
This is how I make tkinter widgets with buttons like I think you are trying to do:
I make a counter variable that counts how many objects my list contains.
I make a button that point to a method or a function that makes a label that contains the info I want it to contain, e.g. .get()
something from an entry box.
The function then adds one entry to the counter variable.
Then I make a button that points to a method or function that destroys the last label, I use the counter variable as index to know
what label to destroy.
I then subtract one from the counter variable
I use a dictionary to keep my labels and other widgets in order, e.g. by using a counter variable as key.
Example:
from tkinter import *
class MyEvent("something tkinter"):
def __init__(self, parent):
"here you need some tkinter code to make a frame or something to put your widgets in"
self.mycounter = 0
self.myEventLabel = {}
addButton = Button(parent, text="add event", command=addEvent).pack()
destroyButton = Button(parent, text="remove last event", command=removeEvent).pack()
def addEvent(self):
self.myEventLabel[self.mycounter] = Label("Here goes the options you want)
self.mycounter+=1
def removeEvent(self):
self.mycounter-=1
self.myEventLabel[self.mycounter].destroy()
Hope this helped, if I missed the point, then in my defense I'm not a programmer, just someone who needs to use programming as a means to an end. Again, Sentdex's videos covers alot of tkinter, check them out.
You have to save the class instance to remove it. Below, addEvent() has been changed to catch the return from calling the class, and that instance is attached to the button so the button knows which instance to destroy. This would better if all of the code was wrapped in classes but I am following the code you posted above which is likely just a simple example.
from Tkinter import *
from functools import partial
class agendaEvent:
def __init__(self, master):
self.frame = Frame(master, padx=10, pady=10)
self.frame.grid()
self.name = Entry(self.frame)
self.name.grid(row=1, column=0)
self.time = Entry(self.frame, width=10)
self.time.grid(row=1, column=1, padx=5)
self.label1 = Label(self.frame, text="Event Name")
self.label1.grid(row=0, column=0)
self.label2 = Label(self.frame, text="Minutes")
self.label2.grid(row=0, column=1)
def addEvent(master):
this_instance=agendaEvent(master)
## add the button to the frame created by this function call
## and not to the root, so it also is destroyed
rem = Button(this_instance.frame, text="Remove", bg="lightblue",
command=partial(remove_class, this_instance))
rem.grid(row=6, column=0)
def remove_class(instance):
## destroys the frame created by the instance
## i.e. self.frame in agendaEvent
instance.frame.destroy()
instance="" ## reassigns "instance" so the class is garbage collected
root = Tk()
addEventButton = Button(root, text="Add Event", command=partial(addEvent, root))
addEventButton.grid(row=5, column=0)
Button(root, text="Exit", bg="orange", command=root.quit).grid(row=99, column=0)
root.mainloop()
Changing your class as shown below will fix the error you're currently getting. It's caused by the fact that the handler specified with a command keyword argument passed to the Button constructor is always called without any arguments. Fixing that with a lambda function then exposes another problem, which is that you've named both the Button instance and the method you want to associated it with the same thing – remove – so I renamed it to remove_button.
from Tkinter import *
class agendaEvent:
def __init__(self, master):
self.frame = Frame(master, padx=10, pady=10)
self.frame.pack(side=TOP)
self.name = Entry(self.frame)
self.name.grid(row=1, column=0)
self.time = Entry(self.frame, width=10)
self.time.grid(row=1, column=1, padx=5)
self.label1 = Label(self.frame, text="Event Name")
self.label1.grid(row=0, column=0)
self.label2 = Label(self.frame, text="Minutes")
self.label2.grid(row=0, column=1)
self.remove_button = Button(self.frame, text="Remove",
command=lambda: self.remove())
self.remove_button.grid(row=1, column=3)
def remove(self): # by convention the first argument to a class
del self # method is usually named "self"
I'm trying to use an Entry field to get manual input, and then work with that data.
All sources I've found claim I should use the get() function, but I haven't found a simple working mini example yet, and I can't get it to work.
I hope someone can tel me what I'm doing wrong. Here's a mini file:
from tkinter import *
master = Tk()
Label(master, text="Input: ").grid(row=0, sticky=W)
entry = Entry(master)
entry.grid(row=0, column=1)
content = entry.get()
print(content) # does not work
mainloop()
This gives me an Entry field I can type in, but I can't do anything with the data once it's typed in.
I suspect my code doesn't work because initially, entry is empty. But then how do I access input data once it has been typed in?
It looks like you may be confused as to when commands are run. In your example, you are calling the get method before the GUI has a chance to be displayed on the screen (which happens after you call mainloop.
Try adding a button that calls the get method. This is much easier if you write your application as a class. For example:
import tkinter as tk
class SampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.entry = tk.Entry(self)
self.button = tk.Button(self, text="Get", command=self.on_button)
self.button.pack()
self.entry.pack()
def on_button(self):
print(self.entry.get())
app = SampleApp()
app.mainloop()
Run the program, type into the entry widget, then click on the button.
You could also use a StringVar variable, even if it's not strictly necessary:
v = StringVar()
e = Entry(master, textvariable=v)
e.pack()
v.set("a default value")
s = v.get()
For more information, see this page on effbot.org.
A simple example without classes:
from tkinter import *
master = Tk()
# Create this method before you create the entry
def return_entry(en):
"""Gets and prints the content of the entry"""
content = entry.get()
print(content)
Label(master, text="Input: ").grid(row=0, sticky=W)
entry = Entry(master)
entry.grid(row=0, column=1)
# Connect the entry with the return button
entry.bind('<Return>', return_entry)
mainloop()
*
master = Tk()
entryb1 = StringVar
Label(master, text="Input: ").grid(row=0, sticky=W)
Entry(master, textvariable=entryb1).grid(row=1, column=1)
b1 = Button(master, text="continue", command=print_content)
b1.grid(row=2, column=1)
def print_content():
global entryb1
content = entryb1.get()
print(content)
master.mainloop()
What you did wrong was not put it inside a Define function then you hadn't used the .get function with the textvariable you had set.
you need to put a textvariable in it, so you can use set() and get() method :
var=StringVar()
x= Entry (root,textvariable=var)
Most of the answers I found only showed how to do it with tkinter as tk. This was a problem for me as my program was 300 lines long with tons of other labels and buttons, and I would have had to change a lot of it.
Here's a way to do it without importing tkinter as tk or using StringVars. I modified the original mini program by:
making it a class
adding a button and an extra method.
This program opens up a tkinter window with an entry box and an "Enter" button. Clicking the Enter button prints whatever is in the entry box.
from tkinter import *
class mini():
def __init__(self):
master = Tk()
Label(master, text="Input: ").grid(row=0, sticky=W)
Button(master, text='Enter', command=self.get_content).grid(row=1)
self.entry = Entry(master)
self.entry.grid(row=0, column=1)
master.mainloop()
def get_content(self):
content = self.entry.get()
print(content)
m = mini()
So I am currently trying to create a button on a GUI that will let the user generate a new entry field.
I have no idea how to do this. I'm guessing that it will require a lambda function, but apart from that, I have no idea.
Here's the basic code I have so far:
from tkinter import *
class prac:
def autoAddWidget(self,frame,x,y):
self.entryField = Entry(frame,text="Entry Field")
self.entryField.grid(row=x, column=y)
#lambda function?
def __init__(self, master):
frame = Frame(master, width=60, height=50)
frame.pack()
x=1
self.addWidgetButton = Button(frame, text="Add new widget", command=self.autoAddWidget(frame, x,0))
self.addWidgetButton.grid(row=0, column=0)
x+=1
root = Tk()
app = prac(root)
root.mainloop()
Would appreciate the help.
Thanks
You're passing to the command argument result from the method self.autoAddWidget(frame, x,0) not method itself. You have to pass there a reference to a callable object, a function that will be called when the event occurs. Please check a documentation next time before you ask the question.
Ok, I fixed the code, now it works:
from tkinter import *
class Prac:
def autoAddWidget(self):
self.entryField = Entry(self.frame,text="Entry Field")
self.entryField.grid(row=self.x, column=0)
self.x+=1
def __init__(self, master):
self.frame = Frame(master, width=60, height=50)
self.frame.pack()
self.x=1
self.addWidgetButton = Button(self.frame, text="Add new widget", command=self.autoAddWidget)
self.addWidgetButton.grid(row=0, column=0)
root = Tk()
app = Prac(root)
root.mainloop()