Here's my unfinished program:
I made a class for a Frame()
with input fields and a button.
I then have a
button which creates new instances of this class by adding them to an empty list. Objects .pack()
themselves upon initialization, so they appear in the window.
Edit: added working code you can try:
import tkinter as tk
from tkinter import ttk
import tkinter.font as tkFont
class JobItem:
def __init__(self, parent):
self.frame = tk.Frame(parent, bd=2, relief=tk.GROOVE)
self.frame.pack(side=tk.TOP, fill=tk.X, padx=2, pady=2)
self.nameLabel = ttk.Label(self.frame, text="Description:")
self.nameLabel.grid(column=0, row=0, sticky=tk.W, padx=5, pady=5)
self.nameEntry = ttk.Entry(self.frame)
self.nameEntry.grid(column=1, row=0, sticky=tk.W, padx=5, pady=5)
self.jobTypeLabel = ttk.Label(self.frame, text="Job type:")
self.jobTypeLabel.grid(column=3, row=0, sticky=tk.W, padx=5, pady=5)
self.selected_job = tk.StringVar()
self.job_type_cb = ttk.Combobox(self.frame, textvariable=self.selected_job, state='readonly')
self.job_type_cb['values'] = ['Still', 'Animation', 'Model production']
self.job_type_cb.current(0)
self.job_type_cb.grid(column=4, row=0, sticky=tk.W, padx=5, pady=5)
self.x = ttk.Button(self.frame, text="X", command=self.delete_itself)
self.x.grid(column=5, row=0, sticky=tk.E, padx=5, pady=5)
# v v v This method is what I don't know how to do properly v v v
def delete_itself():
pass
job_items = list()
def add_jobItem():
job_items.insert(len(job_items), JobItem(itemListContainer))
root=tk.Tk()
root.title('Estimate generator')
root.geometry('800x500')
headerFrame = tk.Frame(root, height=100)
headerFrame.pack(side=tk.TOP, fill=tk.X)
jobNameLabel = ttk.Label(headerFrame, text="Project name: ")
jobNameLabel.pack(side=tk.LEFT)
jobNameEntry = ttk.Entry(headerFrame)
jobNameEntry.pack(side=tk.LEFT, expand=True, fill=tk.X)
buttonFont = tkFont.Font(weight="bold")
plusButton = tk.Button(headerFrame, text='+', command=add_jobItem, font=buttonFont, fg='#656565', height=0, width=10)
plusButton.pack(side=tk.RIGHT, padx=(100,2), pady=2)
# Item List Frame
itemListContainer = tk.Frame(root)
itemListContainer.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
root.mainloop()
How can I make X
button on a given instance remove not only the packed elements, but also the object from job_items
list? How would we know what index does it occupy in the list? Or maybe I should take a different approach?
CodePudding user response:
I would suggest to pass a function to JobItem
which will be executed inside delete_itself
:
class JobItem:
def __init__(self, parent, on_delete=None):
self.on_delete = on_delete
...
def delete_itself(self):
# destroy the frame
self.frame.destroy()
# call self.on_delete if it is not None
if self.on_delete:
# pass JobItem itself to self.on_delete
self.on_delete(self)
job_items = list()
def del_jobItem(item):
# remove the job item from job_items list
job_items.remove(item)
def add_jobItem():
# can use job_items.append(...) instead
#job_items.insert(len(job_items), JobItem(itemListContainer, del_jobItem))
job_items.append(JobItem(itemListContainer, del_jobItem))
...
CodePudding user response:
I think, the question and the example is clear. When clicking on X a method delete_itself
of the JobItem class is called. This method should delete itself from job_items.
I think, this question is very interesting and very common. I have no answer at the moment. But I think you should consider changing the design. JobItem is a class. And a class should only do things that are relevant for that one class. A JobItem should not have any information where it is used or stored, and a JobItem should not do anything that is outside the responsibility of that class. This is called "single responsibility principle".
That means if you want the delete logic to do something with JobItem and also something with job_items (which is not in the class JobItem), then the delete logic must not be in JobItem. That been said, you should consider somehow to connect the delete buttons to something that is not in the JobItem class. Perhaps then your question is no longer relevant.
I know, this sounds like a contradiction. The idea is called "dependency inversion principle", because what you tried is to do something outside JobItem from inside JobItem. But you should rather do something in JobItem from outside.
Another important idea is the "Hollywood principle". This principle means "We call you." Applied to your code: JobItem does not call anything outside its own class. Instead JobItem is called (from outside).
It is not always easy to establish this mindset. But your question demonstrates that this is often helpful. You are in JobItem and you want to do something outside. So you add dependencies and references to make this possible. Your code becomes complicated. Everything depends on everything and you finally may loose control of it. All this does not happen when you have the right starting point outside.
EDIT: After you have improved your example code, I have corrected this comment.
To give you a concrete answer to your question: Instead of doing this:
self.x = ttk.Button(self.frame, text="X", command=self.delete_itself)
Try to do this:
self.x = ttk.Button(self.frame, text="X", command=del_jobItem)
And you will see your question is no longer of interest. You only have to find out how can del_jobItem know for what JobItem it has been called. That is probably another question for StackOverflow.