Reputation: 95
Thanks for your help in advance.
I am creating a GUI and one part of it has me stumped - I create a Combobox using Tkinter and the values are from a CSV file - I then allow the user to select one of the names and delete that person and this causes the line for that user in the CSV file to be removed and the dropdown is refreshed with just the remaining users - this part works fine - my issue is when I go to delete a second user - my get value for the Combobox always remains the value of the first user.
I am a python noob which you will quickly determine from my code but I have been unable to locate a solution to what I am doing wrong which I am sure is very simple.
Here is the CSV file I am reading from:
id,name,username,password,val1,val2
1,Billy,bsmith,GoodPassword,JZ4Z3ATP6,Test1
2,Amanda,asmith,GoodPassword1,NRROAZ6JP6,Test2
3,Sammy,ssmith,GoodPassword2,NRRZ3ATP6,Test3
My python code is here:
from tkinter import *
from tkinter import ttk
import tkinter as tk
from tkinter import messagebox as msg
import os.path
from csv import reader, writer
import csv
import shutil
root = Tk()
root.title('Why am I an idiot')
labelFrame3 = ttk.LabelFrame(text = "Delete Button")
labelFrame3.grid(column = 0, row = 2, padx = 20, pady = 20)
def clicking(event):
to_delete = delete_combo.get()
print("CLICKING")
print(to_delete)
def read_connections_file(conns_dict):
conns = conns_dict
with open('file.csv', 'r') as read_obj:
csv_reader = reader(read_obj)
header = next(csv_reader)
# Check file as empty
if header != None:
# Iterate over each row after the header in the csv
for row in csv_reader:
# row variable is a list that represents a row in csv
conns[row[0]] = {'name':row[1], 'username':row[2], 'password':row[3], 'val1':row[4], 'val2':row[5]}
def get_button_names():
conns = {}
button_names = []
read_connections_file(conns)
for i in conns:
name_ = conns[i]["name"]
button_names.append(name_)
return button_names
buttons1 = get_button_names()
delete_combo = ttk.Combobox(labelFrame3, value=buttons1)
delete_combo.bind("<<ComboboxSelected>>", clicking)
delete_combo.grid(row=0, column=1, columnspan=3, padx=10, pady=10)
def clicking(event):
to_delete = delete_combo.get()
print("CLICKING")
print(to_delete)
def create_delete_dropdown():
buttons2 = get_button_names()
print(buttons2)
delete_combo = ttk.Combobox(labelFrame3, value=buttons2)
delete_combo.bind("<<ComboboxSelected>>", clicking)
delete_combo.grid(row=0, column=1, columnspan=3, padx=10, pady=10)
print("FUNC")
help = delete_combo.get()
print(help)
def delete_saved_button():
original = r'file.csv'
target = r'file.bak'
shutil.copyfile(original, target)
to_delete = delete_combo.get()
print(to_delete)
answer = msg.askyesno("Delete Button", ("Are you sure you want to Delete " + to_delete + "?"))
if answer == True:
conns = []
with open('file.csv', 'r') as read_obj:
csv_reader = reader(read_obj)
header = next(csv_reader)
# Check file as empty
if header != None:
# Iterate over each row after the header in the csv
for row in csv_reader:
# row variable is a list that represents a row in csv
conns.append({'id':row[0],'name':row[1], 'username':row[2], 'password':row[3], 'val1':row[4], 'val2':row[5]})
new_list = None
for item in conns:
if item['name'] == to_delete:
new_list = item
break
new_cons = conns.copy()
new_cons.remove(new_list)
i = 1
for d in new_cons:
d['id'] = i
i += 1
msg.showinfo("Button Deleted", (to_delete + " Button Deleted"))
csv_file_name = "file.csv"
with open(csv_file_name, 'w', newline='') as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=header)
writer.writeheader()
for data in new_cons:
writer.writerow(data)
create_delete_dropdown()
# LabelFrame 3 Content
delete_label = ttk.Label(labelFrame3, text = "Delete Who?: ")
delete_label.grid(row=0, column=0)
deleteButton = tk.Button(master=labelFrame3, text="Delete", command=delete_saved_button)
deleteButton.grid(row=1, column=0)
root.mainloop()
I have left a few print statements in to try to help me work out what I am doing wrong.
If I delete Billy first he is deleted from the CSV and the dropdown which is great.
If I then select Amanda and try to delete her second I get an error - my output from this sequence including my print statements is:
CLICKING
Billy
Billy
['Amanda', 'Sammy']
FUNC
CLICKING
Billy
Billy
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\reeno\AppData\Local\Programs\Python\Python37\lib\tkinter\__init__.py", line 1705, in __call__
return self.func(*args)
File ".\fucked.py", line 90, in delete_saved_button
new_cons.remove(new_list)
ValueError: list.remove(x): x not in list
Here is the script being run:
I appreciate if you can advise what I am doing wrong or point me to a resource that might give me the answer.
Remember I am a noob but I would also love any suggestions to improve my code in general too.
Thank you for your help!!
Upvotes: 0
Views: 1522
Reputation: 142631
Your problem is local
and global
variable delete_combo
.
When you create new Combobox()
in create_delete_dropdown()
then it creates local variable delete_combo
and it assings new combobox to this local variable but it doesn't change value in global variable delete_combo
which is used by other functions - so other functions still use old Combobox()
(which is hidden behind new Combobox()
in grid()
).
You have to use global delete_combo
in function create_delete_dropdown()
to inform this function that you want to assign to external/global variable, not local one
def create_delete_dropdown():
global delete_combo # inform function that you will assign (=) to external/global variable
buttons2 = get_button_names()
print(buttons2)
delete_combo = ttk.Combobox(labelFrame3, value=buttons2)
delete_combo.bind("<<ComboboxSelected>>", clicking)
delete_combo.grid(row=0, column=1, columnspan=3, padx=10, pady=10)
print("FUNC")
help = delete_combo.get()
print(help)
But instead of creating new Combobox()
you can change values in existing Combobox()
as @TheFluffDragon9 mentioned in comment.
def create_delete_dropdown():
buttons2 = get_button_names()
print(buttons2)
delete_combo['value'] = buttons2
delete_combo.set('')
print("FUNC")
help = delete_combo.get()
print(help)
EDIT: Code with other changes.
I use DictReader
to get data in list of rows (dictionares) to keep original order of rows (older Python didn't have to keep order in dictionary)
import os.path
import csv
import shutil
import tkinter as tk # PEP8: `import *` is not preferred
from tkinter import ttk
from tkinter import messagebox as msg
# --- constants --- (UPPER_CASE_NAMES)
ORIGINAL = 'file.csv'
BACKUP = 'file.bak'
# --- function --- (lower_case_names)
def read_data():
global header # to keep in global variable
with open(ORIGINAL) as csvfile:
csv_reader = csv.DictReader(csvfile)
header = csv_reader.fieldnames
data = list(csv_reader)
#print('header:', header)
return data
def write_data(data, make_backup=True):
if make_backup:
shutil.copyfile(ORIGINAL, BACKUP)
with open(ORIGINAL, 'w', newline='') as csvfile:
csv_writer = csv.DictWriter(csvfile, fieldnames=header)
csv_writer.writeheader()
csv_writer.writerows(data)
def clicking(event):
print("CLICKING:", delete_combo.get())
print("CLICKING:", event.widget.get())
def get_column(column="name"):
data = read_data()
values = []
for row in data:
values.append(row[column])
return values
def delete_saved_button():
to_delete = delete_combo.get()
print('to_delete:', to_delete)
if not to_delete:
msg.showinfo("Button Deleted", "You have to select name.")
return
answer = msg.askyesno("Delete Button", "Are you sure you want to Delete {}?".format(to_delete))
if answer == True:
# read old data
data = read_data()
# search element to delete
number_to_delete = None
for number, row in enumerate(data):
if row['name'] == to_delete:
number_to_delete = number
break
if number_to_delete is not None: # it has to compare with None because number can be 0 and bool(0) gives also False
del data[number_to_delete]
else:
msg.showinfo("Button Deleted", "Name '{}' doesn't exist.".format(to_delete))
return
for number, row in enumerate(data, 1):
row['id'] = number
# write new data
write_data(data)
# update dropdown
delete_combo["value"] = get_column("name")
delete_combo.set("")
msg.showinfo("Button Deleted", "{} Button Deleted".format(to_delete))
# --- main --- (lower_case_names)
root = tk.Tk()
root.title('Why am I an idiot')
label_frame = ttk.LabelFrame(text="Delete Button") #
label_frame.grid(column=0, row=2, padx=20, pady=20) # PEP8: without spaces around =
delete_label = ttk.Label(label_frame, text="Delete Who?: ")
delete_label.grid(row=0, column=0)
names = get_column("name")
delete_combo = ttk.Combobox(label_frame, value=names)
delete_combo.bind("<<ComboboxSelected>>", clicking)
delete_combo.grid(row=0, column=1, columnspan=3, padx=10, pady=10)
delete_button = tk.Button(master=label_frame, text="Delete", command=delete_saved_button)
delete_button.grid(row=1, column=0)
root.mainloop()
PEP 8 -- Style Guide for Python Code
Upvotes: 1