I've had a lot of issues with this application because I am simply not good enough yet, but I am almost done with it and just want to finish it so I can move on to some slightly lower level projects.
It is a tkinter to-do application.
You can add a Task to a listbox
For every Task
, there are some associated attributes, among others: ````self.value = vand
self.connectivity = c. The hierarchy of the tasks displayed in the listbox is determined by the value of
val_var``` (e.g. the higher the value the higher on the list it will be displayed).
The Task and the associated attributes are determined by the user's input when one creates another task.
The Task
is appended to a list task_list
and after the user has added more than 1 task to the list, the next time one adds a task one will have the option to check existing tasks that it is connected with somehow.
The list is sorted so the task with the highest value (val_var
) is displayed at the top of the Listbox and the task with the lowest value is displayed at the bottom of the Listbox.
You can "Save tasks" and then launch the application at a later time where you can then "Load tasks".
Issue 1:
After loading tasks from a saved .dat file, it displays in the Listbox in the order it was saved in. However, if you now want to add another task at least two undesirable things happen:
I am somehow interested in being able to load the Tasks
instances from the .dat file and then append them to the task_list so they are a part of the current session/instance of the application, but I don't know how one might do that.
Issue 2:
On a given session where tasks have been added to the Listbox, they can be deleted from the listbox using the "Delete task" button. The selected task in the Listbox is deleted, but it is not the same task that is deleted from the task_list
.
To test what I mean by this one can add a couple of tasks to the Listbox and then delete one after doing so. Notice upon trying to create yet another new task that the one just deleted from the Listbox will still be shown as a checkbutton - however, another task that wasn't just deleted has now vanished as a checkbutton.
Any help with these issues will be sincerely appreciated.
Here's the code:
from tkinter import Tk, Frame, Button, Entry, Label, OptionMenu, Toplevel, StringVar, Checkbutton, DoubleVar
import tkinter.messagebox
import pickle
root = Tk()
task_list = []
class Task:
def __init__(self, n, i, h, v, c):
self.name = n
self.impact = i
self.hours = h
self.value = v
self.connectivity = c
def open_add_task():
taskwin = Toplevel(root)
#NAME
titlelabel = Label(taskwin, text='Title task concisely:').grid(column=1, row=0)
name_entry = Entry(taskwin, width=40, justify='center')
name_entry.grid(column=1, row=1)
#IMPACT
impactlabel = Label(taskwin, text='Impact').grid(column=1, row=2)
imp_var = StringVar(value=0)
OptionMenu(taskwin, imp_var, *range(0, 10+1)).grid(column=1, row=3, sticky='ns')
#HOURS(required)
hourlabel = Label(taskwin, text='Whole hours \n required').grid(column=1, row=16)
hour_entry = Entry(taskwin, width=4, justify='center')
hour_entry.grid(column=1, row=17)
#CONNECTIVITY
C_lab = Label(taskwin,text="Connectivity to other tasks").grid(column=1, row=18)
placement=19
vars = [] # list to hold the DoubleVar used by Checkbutton
for task in task_list:
# add a DoubleVar to the list
vars.append(DoubleVar())
# use the task.value as the "onvalue" option
Checkbutton(taskwin, text=task.name, variable=vars[-1], onvalue=task.value, offvalue=0).grid(column=1, row=placement, sticky="w")
placement+=1
def add_task():
if name_entry.get() != '': # If textbox inputfield is NOT empty do this
#CONNECTIVITY
connectivity = sum(v.get() for v in vars)/10 +1 #if no connectivity the rest below is multiplied by 1
#VALUE
val_var = ((((int(imp_var.get())/5) + 1) * (connectivity)+(float(hour_entry.get())/10))) #-(float(hour_entry.get())/20) #-hours fra højere rangerende opgaver skal minusses urgency_var # c = 1+task1(int(imp_var.get())+(int(man_var))+task2(repeat)+task3(repeat)
task_list.append(Task(name_entry.get(), imp_var.get(), hour_entry.get(), val_var, connectivity))
reload()
taskwin.destroy()
else:
tkinter.messagebox.showwarning(title='Whoops', message='You must enter a task')
Add_button = Button(taskwin, text='Add', command=add_task).grid(column=1, row=placement, sticky="ew")
placement+=1
def reload():
task_list.sort(key=lambda a: a.value, reverse=True)
listbox_tasks.delete(0, tkinter.END)
for x in task_list:
listbox_tasks.insert(tkinter.END, x.name)
def delete_task():
try:
task_index = listbox_tasks.curselection()[0]
listbox_tasks.delete(task_index)
tasks = listbox_tasks.get(0, listbox_tasks.size())
pickle.dump(tasks, open('Todo.dat', 'wb'))
del task_list[0]
except:
tkinter.messagebox.showwarning(title='Error', message='You must select a task to delete')
def save_tasks():
tasks = listbox_tasks.get(0, listbox_tasks.size())
pickle.dump(tasks, open('Todo.dat', 'wb'))
def load_tasks():
try:
tasks = pickle.load(open('Todo.dat', 'rb'))
listbox_tasks.delete(0, tkinter.END)
for task in tasks:
listbox_tasks.insert(tkinter.END, task)
except:
tkinter.messagebox.showwarning(title='Error', message='You have no tasks')
# Create UI
your_tasks_label = Label(root, text='Your tasks:', font=('roboto',11, 'bold'), justify='center')
your_tasks_label.pack(pady=5)
scrollbar_tasks = tkinter.Scrollbar(root)
scrollbar_tasks.pack(side=tkinter.RIGHT, fill=tkinter.Y)
listbox_tasks = tkinter.Listbox(root, height=10, width=45, font=('', 11, 'bold'), fg=('grey'), justify='center') # tkinter.Listbox(where it should go, height=x, width=xx)
listbox_tasks.pack(padx=5, pady=5)
listbox_tasks.config(yscrollcommand=scrollbar_tasks.set)
scrollbar_tasks.config(command=listbox_tasks.yview)
#BUTTONS
New_Task_Button = Button(root, text='New Task', width=42, command=open_add_task)
New_Task_Button.pack()
button_delete_task = Button(root, text='Delete task', width=42, command=delete_task)
button_delete_task.pack()
button_save_tasks = Button(root, text='Save tasks', width=42, command=save_tasks)
button_save_tasks.pack()
button_load_tasks = Button(root, text='Load tasks', width=42, command=load_tasks)
button_load_tasks.pack(pady=5)
root.mainloop()
Your problem is fairly simple. You need to save the objects of the Task
class instead of saving the strings present inside the Listbox.
That said you should never give bare except clause like the one you did, always specify the exception you want to catch. You will find it hard to find the exact problem if you don't.
For example In this block of your code:
try:
tasks = pickle.load(open('Todo.dat', 'rb'))
listbox_tasks.delete(0, tkinter.END)
for task in tasks:
listbox_tasks.insert(tkinter.END, task)
except:
tkinter.messagebox.showwarning(title='Error', message='You have no tasks')
The exception here occurs when the file is not found. But now what if there is an empty file and if everything goes well and no exceptions are raised, the message will not be displayed even if there is no task in the file. A more appropriate thing would be to check if there are any contents in the file and then show the message.
I also often see you rewriting things. For example here:
def delete_task():
...
tasks = listbox_tasks.get(0, listbox_tasks.size())
pickle.dump(tasks, open('Todo.dat', 'wb'))
...
Do you really need to write the same thing that you have written under the save
function?
Here is your corrected code:
...
task_list = []
class Task:
def __init__(self, n, i, h, v, c):
self.name = n
self.impact = i
self.hours = h
self.value = v
self.connectivity = c
...
def reload():
task_list.sort(key=lambda a: a.value, reverse=True)
listbox_tasks.delete(0, tkinter.END)
for x in task_list:
listbox_tasks.insert(tkinter.END, x.name)
def delete_task():
try:
task_index = listbox_tasks.curselection()[0]
listbox_tasks.delete(task_index)
task_list.pop(task_index)
save_tasks()
except IndexError:
tkinter.messagebox.showwarning(title='Error', message='You must select a task to delete')
def save_tasks():
with open('Todo.dat', 'wb') as pk:
pickle.dump(task_list, pk)
def load_tasks():
global task_list
try:
with open('Todo.dat', 'rb') as pk:
task_list = list(pickle.load(pk))
reload()
except Exception as e: # FileNotFound Error
print(e)
tkinter.messagebox.showwarning(title='Error', message='You have no tasks')
# Create UI
your_tasks_label = Label(root, text='Your tasks:', font=('roboto',11, 'bold'), justify='center')
your_tasks_label.pack(pady=5)
...