0

My code:

# -*- coding: utf-8 -*-
"""
Created on Sun Jan 22 14:47:36 2017

@author: Jose Chong
"""
import json
import tkinter

master = tkinter.Tk()
master.title("To-Do List (with saving!)")
master.geometry("300x300")

masterFrame = tkinter.Frame(master)

masterFrame.pack(fill=tkinter.X)

checkboxArea = tkinter.Frame(masterFrame, height=26)

checkboxArea.pack(fill=tkinter.X)

inputStuff = tkinter.Frame(masterFrame)

checkboxList = []

with open('toDoListSaveFile.json') as infile:    
    checkboxList = json.load(infile)

for savedCheckbox in checkboxList:
    n = savedCheckbox
    def destroyCheckbox():
        checkboxRow.destroy()
        del checkboxList[checkboxList.index(str(savedCheckbox))]
    checkboxRow = tkinter.Frame(checkboxArea)
    checkboxRow.pack(fill=tkinter.X)
    checkbox1 = tkinter.Checkbutton(checkboxRow, text = n)
    checkbox1.pack(side=tkinter.LEFT)
    deleteItem = tkinter.Button(checkboxRow, text = "x", command=destroyCheckbox, bg="red", fg="white", activebackground="white", activeforeground="red")
    deleteItem.pack(side=tkinter.RIGHT)

def drawCheckbox():
    newCheckboxInput = entry.get()
    def destroyCheckbox():
        checkboxRow.destroy()
        del checkboxList[checkboxList.index(newCheckboxInput)]
    checkboxList.append(newCheckboxInput)
    entry.delete(0,tkinter.END)
    checkboxRow = tkinter.Frame(checkboxArea)
    checkboxRow.pack(fill=tkinter.X)
    checkbox1 = tkinter.Checkbutton(checkboxRow, text = checkboxList[-1])
    checkbox1.pack(side=tkinter.LEFT)
    deleteItem = tkinter.Button(checkboxRow, text = "x", command=destroyCheckbox, bg="red", fg="white", activebackground="white", activeforeground="red")
    deleteItem.pack(side=tkinter.RIGHT)

def createInputStuff():
    paddingFrame = tkinter.Frame(inputStuff, height=5)
    paddingFrame.pack(fill=tkinter.X)
    buttonDone.pack()
    inputStuff.pack()
    buttonAdd.pack_forget()
    master.bind('<Return>', lambda event: drawCheckbox())

def removeInputStuff():
    inputStuff.pack_forget()
    buttonAdd.pack()
    buttonDone.pack_forget()
    master.unbind('<Return>')


buttonDone = tkinter.Button(inputStuff, text = "Close Input", command=removeInputStuff)


buttonAdd = tkinter.Button(masterFrame, text="Add Item", command=createInputStuff)
buttonAdd.pack()


topInput = tkinter.Frame(inputStuff)
bottomInput = tkinter.Frame(inputStuff)

topInput.pack()
bottomInput.pack()

prompt = tkinter.Label(topInput, text="What do you want your checkbox to be for?")
prompt.pack()
entry = tkinter.Entry(bottomInput, bd=3)
entry.pack(side=tkinter.LEFT)
buttonConfirm = tkinter.Button(bottomInput, text="Confirm", command=drawCheckbox)
buttonConfirm.pack(side=tkinter.LEFT)

master.mainloop()

with open("toDoListSaveFile.json", 'w') as outfile:
    json.dump(checkboxList, outfile)

The relevant bit of my code is the for savedCheckbox in checkboxList: bit (I think), so it would be a good idea to read through there, that's probably where the issue is.

The issue is to do with the loaded checkboxes from the save file, or rather their delete buttons. New checkboxes have delete buttons that work just fine, but the ones that have been loaded from the save file using for savedCheckbox in checkboxList: have delete buttons that malfunction. What I mean by that is:

They all delete the last loaded item (from the save file). So if my save file was a list of four checkboxes ["f", "a", "c", "e"], every loaded delete button would delete "e". If you click the delete button next to "c", too bad, you're deleting "e". And if I try to delete anything after "e" is gone, it gives me the error "ValueError: 'e' is not in list". They also don't destroy checkboxRow if the last loaded checkbox has already been deleted, I'm not sure if that's a side effect of the checkbox not being deleted or if I messed that up too.

How do I make my delete buttons work properly and delete the checkboxes properly? Proper deletion is:

Checkbox's data removed from the JSON file

The entire checkboxRow the checkbox is in should be removed too, taking the checkbox, text next to the checkbox, and delete button with it.

For what "proper" deletion looks like take a look at drawCheckbox(), that's where the working deletion boxes are made.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153

1 Answers1

0

That's the usual problem of functions defined inside for loops.

def destroyCheckbox():
        checkboxRow.destroy()
        del checkboxList[checkboxList.index(str(savedCheckbox))]

destroyCheckbox destroys checkboxRow and removes savedCheckbox from the list, but at the end of the for loop, checkboxRow is the last row and savedCheckbox the last checkbox, so all the button commands do the same thing: remove the last item.

To solve that problem, I defined the function destroyCheckbox outside the for loop with two arguments: the savedCheckbox and the row. Then I pass the command to the buttons inside the for loop using lambda and default arguments (see How to understand closure in a lambda?).

def destroyCheckbox(checkbox, row):
    row.destroy()
    checkboxList.remove(str(checkbox))

for savedCheckbox in checkboxList:
    checkboxRow = tkinter.Frame(checkboxArea)
    checkboxRow.pack(fill=tkinter.X)
    checkbox1 = tkinter.Checkbutton(checkboxRow, text=savedCheckbox)
    checkbox1.pack(side=tkinter.LEFT)
    deleteItem = tkinter.Button(checkboxRow, text="x", bg="red", fg="white",
                                activebackground="white", activeforeground="red",
                                command=lambda c=savedCheckbox, r=checkboxRow: destroyCheckbox(c, r))
    deleteItem.pack(side=tkinter.RIGHT)
Community
  • 1
  • 1
j_4321
  • 15,431
  • 3
  • 34
  • 61
  • Thanks for the solution, it worked! What I don't get is how destroyCheckbox() knows that "checkbox" refers to the loaded checkbox text and that "row" refers to checkboxRow. Could you explain it? – Jose Dzireh Chong Jan 26 '17 at 10:08
  • Actually I think you assigned those in the lambda. Thanks for the help! – Jose Dzireh Chong Jan 26 '17 at 10:09
  • Exact, I assigned checkbox and row to the right objects in the lambda when I pass the command to the button. – j_4321 Jan 26 '17 at 10:13