Search code examples
python-3.xstringvariablestkinterlistbox

Python Tkinter: Create a variable for each string inside a listbox?


I'm (almost) a poor programmer-virgin so please go easy on me.

This is my second attempt at making a program and this is turning out to be a bit more than I can navigate, I am afraid. I ask for your help after a long time of trying to solve this.

I am basically making a ToDo-list but wanted it to have some more functionality than just being a boring list.

How I imagine it in my head is that the user adds a task into an entry widget, which would then be displayed in a Listbox. Each string in the Listbox would then have a value associated with it (which I need to some calculations for the functionality I want the program to have). So what I think I kind of want is every string in the Listbox to be made a variable and then associated with that variable I want a value.

I will try to show you:

Here I am adding the string that I want to become a new variable

Adding the string

Then I specify a number from a drop down menu. I want this number to be the value of the variable/string from previous step.

Value that must be designated to the variable/string

I really hope one of you can lead me in the right direction in a way that (preferably) doesn't require me to change things up too much. Things are still very slippery to me and it is already fairly hard for me to navigate the code. The purpose is simply that I want to do some calculations with the (hopefully) soon-to-be values associated with each task. Thanks in advance if any of you dare!

The associated code is here:

import tkinter.messagebox # Import the messagebox module
import pickle # Module to save to .dat
import tkinter as tk

root = tk.Tk() #
root.title('SmaToDo') # Name of the program/window


def new_task():
    global entry_task
    global task_window
    task_window = Toplevel(root)
    task_window.title('Add a new task')
    task_label = tk.Label(task_window, text = 'Title your task concisely:', justify='center')
    task_label.pack()
    # Entry for tasks in new window
    entry_task = tkinter.Entry(task_window, width=50, justify='center')
    entry_task.pack()
    # Add task button in new window
    button_add_task = tkinter.Button(task_window, text='Add task', width=42, command=lambda: [add_task(), impact()])
    button_add_task.pack()

def add_task():
    global task
    global impact_window
    task = entry_task.get() # we get the task from entry_task and we get the input from the entry_task type-field with .get()
    if task != '': # If textbox inputfield is NOT empty do this:
        listbox_tasks.insert(tkinter.END, task)
        entry_task.delete(0, tkinter.END) # Slet hvad der står i inputfeltet fra første bogstav til sidste (0, tkinter.END)
        task_window.destroy()
    else:
        tkinter.messagebox.showwarning(title='Whoops', message='You must enter a task')
        task_window.destroy()


def delete_task():
    try:
        task_index = listbox_tasks.curselection()[0]
        listbox_tasks.delete(task_index)
    except:
        tkinter.messagebox.showwarning(title='Oops', message='You must select a task to delete')

def save_tasks():
    tasks = listbox_tasks.get(0, listbox_tasks.size())
    pickle.dump(tasks, open('tasks.dat', 'wb'))

def prioritize_tasks():
    pass



# Create UI
frame_tasks = tkinter.Frame(root)
frame_tasks.pack()

scrollbar_tasks = tkinter.Scrollbar(frame_tasks)
scrollbar_tasks.pack(side=tkinter.RIGHT, fill=tkinter.Y)

listbox_tasks = tkinter.Listbox(frame_tasks, height=10, width=50, justify='center') # tkinter.Listbox(where it should go, height=x, width=xx)
listbox_tasks.pack()

listbox_tasks.config(yscrollcommand=scrollbar_tasks.set)
scrollbar_tasks.config(command=listbox_tasks.yview)


try:
    tasks = pickle.load(open('tasks.dat', 'rb'))
    listbox_tasks.delete(0, tkinter.END)
    for task in tasks:
        listbox_tasks.insert(tkinter.END, task)
except:
    tkinter.messagebox.showwarning(title='Phew', message='You have no tasks')

# Add task button
button_new_task = tkinter.Button(root, text='New task', width=42, command=new_task)
button_new_task.pack()


button_delete_task = tkinter.Button(root, text='Delete task', width=42, command=delete_task)
button_delete_task.pack()


button_save_tasks = tkinter.Button(root, text='Save tasks', width=42, command=save_tasks)
button_save_tasks.pack()

button_prioritize_tasks = tkinter.Button(root, text='Prioritize', width=42, command=prioritize_tasks)
button_prioritize_tasks.pack()

root.mainloop() 


Solution

  • The simple way is to add another list to store the impact values. You need to synchronize the task list and the impact list.

    Below is the modified code:

    import pickle # Module to save to .dat
    import tkinter as tk
    from tkinter import messagebox
    import random
    
    TASKS_FILE = "tasks.dat"
    task_impacts = []  # store impact of tasks
    
    root = tk.Tk() #
    root.title('SmaToDo') # Name of the program/window
    
    def impact():
        # assign random impact to new task
        task_impacts.append(random.randint(1, 11))    
    
    def new_task():
        def add_task():
            task = entry_task.get().strip()
            if task:
                listbox_tasks.insert(tk.END, task)
                impact() # get the impact of the task
            else:
                messagebox.showwarning(title='Whoops', message='You must enter a task')
            task_window.destroy()
        
        task_window = tk.Toplevel(root)
        task_window.title('Add a new task')
        task_label = tk.Label(task_window, text = 'Title your task concisely:', justify='center')
        task_label.pack()
        # Entry for tasks in new window
        entry_task = tk.Entry(task_window, width=50, justify='center')
        entry_task.pack()
        # Add task button in new window
        button_add_task = tk.Button(task_window, text='Add task', width=42, command=add_task)
        button_add_task.pack()
    
    def delete_task():
        try:
            task_index = listbox_tasks.curselection()[0]
            listbox_tasks.delete(task_index)
            task_impacts.pop(task_index) # remove corresponding impact value as well
        except:
            messagebox.showwarning(title='Oops', message='You must select a task to delete')
    
    def load_tasks():
        try:
            with open(TASKS_FILE, 'rb') as f:
                tasks = pickle.load(f)
            listbox_tasks.delete(0, tk.END)
            task_impacts.clear()
            for task, impact in tasks:
                listbox_tasks.insert(tk.END, task)
                task_impacts.append(impact)
        except:
            messagebox.showwarning(title='Phew', message='You have no tasks')
    
    def save_tasks():
        tasks = zip(listbox_tasks.get(0, tk.END), task_impacts)
        with open(TASKS_FILE, "wb") as f:
            pickle.dump(tasks, f)
    
    def prioritize_tasks():
        print(list(zip(listbox_tasks.get(0, tk.END), task_impacts)))
    
    
    # Create UI
    frame_tasks = tk.Frame(root)
    frame_tasks.pack()
    
    scrollbar_tasks = tk.Scrollbar(frame_tasks)
    scrollbar_tasks.pack(side=tk.RIGHT, fill=tk.Y)
    
    listbox_tasks = tk.Listbox(frame_tasks, height=10, width=50, justify='center') # tkinter.Listbox(where it should go, height=x, width=xx)
    listbox_tasks.pack()
    
    listbox_tasks.config(yscrollcommand=scrollbar_tasks.set)
    scrollbar_tasks.config(command=listbox_tasks.yview)
    
    # Add task button
    button_new_task = tk.Button(root, text='New task', width=42, command=new_task)
    button_new_task.pack()
    
    button_delete_task = tk.Button(root, text='Delete task', width=42, command=delete_task)
    button_delete_task.pack()
    
    button_save_tasks = tk.Button(root, text='Save tasks', width=42, command=save_tasks)
    button_save_tasks.pack()
    
    button_prioritize_tasks = tk.Button(root, text='Prioritize', width=42, command=prioritize_tasks)
    button_prioritize_tasks.pack()
    
    load_tasks()
    
    root.mainloop() 
    

    Note that I have:

    • moved the add_task() function inside new_task() function in order to not using global variables
    • created the missing impact() function to assign a random impact value to the new task
    • moved the loading of saved tasks in a function load_tasks()

    Better suggestion is to use ttk.Treeview() instead of tk.Listbox() because you can associate the impact value to the task using tags or text option of each record in the Treeview.