Python tkinter generating buttons corresponding functions in a loop - python

I'm working on a tkinter database program for landscape architects to assist with selecting plants.
I have a search function which retrieves some entries with a for loop based on some user inputs, namely checkboxes for things like sun or shade (these variables are stored in search_current).
I'm trying to make delete buttons appear next to database entries, but I cannot make the button actually delete the corresponding entry. Each button will just delete the entry for the last result in the loop.
I know the problem is my delete function is being redefined in each iteration to the latest value in the loop, but I'm not sure how to approach the problem.
Here's the code.
def search():
for widgets in result_frame.winfo_children():
widgets.destroy()
conn = sqlite3.connect('plant_records.db')
c = conn.cursor()
c.execute("SELECT *, oid FROM plants")
records = c.fetchall()
for record in records:
countVar = 0
for i in search_current:
if record[i] == 1:
countVar += 1
if countVar == len(search_current):
result = tk.Frame(result_frame, bg='white', highlightbackground="Azure2", highlightthickness=1,
height=25, width=500)
result.pack()
result.grid_propagate(False)
result_name = tk.Label(result, bg='white', text=record[0])
result_name.grid(row=0, column=0)
del_button = tk.Label(result, text="Delete", bg="White", fg='Blue', cursor="hand2")
del_button.bind("<Button-1>", lambda del_button=del_button: delete())
del_button.grid(row=0, column=1)
n = record[-1]
def delete():
b = str(n)
conn = sqlite3.connect('plant_records.db')
c = conn.cursor()
c.execute('DELETE FROM plants WHERE oid = ' + b)
conn.commit()
conn.close()
conn.commit()
conn.close()
Please help, I'm quite new to programming and I've been stuck on this one for about a week, I've been reading through similar questions and answers and I can't get my head around it.
Without really understanding it, I've been trying to appropriate the solutions for my purpose, and that hasn't worked out.

Related

How can I get the value of a row in a column in SQLite 3 table with Tkinter Listbox widget?

I have Python program connected to SQLite 3 database with Tkinter on the frontend. My database table (subjectlist) consists of three columns: [id (unique interger), subject (text), serial (unique integer)]. Here is my program:
import sqlite3
from tkinter import *
conn = sqlite3.connect('database.db')
c = conn.cursor()
c.execute('SELECT COUNT() FROM subjectlist')
number = (c.fetchone()[0])
c.execute('SELECT * FROM subjectlist ORDER BY serial')
data = c.fetchall()
c.close
conn.close()
root = Tk()
listbox = Listbox(root)
listbox.pack()
for i in range(number):
listbox.insert(END, data[i][1])
def get_serial():
print(listbox.get(listbox.curselection()))
btn = Button(root, text="Show serial", command=lambda: get_serial())
btn.pack()
mainloop()
Currently at runtime when I click item on listbox (witch basically shows all subject column values) and then press Tkinter button I get the subject I clicked. Instead I want to get serial corresponding to the subject. Notice that subject column may have same value on two or more different rows. How would I go about achieving this?
Here is just one more example; If I have this table:
I want the GUI to show first all of the subjects in the listbox. Then I click "Cats" (on the the id row 3) and then I click the button, I want the program to print me serial 4.
Well, this should also be easy. But since there is a database in your code that I can't currently test this with it, I made some assumptions. Try this out:
def get_letter():
conn = sqlite3.connect('database.db')
c = conn.cursor()
ids = listbox.curselection()[0]+1 # Getting the index number starting from 1
c.execute('SELECT * FROM subjectlist WHERE `id`=?;',(ids,)) # Searching the database for items with selected items index
rows = c.fetchall()
print(rows[0]) # Print the first and hopefully the only item in the list
conn.close() # Close the connection
This should print the rows which corresponds to the id, and since usually id is a unique number, it will only print out one row. I'm also assuming that you have both the database and the listbox in the same order or this might not work.
You can use the index of the item inside listbox to get the serial from data:
import sqlite3
from tkinter import *
conn = sqlite3.connect('database.db')
c = conn.cursor()
c.execute('SELECT * FROM subjectlist ORDER BY serial')
data = c.fetchall()
c.close()
conn.close()
root = Tk()
listbox = Listbox(root)
listbox.pack()
for rec in data:
listbox.insert(END, rec[1])
def get_serial():
selected = listbox.curselection()
if selected:
idx = selected[0]
print(data[idx][2]) # print serial of selected item
btn = Button(root, text="Show serial", command=get_serial)
btn.pack()
mainloop()
Note that you should make sure data and listbox are synchronised.

Having problems pulling out check box values in a loop

I want to make a food selector page where food items being stored in a database are being printed out with check boxes but I'm having problems getting the value from the check boxes
def foodSelector():
editor = Tk()
editor.title('Food Items')
editor.geometry("400x400")
conn = sqlite3.connect('food.db')
c = conn.cursor()
c.execute("SELECT *, oid FROM foods")
records = c.fetchall()
def show():
myLabel = Label(editor, text=record).pack()
v = []
for i, record in enumerate(records):
v.append(IntVar(value=1))
query = Checkbutton(editor, text=record, variable = v[i])#record
query.pack()
myButton = Button(editor, text="Show Selection", command=show).pack()
Posting as an answer as its too big for comments:
from tkinter import *
editor = Tk()
editor.title('Food Items')
editor.geometry("400x400")
records = [(1,2,3,4),(3,2,1,3),(12,31,5,12)]
def show(rec):
global items
items = []
for i, record in enumerate(records):
if v[i].get()==1:
items.append(record)
def final():
Label(editor, text=items).pack()
v = []
for i, record in enumerate(records):
v.append(IntVar(value=0))
query = Checkbutton(editor, text=record, variable = v[i],command=lambda i=record:show(i))#record
query.pack()
myButton = Button(editor, text="Show Selection", command=final).pack()
editor.mainloop()
Is this what you wanted? I've used a random list of tuple here to mimic your records fetched from database. Keep in mind to not use the button as it will give the error.
Anyway for small parts of explanation, what lambda i=record:show(i) does is that, it holds the value of record(while iterating) in a parameter which is then passed to show()(when clicked on checkbutton) and then that is used in show(records) to put the text rec on the label, with text=rec.
In function show, it take variable record as label text, so it always show last value at end of for loop.
Example for show,
# records = ['Bread', 'Beef', 'Pork']
def show():
items = [record for i, record in enumerate(records) if v[i].get()==1]
text = 'Selected items: ' + ','.join(items) if items else 'No items selected !'
myLabel = Label(editor, text=text).pack()

Create different entries each loop python tkinter

I created a function to create entry boxes (tkinter) but I want to be able to access the data in the entry boxes after. Therefore, I can't call all the boxes the same name e.g. Entry1=Entry(Solo) - I have to have Entry1, Entry2 etc...
I thought that I might be able to store the Entry Names in an array but it doesn't work. I can't just store the data in a list because when creating the entry boxes there is no data to store yet.
[Update] I tried to store an entry box in an list but it's not supported.
TypeError: 'set' object does not support item assignment
This is a snippet of code from my program (the code works without the lines with inputlist[i] on it):
StudentsArray1 = c.fetchall()
c.execute('SELECT Count(*) from students WHERE solo = 1;')
x = c.fetchone()[0]
a = 0
rownum = 1
inputlist = []
for i in range(0, x):
label1 = Label(Solo, text=StudentsArray1[a][0], font=font4, bg="white")
label1.grid(row=rownum, column=1)
label2 = Label(Solo, text=StudentsArray1[a][1], font=font4, bg="white")
label2.grid(row=rownum, column=2)
inputlist[i] = Entry(Solo)
inputlist[i].grid(row=rownum, column=2)
rownum = rownum + 1
a = a + 1

How do I apply selected data entry to my to sql update function?

I am able to access the database and populate the listbox but unable to conduct a search based on text in the Entry String.
def find_roster(num_input):
global cursor
cursor.execute("""SELECT num, firstname, surname, duty FROM active WHERE num='%s'""" %(num_input))
rows = cursor.fetchall()
dbi.close()
for results in rows:
rosterList.insert("end", results)
return rows
numLabel=Label(root, text="Employee #")
numLabel.grid(row=0,column=0)
findButt=Button(root, text="Find", width=12, command=find_roster)
findButt.grid(row=1, column=5)
num_input=StringVar()
num_input=Entry(root,textvariable=num_input)
num_input.grid(row=0,column=1)
I have selected the specific syntax
Here is the error: TypeError: find_roster() missing 1 required positional argument: 'num_input'
I appreciate any direction.
Simply use lambda for the Tkinter command callback and pass in num_input value as parameter. Also, adjust your database connection and cursor objects to work with your process and use SQL parameterization:
dbi = pymysql.connect(...) # OUTSIDE OF FUNCTION
def find_roster(n):
cursor = dbi.cursor()
cursor.execute("SELECT num, firstname, surname, duty FROM active WHERE num = %s", (n,))
rows = cursor.fetchall()
for results in rows:
rosterList.insert("end", results)
cursor.close()
return rosterList
#... other code
findButt=Button(root, text="Find", width=12, command=lambda: find_roster(num_input))
dbi.close() # OUTSIDE OF FUNCTION
I solved this myself (late update). This issue was simply resolved by adding
def find_roster(n):
n=num_input.get()
Had nothing to do with DB query just simple syntax omission..

Python Tkinter: Delete label not working

I have a GUI that contains all the database records. Upon clicking the "search" button, I want all the records to be cleared from the screen, and the records relevant to the user's search to appear. I am trying to reference my labels and call the "destroy" method like this: a.destroy() but the program is not recognizing these labels as widgets.
class DBViewer:
def __init__(self, rows):
# Display all records
row_for_records = 2
for record in rows:
a = tkinter.Label(self.main_window, text=record[0])
a.grid(row=row_for_records, column=0)
b = tkinter.Label(self.main_window, text=record[1])
b.grid(row=row_for_records, column=1)
# More labels here
self.display_rows(rows)
tkinter.mainloop()
def display_rows(self, rows):
# Where I'm having trouble
def main():
global db
try:
dbname = 'books.db'
if os.path.exists(dbname):
db = sqlite3.connect(dbname)
cursor = db.cursor()
sql = 'SELECT * FROM BOOKS'
cursor.execute(sql)
rows = cursor.fetchall()
DBViewer(rows)
db.close()
Edit: Separated widget assignment and grid but still facing have the same problem. Anyone want to help out a programming newbie?
In order to delete something, you must have a reference to it. You aren't saving references to the widgets you are creating. Try saving them to a list or dictionary.
When creating them:
...
self.labels = []
for record in rows:
a = Tkinter.Label(...)
self.labels.append(a)
...
When you want to delete them:
for label in self.labels:
label.destroy()

Categories

Resources