Making a class remove itself - python

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"

Related

Is there a way to display a class in python tkinter as a Frame, so other things can be added?

I want to have a tkinter window that displays both a cronometer and a sudoku. The cronometer is a class, so how can I add it to the window that displays the sudoku?
I already managed to get two separate windows, but I couldn't make one with both things.
def GUI4x4(dif): #This function gets just called from other place
# What I want is to be able to display this class
# Cronometer in the main window that's created below
class Cronometer():
...
def __init__(self):
self.crono=Tk()
self.tiempo = StringVar()
self.tiempo.set("00:00:00")
self.label = Label(self.crono,textvariable=self.tiempo, bg="white")
self.label.grid(column=0,row=0)
self.label.configure(font=("Times 13 bold"))
self.btnI = Button(self.crono, bg="white", text="Start",command=self.iniciarT,font=("Times 11"))
self.btnI.grid(pady=3,column=0,row=1)
self.btnP = Button(self.crono, bg="white", text="Pause",command=self.pausarT,font=("Times 11"))
self.btnP.grid(pady=3,column=0,row=2)
self.btnR = Button(self.crono, bg="white", text="Restart",command=self.reiniciarT,font=("Times 11"))
self.btnR.grid(pady=3,column=0,row=3)
GUI = Tk() # This creates the main window, and places
# 34 buttons in it
...
# Defining the Buttons
btn00 = Button(GUI, text=Tablero[0][0], width=5, height=3, activebackground="lime")
btn01 = Button(GUI, text=Tablero[0][1], width=5, height=3, activebackground="lime")
btn02 = Button(GUI, text=Tablero[0][2], width=5, height=3, activebackground="lime")
...
btn33 = Button(GUI, text=Tablero[3][3], width=5, height=3, activebackground="lime")
#Placing the 34 buttons
btn00.grid(row=0, column=0)
btn01.grid(row=0, column=1)
btn02.grid(row=0, column=2)
...
btn33.grid(row=3, column=3)
The standard way to deal with this with tkinter is that each "widget" in the application is its own class based on the tkinter Frame widget, one class for the chrono, another for the sudoko game. There might even be a main app class.
Advantage of this method is that each widget frame can be created independently and then joined together later. These classes might also be split up in to separate code files.
A fairly simple example below
import tkinter as tk
class Chromometer(tk.Frame):
def __init__(self,master=None,**kw):
tk.Frame.__init__(self,master=master,**kw)
self.tiempo = tk.StringVar()
self.tiempo.set("00:00:00")
self.label = tk.Label(self,textvariable=self.tiempo, bg="white")
self.label.grid(column=0,row=0)
class Sudoko(tk.Frame):
def __init__(self,master=None,**kw):
tk.Frame.__init__(self,master=master,**kw)
self.label = tk.Label(self,text="Sudoko", bg="white")
self.label.grid(column=0,row=0)
class MainApp(tk.Frame):
def __init__(self,master=None,**kw):
tk.Frame.__init__(self,master=master,**kw)
self.chrono = Chromometer(master=self)
self.chrono.grid()
self.sudoko = Sudoko(master=self)
self.sudoko.grid()
if __name__ == '__main__':
root = tk.Tk()
app = MainApp(master=root)
app.grid()
root.mainloop()
Each class will have their own methods to perform the functionality needed by each. The chromo/chrono class will have a method to update the timer.

Tkinter ttk Combobox Default Value

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.

Store class variable in dictionary

I am making a program that will have a box that receives two inputs, an event name and the time allocated for the event, and will later output a list of all the events that have been added. This is the class for the input boxes:
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)
I am fairly new to programming, but I believe the correct way to store the name and time would be in a dictionary.
There could be multiple instances of the class running at the same time and more could run after these have already been stored. It would be preferable to have them all stored in the same dictionary.
However, I do not know how to store the self variables in a dictionary that I could later access outside of the class. Is this possible? If so, how? If not, how could I store the variables in a way that they could be accessed in a separate function?
EDIT: This is the minimal code (I think) needed to replicate my issue:
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 addOne(master):
this_instance = agendaEvent(master)
def addEvent():
window = Toplevel()
addOneButton = Button(window, text='+1', command=partial(addOne, window))
addOneButton.grid(row=0, column=0, pady=5)
doneButton = Button(window, text='Done', command=partial(done, window))
doneButton.grid(row=1, column=0, pady=3)
def done(windowInstance):
#This is where I was thinking
#the code for storing information
#should be because it should store
#when the done button is pressed.
#This may not be a good way to do it,
#I don't know.
windowInstance.destroy()
windowInstance = ""
root = Tk()
addEventButton = Button(root, text="Add Events", command=addEvent)
addEventButton.pack()
root.mainloop()
I think what you need to do is store the agendaEvent instances you create in a list; then you can iterate over the list afterwards:
from Tkinter import *
from functools import partial
events = [] # place to store the instances
class agendaEvent:
...
def addOne(master):
events.append(agendaEvent(master)) # store the new instance in the list
def addEvent():
...
def done(windowInstance):
for event in events:
... # use event.time, event.date, etc.
windowInstance.destroy()
windowInstance = ""
root = Tk()
addEventButton = Button(root, text="Add Events", command=addEvent)
addEventButton.pack()
root.mainloop()
You may also need to clear the list in done, if this section can be called again.
You could use a class variable for storing data available outside the instance. Assuming you want to store names mapped to times you could add the following (this assumes that the Entry class is hashable):
class agendaEvent:
instances = dict() # Class variable, access with agendaEvent.instances
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.instances[self.name] = self.time
Now to access these values from elsewhere use:
agendaEvent.instances
E.g. if you have a name Entry stored in 'name' retrieve its time Entry with:
time = agendaEvent.instances[name]
If you want to store agenda events in a dictionary, you will have to serialize the agenda events object before storing it as the value and de-serialize the object when trying to access its name or time.
You could do this using a library such as Pickle or do it yourself using Json strings.
From your question, I'm not sure if this is what you want to do.

Python - Auto add widgets

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

tkinter script to print entry text

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.

Categories