I apologize upfront if there is another discussion that already addresses this issue, but I could not find anything. I am new to Python (and for that matter programming other than a little bit of Pascal back in the 90's).
I am building a GUI with tk Entry boxes for a user to enter values, which is then stored in a sqlite db. I would like the user to be able to click on the values from one of the fields in a listbox, which would then re-populate the tk Entry boxes with the values for all fields for the selected record. I have been able to make this work, but it only works on the second click as I bind the list box to the function to populate the tk Entry boxes on Button-1. The first click generates the following error, which I have seen references to in other questions, but I cannot translate those answers to my situation:
Error:
Traceback (most recent call last): File "...\Python\Python38-32\lib\tkinter__init__.py", line 1883, in call return self.func(*args) File "fe.py", line 108, in selectitem selecteditem.append(lb1.get(ndex)) File "...\Python\Python38-32\lib\tkinter__init__.py", line 3182, in get return self.tk.call(self._w, 'get', first) _tkinter.TclError: bad listbox index "": must be active, anchor, end, @x,y, or a number
Here is example code that replicates the error - to use, first add a a couple of values through the Setup -> Items menu by filling in the VAL and REF boxes and clicking the Add Item button. Then click on one of the items in the list box. The first click should generate the error above. When you click a second time the Entry boxes should populate:
```
import sqlite3
import itertools
import tkinter as tk
from tkinter import ttk
INTRO_FONT = ("Arial", 72)
LARGE_FONT = ("Arial", 12)
NORMAL_FONT = ("Arial", 10)
SMALL_FONT = ("Arial", 8)
#Function to create database
def create_db():
conn = sqlite3.connect("demo.db")
cur = conn.cursor()
cur.execute("CREATE TABLE IF NOT EXISTS demotable (id INTEGER PRIMARY KEY, val TEXT, ref TEXT)")
conn.commit()
conn.close()
#Create database
create_db()
#Main Class to manage frames
class demo(tk.Tk):
def __init__(self,*args,**kwargs):
tk.Tk.__init__(self,*args,**kwargs)
container = tk.Frame(self)
container.pack(side="top",fill="both",expand=True)
container.grid_rowconfigure(0,weight=1)
container.grid_columnconfigure(0,weight=1)
menubar = tk.Menu(container)
filemenu = tk.Menu(menubar,tearoff=0)
filemenu.add_command(label="Exit", command=quit)
menubar.add_cascade(label="File", menu=filemenu)
setupmenu = tk.Menu(menubar, tearoff=0)
setupmenu.add_command(label="Items",command = lambda: self.show_frame(Itemsetuppage))
menubar.add_cascade(label="Setup", menu=setupmenu)
tk.Tk.config(self, menu=menubar)
self.frames={}
for F in (Itemsetuppage,Itemsetuppage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(Itemsetuppage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
# Frame that inserts and loads values
class Itemsetuppage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
#Function to populate listbox with values
def popitemlist(self):
lb1.delete(0, tk.END)
for item in itemlist():
lb1.insert(tk.END, item)
#Function to add new item to database
def additem():
additem_db(val.get(), ref.get())
val_entry.delete(0, 'end')
ref_entry.delete(0, 'end')
popitemlist(self)
#Function used to populate tk.Entry boxes from database when item in listbox is clicked
def selectitem(event):
global selecteditem
selecteditem = []
ndex = lb1.curselection()
selecteditem.append(lb1.get(ndex))
itemquery = select_item(selecteditem)
val_entry.delete(0, 'end')
val_entry.insert(0, itemquery[1])
ref_entry.delete(0, 'end')
ref_entry.insert(0, itemquery[2])
#Function to query database for values to populate lb1
def itemlist():
conn = sqlite3.connect("demo.db")
cur = conn.cursor()
cur.execute("SELECT DISTINCT val FROM demotable")
results = cur.fetchall()
itemlist = list(itertools.chain(*results))
conn.commit()
conn.close()
return itemlist
#Function to insert values from tk.Entry boxes to database
def additem_db(val, ref):
conn = sqlite3.connect("demo.db")
cur = conn.cursor()
cur.execute("INSERT OR IGNORE INTO demotable VALUES (NULL, ?, ?)",(val,ref))
conn.commit()
conn.close()
#Function to query database for individual record to populate tk.Entry boxes when item is clicked in lb1
def select_item(val):
conn = sqlite3.connect("demo.db")
cur = conn.cursor()
cur.execute("SELECT * FROM demotable WHERE val=?",(val))
results = cur.fetchall()
itemdetail = list(itertools.chain(*results))
conn.commit()
conn.close()
return itemdetail
l1 = tk.Label(self, text="Values in database:")
l1.grid(row=0, column=0, padx=5, pady=5)
lb1 = tk.Listbox(self, selectmode=tk.SINGLE)
lb1.grid(row=1, column=0, padx=5, pady=5)
popitemlist(self)
lb1.bind("<Button-1>", selectitem)
l2 = tk.Label(self, text="Type val into entry box to store:")
l2.grid(row=0, column=1, padx=5, pady=5)
val = tk.StringVar(self)
val_entry = tk.Entry(self, textvariable=val)
val_entry.grid(row=0, column=2, padx=5, pady=5)
l2 = tk.Label(self, text="Type ref into entry box to store:")
l2.grid(row=1, column=1, padx=5, pady=5)
ref = tk.StringVar(self)
ref_entry = tk.Entry(self, textvariable=ref)
ref_entry.grid(row=1, column=2, padx=5, pady=5)
b1 = tk.Button(self, text="Add item",command=additem)
b1.grid(row=2, column=2, padx=5, pady=5)
app = demo()
app.geometry("480x240")
app.mainloop()
```
Thank you & apologies if my code offends anyone!
Not having run your code as I have no sqlite3 database I may be way off, but listbox generates a virtual event when a value is selected <<ListboxSelect>>
which you can bind to. Try replacing:
lb1.bind("<Button-1>", selectitem)
with
lb1.bind("<<ListboxSelect>>", selectitem)
Also, I think lb1.curselection()
returns a tuple..., try printing it and see what you get. In the code I checked I always do lb1.curselection()[0]
.