Tkinter error when calling function through a button - python

So I'm trying to use some entry widgets in tkinter and then use a function that does multiple .get() commands at the same time. However I get an error that the function is not defined, even though they are within the same class. Here's the samples of the code:
def GetSubjects():
subject1 = subject1entry.get()
subject2 = subject2entry.get()
subject3= subject3entry.get()
subjectConfirm.grid(row=3, column=0, command=GetSubjects)
As I said these are both in the same class and yet the button cannot call the function.
I am relatively new to tkinter and this site so forgive me if this is easy stuff, but I just couldn't find a solution anywhere else.
Edit: Here's the whole class, I know my code is probably very suboptimal but this is my first large project I've tried
class Menu:
def __init__(self, master):
frame = tk.LabelFrame(master, text="Main Menu", padx=100, pady=10)
frame.grid(row=0, column=0, padx=15, pady=15)
# Create timetable button
createTimetable = tk.Button(frame, text="Create Timetable", command=self.CreateTimetable)
createTimetable.grid(row=0, column=0)
# Exit program button
exitProgram = tk.Button(frame, text="Exit Program", command=self.CloseWindow)
exitProgram.grid(row=1, column=0)
def CloseWindow(self):
root.destroy()
def GetSubjects():
subject1 = subject1entry.get()
subject2 = subject2entry.get()
subject3 = subject3entry.get()
def CreateTimetable(self):
tableWindow = tk.Toplevel(root)
tableWindow.title('Timetable Maker Window')
tableWindow.geometry("800x500+400+200")
# Subject labels
subjectlabel1 = tk.Label(tableWindow, text="Enter your first subject:")
subjectlabel2 = tk.Label(tableWindow, text="Enter your second subject:")
subjectlabel3 = tk.Label(tableWindow, text="Enter your third subject:")
# Subject entry boxes
subject1entry = tk.Entry(tableWindow)
subject2entry = tk.Entry(tableWindow)
subject3entry = tk.Entry(tableWindow)
# Puts subject entry boxes on screen
subject1entry.grid(row=0, column=1)
subject2entry.grid(row=1, column=1)
subject3entry.grid(row=2, column=1)
# Puts subject labels on screen
subjectlabel1.grid(row=0, column=0)
subjectlabel2.grid(row=1, column=0)
subjectlabel3.grid(row=2, column=0)
# Creates subject confirm button
subjectConfirm = tk.Button(tableWindow,text="Press to confirm subjects")
# Puts subject confirm button on screen
subjectConfirm.grid(row=3, column=0, command=GetSubjects)
print(subject1)
print(subject2)
print(subject3)

Your GetSubjects() method should look like this
def GetSubjects(self):
subject1 = self.subject1entry.get()
subject2 = self.subject2entry.get()
subject3 = self.subject3entry.get()
Your CreateTimetable method should look like this (incomplete but give you the right idea)
def CreateTimetable(self):
# Code removed for clarity
self.subject1entry = tk.Entry(tableWindow)
self.subject2entry = tk.Entry(tableWindow)
self.subject3entry = tk.Entry(tableWindow)
And your callback for the button should look like this
subjectConfirm = tk.Button(tableWindow,text="Press to confirm subjects", command=self.GetSubjects)
The properties/methods need to be part of the Menu class so you use self. to tell python to make these properties of the current class (self is the typical convention but could be anything as long as you are consistent).
Oh and the print(subject1) parts need to be at the end of the GetSubjects method instead of where they currently are.

Related

Tkinter - Python, how do I cause a button click to assign a value to a variable?

Using Tkinter and Python. Already created a window for the buttons to be placed on. I want there to be four buttons to appear, and I want to be able to click one of the four buttons, and be able for it to set the selection variable = "whatever I clicked", so that I can then use this variable later to call an API. When I run the program and click on the "General knowledge" button and print the selection, it does correctly print "General knowledge", but then when I try to return this selection variable it just doesn't work and I don't know why.
def select1():
selection = "General Knowledge"
print(selection)
def select2():
selection = "Science"
def select3():
selection = "Entertainment"
def select4():
selection = "Miscellaneous"
button1 = tk.Button(text = "General Knowledge", command = select1)
button1.place(x=100, y=100)
button2 = tk.Button(text = "Science", command = select2)
button2.place(x=100, y=140)
button3 = tk.Button(text = "Entertainment", command = select3)
button3.place(x=100, y=180)
button4 = tk.Button(text = "Miscellaneous", command = select4)
button4.place(x=100, y=220)
There are several ways to accomplish your goal.
One way is to write a single function that will take a value to assign to your variable. This way you can have as many buttons as you like and only a single function.
Not if you are using functions you have to either pass the variable to the function or let the function know it is in the global namespace.
import tkinter as tk
root = tk.Tk()
selection = ''
def assign_value(value):
global selection
selection = value
lbl["text"] = value
print(selection)
lbl = tk.Label(root, text='Selection Goes Here')
lbl.grid(row=0, column=0)
tk.Button(text="General Knowledge", command=lambda: assign_value("General Knowledge")).grid(row=1, column=0)
tk.Button(text="Science", command=lambda: assign_value("Science")).grid(row=2, column=0)
tk.Button(text="Entertainment", command=lambda: assign_value("Entertainment")).grid(row=3, column=0)
tk.Button(text="Miscellaneous", command=lambda: assign_value("Miscellaneous")).grid(row=4, column=0)
root.mainloop()
Or you can assign the value directly from the button.
import tkinter as tk
root = tk.Tk()
selection = tk.StringVar()
selection.set('Selection Goes Here')
lbl = tk.Label(root, textvariable=selection)
lbl.grid(row=0, column=0)
tk.Button(text="General Knowledge", command=lambda: selection.set("General Knowledge")).grid(row=1, column=0)
tk.Button(text="Science", command=lambda: selection.set("Science")).grid(row=2, column=0)
tk.Button(text="Entertainment", command=lambda: selection.set("Entertainment")).grid(row=3, column=0)
tk.Button(text="Miscellaneous", command=lambda: selection.set("Miscellaneous")).grid(row=4, column=0)
root.mainloop()
I am sure if I spent more time on this I could think up something else but the idea is basically write your code in a more DRY (Don't Repeat Yourself) fashion and make sure you are assigning the value to the variable in the global namespace or else it will not work as you expect.

Text Output from Class function? - Tkinter

I apologize if I'm not using proper terminology, I'm new to Python and have been doing this leisurely for fun. I'm trying to figure out everything myself by watching some tutorials, and reading online. The problem I'm having is I wanted to make a GUI for a python password generator (very easy first project.) I've created what I thought to be the correct format, but I'm having an issue with the function displaying in the GUI window rather than the terminal. I think it's when I come to the self.output where it messes everything up.
class GenPass:
def __init__(self, master):
frame = Frame()
frame.pack()
self.printButton = Button(frame, text="Generate Password", padx=4, pady=4, command=self.generate)
self.printButton.pack(side=LEFT)
self.quitButton = Button(frame, text="Copy to Clipboard", padx=4, pady=4, command=master.destroy)
self.quitButton.pack(side=LEFT)
self.output = Label(frame, fg="Green")
self.output.place(x=240, y=85)
self.output.config(text=self.generate)
def generate(self):
for i in range(3):
print(random.choice(Words).capitalize(), end='')
for i in range(2):
print(random.choice(Numbers), end='')
for i in range(1):
print(random.choice(Spec_Char))
I expect the outcome to be in the GUI window, there would be the generated password. It comes up in the terminal, but not in the window. When it does come up by making tweaks to the output.pack() it just lists random numbers and the name of the function (ex. 9012381generate)
Inside generate you should create string with password and use self.output.config(text=password) instead of print().
I changed code because it didn't work for me. Now everyone can copy code and run it.
from tkinter import *
import random
import string
words = string.ascii_uppercase
numbers = string.digits
spec_char = '!##$%'
class GenPass:
def __init__(self, master):
frame = Frame(master) # add parent for Frame
frame.pack()
self.printButton = Button(frame, text="Generate Password", padx=4, pady=4, command=self.generate)
self.printButton.pack(side=LEFT)
self.quitButton = Button(frame, text="Copy to Clipboard", padx=4, pady=4, command=master.destroy)
self.quitButton.pack(side=LEFT)
self.output = Label(master, fg="Green")
self.output.pack()
self.generate() # genrate password at start
def generate(self):
password = ''
for i in range(3):
password += random.choice(words)
for i in range(2):
password += random.choice(numbers)
for i in range(1):
password += random.choice(spec_char)
self.output.config(text=password)
root = Tk()
GenPass(root)
root.mainloop()
BTW: every widget should have parent so I add master in Frame(). Maybe it makes no difference here but if you would have many frames or widgets then widget without parent can be displayed in unexpected place.
pack() and place() and grid() shouldn't be mixed in one window or frame because pack() and grid() try to calculate position dynamically and other layour manager can makes problem with it. But using pack/grid/place you can put frame and inside this frame you can use different layour manager (pack/grid/place).

Entry data manipulation difficulties [duplicate]

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()

Making a class remove itself

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"

Tkinter. Press Enter in Entry box. Append to Text box. How?

I am making a chat program and decided to use Tkinter for the interface.
What I wanna do is a breeze in C# but Tkinter is new to me.
Basically I have a form with a Entry control and a Text control.
I want to know how to append text from the Entry control to the Text control after the user presses Enter.
Here's my code so far:
from tkinter import *
class Application:
def hello(self):
msg = tkinter.messagebox.askquestion('title','question')
def __init__(self, form):
form.resizable(0,0)
form.minsize(200, 200)
form.title('Top Level')
# Global Padding pady and padx
pad_x = 5
pad_y = 5
# create a toplevel menu
menubar = Menu(form)
#command= parameter missing.
menubar.add_command(label="Menu1")
#command= parameter missing.
menubar.add_command(label="Menu2")
#command= parameter missing.
menubar.add_command(label="Menu3")
# display the menu
form.config(menu=menubar)
# Create controls
label1 = Label(form, text="Label1")
textbox1 = Entry(form)
#command= parameter missing.
button1 = Button(form, text='Button1')
scrollbar1 = Scrollbar(form)
textarea1 = Text(form, width=20, height=10)
textarea1.config(yscrollcommand=scrollbar1.set)
scrollbar1.config(command=textarea1.yview)
textarea1.grid(row=0, column=1, padx=pad_x, pady=pad_y, sticky=W)
scrollbar1.grid(row=0, column=2, padx=pad_x, pady=pad_y, sticky=W)
textbox1.grid(row=1, column=1, padx=pad_x, pady=pad_y, sticky=W)
button1.grid(row=1, column=2, padx=pad_x, pady=pad_y, sticky=W)
form.mainloop()
root = Tk()
Application(root)
So you're using a tkinter.Text box, which supports the .insert method. Let's use it!
def __init__(self,form):
# Lots of your code is duplicated here, so I'm just highlighting the main parts
button1 = Button(form, text='Button1', command = self.addchat)
self.textbox = textbox1 # to make it accessible outside your __init__
self.textarea = textarea1 # see above
form.bind("<Return>", lambda x: self.addchat())
# this is the magic that makes your enter key do something
def addchat(self):
txt = self.textbox.get()
# gets everything in your textbox
self.textarea.insert(END,"\n"+txt)
# tosses txt into textarea on a new line after the end
self.textbox.delete(0,END) # deletes your textbox text

Categories