I made a checkout function in my library management system. It aims to let the user choose books from the given treeview: I aim to get the row of data that the user clicks on and pressed "add to cart" to another window with a treeview: . Only adding the specific row of books data that they chose from the main page. Here is my treeview code:
reserve_button = Button(text="RESERVE" ,fg=WHITE, bg=NAVY_BLUE, width=20, command=reserve)
reserve_button.grid(column=5,row=5)
checkout_button = Button(text="CHECKOUT", fg=WHITE,bg="#6DA5A9",width=20,command=checkout_page)
checkout_button.grid(column=0,row=5,sticky="w")
search_button = Button(text="SEARCH", fg=WHITE,bg="pink", width=20, command=search)
search_button.grid(column=3,row=3)
add_to_cart = Button(text="ADD TO CART", fg=WHITE,bg="#7E370C", width=20,command=add_to_cart_f)
add_to_cart.grid(column=7, row=5,sticky="e")
tree = ttk.Treeview()
books_data = pandas.read_csv("List of Books - Sheet1 (3).csv")
df_column = books_data.columns.values
print(len(df_column))
print(df_column)
tree["column"] = list(books_data.columns)
tree["show"] = "headings"
vsb = ttk.Scrollbar(orient="vertical", command=tree.yview())
vsb.grid(column=8, row=4, sticky="ns")
tree.configure(yscrollcommand=vsb.set)
for column in tree['column']:
tree.heading(column,text=column)
df_rows = books_data.to_numpy().tolist()
for row in df_rows:
tree.insert("","end",values=row)
tree.grid(column=0,row=4,columnspan=8)
The short answer
I created an example project that would use this:
import tkinter as tk
import tkinter.ttk as ttk
import sys
from numpy import select
import pandas
# https://gist.githubusercontent.com/netj/8836201/raw/6f9306ad21398ea43cba4f7d537619d0e07d5ae3/iris.csv
df = pandas.read_csv('./booklist.csv')
df.columns = df.columns.str.replace('.', '', regex=False)
df.columns = df.columns.str.replace(' ', '_', regex=False)
df.head()
class main_window:
def __init__(self, root):
self.root = root
root.title("Treeview Search Example")
# Create DataFrame for this window
self.build_df = df.copy()
self.checkout_ids = []
# INITIALIZE TREEVIEW + SCROLLVIEW
self.tree = ttk.Treeview(root, columns=list(df.columns.values), show='headings')
self.tree.grid(row=1, column=0, sticky='nsew')
# https://stackoverflow.com/a/41880534/5210078
vsb = ttk.Scrollbar(root, orient="vertical", command=self.tree.yview)
vsb.grid(row=1, column=1, sticky='ns')
self.tree.configure(yscrollcommand=vsb.set)
for column in self.tree['column']:
self.tree.heading(column,text=column)
df_rows = df.to_numpy().tolist()
for row in df_rows:
if row[4] != 0:
self.tree.insert("","end",values=row)
# ADD SEARCH BOXES
search_frame = tk.Frame(root)
search_frame.grid(row=0, column=0, columnspan=2, sticky='nsew')
tk.Label(search_frame, text="TITLE:").grid(row=0, column=0)
tk.Label(search_frame, text="AUTHOR:").grid(row=0, column=2)
tk.Label(search_frame, text="IDENTIFICATION NO:").grid(row=0, column=4)
tk.Label(search_frame, text="SUBJECT CATEGORY:").grid(row=0, column=6)
# Add Search boxes
self.title_ent = tk.Entry(search_frame)
self.title_ent.grid(row=0, column=1)
self.author_ent = tk.Entry(search_frame)
self.author_ent.grid(row=0, column=3)
self.identifaction_ent = tk.Entry(search_frame)
self.identifaction_ent.grid(row=0, column=5)
self.category_ent = tk.Entry(search_frame)
self.category_ent.grid(row=0, column=7)
tk.Button(search_frame, text="Search", command=self.search).grid(row=0, column=10)
tk.Button(search_frame, text="Reseve", command=self.reserve).grid(row=0, column=11)
def search(self):
# https://stackoverflow.com/a/27068344/5210078
self.tree.delete(*self.tree.get_children())
self.build_df = df.copy()
# https://stackoverflow.com/a/56157729/5210078
entries = [
self.title_ent,
self.author_ent,
self.identifaction_ent,
self.category_ent
]
if entries[0].get():
self.build_df = self.build_df[self.build_df.TITLE.str.contains(entries[0].get())]
if entries[1].get():
self.build_df = self.build_df[self.build_df.AUTHOR.str.contains(entries[1].get())]
if entries[2].get():
self.build_df = self.build_df[self.build_df.SUBJECT_CATEGORY.str.contains(entries[2].get())]
if entries[3].get():
self.build_df = self.build_df[self.build_df.PUBLICATION_DATE == (entries[3].get())]
df_rows = self.build_df.to_numpy().tolist()
for row in df_rows:
print(row)
if row[4] != 0:
self.tree.insert("","end",values=row)
def reserve(self):
selected = self.tree.item(self.tree.focus())
if selected['values']:
# get the id
book_id = selected['values'][3]
book_id_val = df.loc[df['IDENTIFICATION_NO'] == book_id, 'BOOK_ITEM'].to_numpy().tolist()[0]
if book_id_val < 1:
return 0
else:
self.checkout_ids.append(book_id)
df.loc[df['IDENTIFICATION_NO'] == book_id, 'BOOK_ITEM'] = book_id_val - 1
self.search()
if __name__ == '__main__':
main = tk.Tk()
main_window(main)
main.mainloop()
sys.exit()
Where booklist.csv looks like the following:
TITLE,AUTHOR,PUBLICATION DATE,IDENTIFICATION NO.,BOOK ITEM,SUBJECT CATEGORY
Book 1,Author 1,1923,001/geo003/1993,4,Awesome
Book 2,Author 2,1924,001/geo003/1994,5,Awesome
Book 3,Author 3,1925,001/geo003/1995,6,Awesome
Book 4,Author 4,1926,001/geo003/1996,7,Awesome
Book 5,Author 5,1927,001/geo003/1997,8,Awesome
Book 6,Author 6,1928,001/geo003/1998,9,Awesome
Book 7,Author 7,1929,001/geo003/1999,10,Awesome
Book 8,Author 8,1930,001/geo003/2000,11,Awesome
Book 9,Author 9,1931,001/geo003/2001,12,Awesome
Book 10,Author 10,1932,001/geo003/2002,13,Awesome
The explanation
df = pandas.read_csv('./booklist.csv')
df.columns = df.columns.str.replace('.', '', regex=False)
df.columns = df.columns.str.replace(' ', '_', regex=False)
Clean-up the csv file, so there are no spaces or . in column names (pandas really doesn't like them and they are not necessary)
class main_window:
def __init__(self, root):
self.root = root
root.title("Treeview Search Example")
# Create DataFrame for this window
self.build_df = df.copy()
self.checkout_ids = []
Initialize a class main_window which contains the code you need. And create two variables build_df and checkout_ids, the checkout_ids will be a list containing your current "basket". The build_df holds a temporary copy of your DataFrame (df), which can be filtered and moved around as you like, without effecting the original DataFrame.
# INITIALIZE TREEVIEW + SCROLLVIEW
self.tree = ttk.Treeview(root, columns=list(df.columns.values), show='headings')
self.tree.grid(row=1, column=0, sticky='nsew')
# https://stackoverflow.com/a/41880534/5210078
vsb = ttk.Scrollbar(root, orient="vertical", command=self.tree.yview)
vsb.grid(row=1, column=1, sticky='ns')
self.tree.configure(yscrollcommand=vsb.set)
for column in self.tree['column']:
self.tree.heading(column,text=column)
df_rows = df.to_numpy().tolist()
for row in df_rows:
if row[4] != 0:
self.tree.insert("","end",values=row)
# ADD SEARCH BOXES
search_frame = tk.Frame(root)
search_frame.grid(row=0, column=0, columnspan=2, sticky='nsew')
tk.Label(search_frame, text="TITLE:").grid(row=0, column=0)
tk.Label(search_frame, text="AUTHOR:").grid(row=0, column=2)
tk.Label(search_frame, text="IDENTIFICATION NO:").grid(row=0, column=4)
tk.Label(search_frame, text="SUBJECT CATEGORY:").grid(row=0, column=6)
# Add Search boxes
self.title_ent = tk.Entry(search_frame)
self.title_ent.grid(row=0, column=1)
self.author_ent = tk.Entry(search_frame)
self.author_ent.grid(row=0, column=3)
self.identifaction_ent = tk.Entry(search_frame)
self.identifaction_ent.grid(row=0, column=5)
self.category_ent = tk.Entry(search_frame)
self.category_ent.grid(row=0, column=7)
tk.Button(search_frame, text="Search", command=self.search).grid(row=0, column=10)
tk.Button(search_frame, text="Reseve", command=self.reserve).grid(row=0, column=11)
Initialize the main UI, this holds things like your entry boxes. Importantly, the if statement: if row[4] != 0: is used because if there are no books available, there is no reason to display them!
def search(self):
# https://stackoverflow.com/a/27068344/5210078
self.tree.delete(*self.tree.get_children())
self.build_df = df.copy()
# https://stackoverflow.com/a/56157729/5210078
entries = [
self.title_ent,
self.author_ent,
self.identifaction_ent,
self.category_ent
]
if entries[0].get():
self.build_df = self.build_df[self.build_df.TITLE.str.contains(entries[0].get())]
if entries[1].get():
self.build_df = self.build_df[self.build_df.AUTHOR.str.contains(entries[1].get())]
if entries[2].get():
self.build_df = self.build_df[self.build_df.SUBJECT_CATEGORY.str.contains(entries[2].get())]
if entries[3].get():
self.build_df = self.build_df[self.build_df.PUBLICATION_DATE == (entries[3].get())]
df_rows = self.build_df.to_numpy().tolist()
for row in df_rows:
print(row)
if row[4] != 0:
self.tree.insert("","end",values=row)
The search system remains the same as the previous answer! Other than the if statement functionality added as seen earlier!
The reserve function
def reserve(self):
selected = self.tree.item(self.tree.focus())
if selected['values']:
# get the id
book_id = selected['values'][3]
book_id_val = df.loc[df['IDENTIFICATION_NO'] == book_id, 'BOOK_ITEM'].to_numpy().tolist()[0]
if book_id_val < 1:
return 0
else:
self.checkout_ids.append(book_id)
df.loc[df['IDENTIFICATION_NO'] == book_id, 'BOOK_ITEM'] = book_id_val - 1
self.search()
selected = self.tree.item(self.tree.focus())
Gets the currently selected item from the self.tree
if selected['values']:
If there is a selected item (stops errors when nothing is selected)
book_id = selected['values'][3]
Takes the book_id (4th column) from the selected item and stores a copy of it.
book_id_val = df.loc[df['IDENTIFICATION_NO'] == book_id, 'BOOK_ITEM'].to_numpy().tolist()[0]
Finds the book in the original DataFrame and get how many books there are of it!
if book_id_val < 1:
return 0
If there are no books available, don't do anything, (this is a fallback error-catcher because it isn't entirely necessary).
self.checkout_ids.append(book_id)
df.loc[df['IDENTIFICATION_NO'] == book_id, 'BOOK_ITEM'] = book_id_val - 1
self.search()
Store a copy of the id into the checkout_ids list
Decrease the number of available books by one
Reload the treeview!
Related
I'm currently working on a stock portfolio tracker, and I'm super confused about the way variables are passed from one frame to another. I basically have two frames so far, one is a treeview and the second is Userinputframe, a bunch of buttons and entry widgets.
My code should use those entry widgets and insert them into my treeview. However now that those are into two different frames, I cannot manage to get() the values from Userinputframe to work with my treeview, and I get the error: 'Frame' Object has no attribute 'Entry_ticker'. What am I missing here?
import tkinter
from tkinter import ttk
import datetime as dt
import yfinance as yf
import os
import csv
root_window = tkinter.Tk()
root_window.title('Python portfolio')
root_window.geometry('700x700')
# Frame functions
def call_first_frame():
second_frame.pack_forget()
first_frame.pack()
def call_second_frame():
first_frame.pack_forget()
second_frame.pack()
def call_third_frame():
second_frame.pack_forget()
def quit_program():
root_window.destroy()
# We create an csv flat file to store our values in
def create_database():
global header
header = ['Ticker', 'Volume', 'Purchased at', 'Current value']
with open('stocks.csv', 'w', encoding='UTF8', newline='') as f:
writer = csv.writer(f)
writer.writerow(header)
# we create a function to get the current share price of a ticker
def Shareprice(ticker):
ticker_yahoo = yf.Ticker(ticker)
data = ticker_yahoo.history()
return data.tail(1)['Close'].iloc[0]
# we use this to record a date of purchase
def stamp():
x = dt.datetime.now()
return x.strftime("%c")
# we fetch the price of the ticker entered
def CurrentPrice():
a=Shareprice(first_frame.Entry_ticker.get())
b=int(first_frame.Entry_volume.get())
return round(a*b,2)
# add an investment to the portfolio
def add_to_portfolio():
my_tree = ttk.Treeview(Treeviewframe, show='headings')
# Defining columns
my_tree['columns'] = ('Ticker', 'Volume', 'Purchased at', 'Current value')
# Column_formatting
my_tree.column('#0', anchor=tkinter.W, width=70, stretch=False)
my_tree.column('Ticker', anchor=tkinter.CENTER, width=50)
my_tree.column('Volume', anchor=tkinter.CENTER, width=50)
my_tree.column('Purchased at', anchor=tkinter.CENTER, width=175)
my_tree.column('Current value', anchor=tkinter.CENTER, width=150)
# Heading_formatting
my_tree.heading('#0', text='ID', anchor=tkinter.W)
my_tree.heading('Ticker', text='Ticker', anchor=tkinter.W)
my_tree.heading('Volume', text='Volume', anchor=tkinter.W)
my_tree.heading('Purchased at', text='Purchased at', anchor=tkinter.W)
my_tree.heading('Current value', text='Current value', anchor=tkinter.W)
global count
my_tree.insert(parent='', index='end', iid=count, text='',
values=(Userinputframe.Entry_ticker.get(), Userinputframe.Entry_volume.get(), stamp(), CurrentPrice()))
count += 1
# immediately after adding an investment in our treeview, we add our new investment to a our database
csvdata=[first_frame.Entry_ticker.get(), first_frame.Entry_volume.get(), stamp(), CurrentPrice()]
with open('stocks.csv', 'w', encoding='UTF8',newline='') as f:
writer = csv.writer(f)
writer.writerow(header)
with open('stocks.csv', 'a') as f:
writer = csv.writer(f)
writer.writerow(csvdata)
# We clear the entry widgets automatically when the button is pressed
first_frame.Entry_ticker.delete(0, tkinter.END)
first_frame.Entry_volume.delete(0, tkinter.END)
# remove one stock
def rmv_from_portfolio():
x = my_tree.selection()[0]
my_tree.delete(x)
# clear the portfolio (remove all stocks)
def rmv_all():
for record in my_tree.get_children():
my_tree.delete(record)
def start_tab_widgets():
# We initialize the Treeview to hold our portfolio
my_tree = ttk.Treeview(Treeviewframe, show='headings')
# Defining columns
my_tree['columns'] = ('Ticker', 'Volume', 'Purchased at', 'Current value')
# Column_formatting
my_tree.column('#0', anchor=tkinter.W, width=70, stretch=False)
my_tree.column('Ticker', anchor=tkinter.CENTER, width=50)
my_tree.column('Volume', anchor=tkinter.CENTER, width=50)
my_tree.column('Purchased at', anchor=tkinter.CENTER, width=175)
my_tree.column('Current value', anchor=tkinter.CENTER, width=150)
# Heading_formatting
my_tree.heading('#0', text='ID', anchor=tkinter.W)
my_tree.heading('Ticker', text='Ticker', anchor=tkinter.W)
my_tree.heading('Volume', text='Volume', anchor=tkinter.W)
my_tree.heading('Purchased at', text='Purchased at', anchor=tkinter.W)
my_tree.heading('Current value', text='Current value', anchor=tkinter.W)
# We find out if the user already has investments in his portfolio
path_name = 'stocks.csv'
if os.path.exists(path_name):
# then we read the data from the csv file, and make sure it appears in our treeview
old_data = []
else:
# then we create the database (CSV file)
create_database()
old_data = []
# We insert data in our portfolio
global count
count = 0
for record in old_data:
my_tree.insert(parent='', index='end', iid=count, text='', values=(record[0], record[1], record[2], record[3]))
count += 1
# widgets_user_input:Labels and entry
Lbl_ticker = tkinter.Label(Userinputframe, text='Ticker')
Lbl_ticker.grid(row=0, column=0, padx=10, pady=20, sticky='n')
Lbl_volume = tkinter.Label(Userinputframe, text='Volume')
Lbl_volume.grid(row=1, column=0, padx=10, pady=20, sticky='n')
Entry_ticker = tkinter.Entry(Userinputframe, text='Add ticker here...')
Entry_ticker.grid(row=0, column=1, pady=20, sticky='n', columnspan=2)
Entry_volume = tkinter.Entry(Userinputframe, text='Add nb of shares here...')
Entry_volume.grid(row=1, column=1, pady=20, sticky='n', columnspan=2)
# Buttons to add and remove records
Btn_add_record = tkinter.Button(Userinputframe, text='Add to portfolio', command=add_to_portfolio)
Btn_add_record.grid(row=2, column=1, pady=20, sticky='ns')
Btn_rmv_record = tkinter.Button(Userinputframe, text='Remove one selected stock', command=rmv_from_portfolio)
Btn_rmv_record.grid(row=3,column=1, pady=20, sticky='n')
Btn_rmv_all = tkinter.Button(Userinputframe, text='Clear my portfolio', command=rmv_all)
Btn_rmv_all.grid(row=4, column=1, pady=20, sticky='n')
# we pack our Tree view onto our frame
my_tree.pack(in_=Treeviewframe, pady=20, padx=0, fill='y')
# we place those two frames one after the other
Treeviewframe.pack(in_=first_frame, pady=20)
Treeviewframe['borderwidth'] = 0
Userinputframe.pack(in_=first_frame, pady=20)
Userinputframe['borderwidth'] = 0
# Buttons to leave the program
# We declare our frames
first_frame = tkinter.ttk.Frame(root_window)
first_frame.pack()
# we define two sub-frames to work into
Treeviewframe = tkinter.ttk.Frame(first_frame)
Userinputframe = tkinter.ttk.Frame(first_frame)
second_frame = tkinter.Frame(root_window)
second_frame.pack()
third_frame = tkinter.Frame(root_window)
third_frame.pack()
# We hide all frames in reverse order, but leave the first frame visible
start_tab_widgets()
'''
portfolio_widgets()
reco_widgets()
'''
# Hide all frames in reverse order, but we leave the first frame visible
third_frame.pack_forget()
second_frame.pack_forget()
# we add this program to the main loop
root_window.mainloop()
Looking at the code, the Error: Frame' Object has no attribute 'Entry_ticker' is expected.
Entry_ticker is a local variable and not part of the Userinputframe.
There are a few options you have here:
Option 1: Define your entry as global variable
entry_ticker = None
....
...
...
def start_tab_widgets():
...
...
global entry_ticker
entry_ticker = tkinter.Entry(Userinputframe, text='Add ticker here...')
entry_ticker.grid(row=0, column=1, pady=20, sticky='n', columnspan=2)
def add_to_portfolio():
...
...
#access entry_ticker
global entry_ticker
value = entry_ticker.get()
Option 2: Create a new class for your frame and define a class level variable
class UserInputFrame:
def __init__(self, parent):
self.frame = tkinter.ttk.Frame(parent)
self.entry_ticker = tkinter.Entry(self.frame, text='Add ticker here...')
self.entry_ticker.grid(row=0, column=1, pady=20, sticky='n', columnspan=2)
def start_tab_widgets():
....
....
....
# Create class instance
user_input_frame = UserInputFrame(root_window)
# Access the entry_ticker value
user_input_frame.entry_ticker.get()
Option 3: Use tkinter.StringVar()
Now, The most suitable option to access the value is to use tkinter <type>Var() in your case StringVar() can be use.
So, when you create your entry
# create the stringvar
entry_ticker_var = tkinter.StringVar()
# Use the var while createing entry (textvariable=entry_ticker_var)
entry_ticker = tkinter.Entry(self.frame, text='Add ticker here...', textvariable=entry_ticker_var)
....
....
Now, while using it just call entry_ticker.get() or if you want to update the text in your entry you can entry_ticker.set("New Value")
You can also bind trace event to this variable entry_ticker.trace_add('write', on_change). Here on_change function will be called whenever value of your that variable is changed (i.e whenever user writes input to the text box)
Option 4: Option 2 and 3 together (Recommended)
class UserInputFrame:
def __init__(self, parent):
# create the variable
self.entry_ticker_var = tkinter.StringVar()
# create frame
frame = tkinter.ttk.Frame(parent)
# create text box
entry_ticker = tkinter.Entry(self.frame, text='Add ticker here...', textvariable=self.entry_ticker_var)
entry_ticker.grid(row=0, column=1, pady=20, sticky='n', columnspan=2)
def start_tab_widgets():
....
....
....
# Create class instance
user_input_frame = UserInputFrame(root_window)
# Access the entry_ticker value
user_input_frame.entry_ticker_var.get()
References:
Python Tkinter
python Tkinter-module
On the side note, if you want to access child widget of a frame you may need to use winfo_children()
for child in Userinputframe.winfo_children():
widget_type = child.winfo_class()
if widget_type == 'Entry':
# do stuff
I want to display a row that matches the entry from all of my entry boxes into the treeview. How can I get the values of the treeview and check if it matches the entry from one of the boxes and display the whole row. Here is my treeview code
tree = ttk.Treeview()
books_data = pandas.read_csv("List of Books - Sheet1 (3).csv")
df_column = books_data.columns.values
print(len(df_column))
print(df_column)
tree["column"] = list(books_data.columns)
tree["show"] = "headings"
for column in tree['column']:
tree.heading(column,text=column)
df_rows = books_data.to_numpy().tolist()
for row in df_rows:
tree.insert("","end",values=row)
tree.grid(column=0,row=4,columnspan=8)
The short solution
import tkinter as tk
import tkinter.ttk as ttk
import sys
import pandas
# https://gist.githubusercontent.com/netj/8836201/raw/6f9306ad21398ea43cba4f7d537619d0e07d5ae3/iris.csv
df = pandas.read_csv('./iris.csv')
df.head()
class main_window:
def __init__(self, root):
self.root = root
root.title("Treeview Search Example")
# INITIALIZE TREEVIEW + SCROLLVIEW
self.tree = ttk.Treeview(root, columns=list(df.columns.values), show='headings')
self.tree.grid(row=1, column=0, sticky='nsew')
# https://stackoverflow.com/a/41880534/5210078
vsb = ttk.Scrollbar(root, orient="vertical", command=self.tree.yview)
vsb.grid(row=1, column=1, sticky='ns')
self.tree.configure(yscrollcommand=vsb.set)
for column in self.tree['column']:
self.tree.heading(column,text=column)
df_rows = df.to_numpy().tolist()
for row in df_rows:
self.tree.insert("","end",values=row)
# ADD SEARCH BOXES
search_frame = tk.Frame(root)
search_frame.grid(row=0, column=0, columnspan=2, sticky='nsew')
tk.Label(search_frame, text="sepal.length:").grid(row=0, column=0)
tk.Label(search_frame, text="sepal.width:").grid(row=0, column=2)
tk.Label(search_frame, text="petal.length:").grid(row=0, column=4)
tk.Label(search_frame, text="petal.width:").grid(row=0, column=6)
tk.Label(search_frame, text="variety:").grid(row=0, column=8)
# Add Search boxes
self.sepal_length_ent = tk.Entry(search_frame)
self.sepal_length_ent.grid(row=0, column=1)
self.sepal_width_ent = tk.Entry(search_frame)
self.sepal_width_ent.grid(row=0, column=3)
self.petal_length_ent = tk.Entry(search_frame)
self.petal_length_ent.grid(row=0, column=5)
self.petal_width_ent = tk.Entry(search_frame)
self.petal_width_ent.grid(row=0, column=7)
self.variety_ent = tk.Entry(search_frame)
self.variety_ent.grid(row=0, column=9)
tk.Button(search_frame, text="Search", command=self.search).grid(row=0, column=10)
def search(self):
# https://stackoverflow.com/a/27068344/5210078
self.tree.delete(*self.tree.get_children())
build_query = ""
# https://stackoverflow.com/a/56157729/5210078
if self.sepal_length_ent.get():
build_query += f'& {self.sepal_length_ent.get()} == `sepal.length` '
if self.sepal_width_ent.get():
build_query += f'& {self.sepal_width_ent.get()} == `sepal.width` '
if self.petal_length_ent.get():
build_query += f'& {self.petal_length_ent.get()} == `petal.length` '
if self.petal_width_ent.get():
build_query += f'& {self.petal_width_ent.get()} == `petal.width` '
if self.variety_ent.get():
build_query += f'& "{self.variety_ent.get()}" in `variety`'
if build_query:
print(build_query)
queried_df = df.query(build_query[1:])
else:
queried_df = df
df_rows = queried_df.to_numpy().tolist()
for row in df_rows:
self.tree.insert("","end",values=row)
if __name__ == '__main__':
main = tk.Tk()
main_window(main)
main.mainloop()
sys.exit()
The explanation of the solution
import tkinter as tk
import tkinter.ttk as ttk
import sys
import pandas
Obviously importing all the needed programs.
# https://gist.githubusercontent.com/netj/8836201/raw/6f9306ad21398ea43cba4f7d537619d0e07d5ae3/iris.csv
df = pandas.read_csv('./iris.csv')
df.head()
Loading the csv dataset. I used the IRIS dataset because it is easily accessible.
Jump ahead to:
if __name__ == '__main__':
main = tk.Tk()
main_window(main)
main.mainloop()
sys.exit()
Here we load the main_window class into our main tkinter window (makes your code look neater)
The self-explanatory bit (added a ttk treeview with scrollbar):
class main_window:
def __init__(self, root):
self.root = root
root.title("Treeview Search Example")
# INITIALIZE TREEVIEW + SCROLLVIEW
self.tree = ttk.Treeview(root, columns=list(df.columns.values), show='headings')
self.tree.grid(row=1, column=0, sticky='nsew')
# https://stackoverflow.com/a/41880534/5210078
vsb = ttk.Scrollbar(root, orient="vertical", command=self.tree.yview)
vsb.grid(row=1, column=1, sticky='ns')
self.tree.configure(yscrollcommand=vsb.set)
for column in self.tree['column']:
self.tree.heading(column,text=column)
df_rows = df.to_numpy().tolist()
for row in df_rows:
self.tree.insert("","end",values=row)
# ADD SEARCH BOXES
search_frame = tk.Frame(root)
search_frame.grid(row=0, column=0, columnspan=2, sticky='nsew')
tk.Label(search_frame, text="sepal.length:").grid(row=0, column=0)
tk.Label(search_frame, text="sepal.width:").grid(row=0, column=2)
tk.Label(search_frame, text="petal.length:").grid(row=0, column=4)
tk.Label(search_frame, text="petal.width:").grid(row=0, column=6)
tk.Label(search_frame, text="variety:").grid(row=0, column=8)
# Add Search boxes
self.sepal_length_ent = tk.Entry(search_frame)
self.sepal_length_ent.grid(row=0, column=1)
self.sepal_width_ent = tk.Entry(search_frame)
self.sepal_width_ent.grid(row=0, column=3)
self.petal_length_ent = tk.Entry(search_frame)
self.petal_length_ent.grid(row=0, column=5)
self.petal_width_ent = tk.Entry(search_frame)
self.petal_width_ent.grid(row=0, column=7)
self.variety_ent = tk.Entry(search_frame)
self.variety_ent.grid(row=0, column=9)
tk.Button(search_frame, text="Search", command=self.search).grid(row=0, column=10)
def search(self):
# https://stackoverflow.com/a/27068344/5210078
self.tree.delete(*self.tree.get_children())
Delete all the rows in the table
build_query = ""
# https://stackoverflow.com/a/56157729/5210078
if self.sepal_length_ent.get():
build_query += f'& {self.sepal_length_ent.get()} == `sepal.length` '
if self.sepal_width_ent.get():
build_query += f'& {self.sepal_width_ent.get()} == `sepal.width` '
if self.petal_length_ent.get():
build_query += f'& {self.petal_length_ent.get()} == `petal.length` '
if self.petal_width_ent.get():
build_query += f'& {self.petal_width_ent.get()} == `petal.width` '
if self.variety_ent.get():
build_query += f'& "{self.variety_ent.get()}" in `variety`'
Build a search query. Use == for integers/floats and use in when searching inside strings. If you want to read more about queries, read this. If you wanted to do something wacky, like make one of your entries a "lower than" box for integers, you could even substitute in a < or >. It basically makes our life much easier here! Because we can do a query, based off a dynamically changing string!
if build_query:
print(build_query)
queried_df = df.query(build_query[1:])
else:
queried_df = df
If there is no build_query (no inputs in entry boxes), just reload the table with the data from the df. (if build_query: is shorthand for if build_query != '':)
If there is query data, do a query with the query terms.
df_rows = queried_df.to_numpy().tolist()
for row in df_rows:
self.tree.insert("","end",values=row)
Reload the treeview with the data from the "new" queried df!
If you wanted a solution relate to your actual csv and interface, I'd recommend sharing an example of your csv and the gui design.
What the solution became
def search():
# clear the tree of data
tree.delete(*tree.get_children())
entries = [
title_entry,
author_entry,
subject_category_entry,
publication_date_entry
]
build_df = books_data
if entries[0].get():
build_df = build_df[build_df.TITLE.str.contains(entries[0].get())]
if entries[1].get():
build_df = build_df[build_df.AUTHOR.str.contains(entries[1].get())]
if entries[2].get():
build_df = build_df[build_df.SUBJECT_CATEGORY.str.contains(entries[2].get())]
if entries[3].get():
build_df = build_df[build_df.PUBLICATION_DATE == (entries[3].get())]
df_rows = build_df.to_numpy().tolist()
for row in df_rows:
tree.insert("","end",values=row)
I have a function where the user can checkout a book
def checkout(self):
global book_id
global checkout_ids
self.main_page.destroy()
self.checkout_page = Tk()
self.checkout_page.title("Checkout Page")
self.checkout_page.config(padx=20, pady=20, bg="white")
confirm_checkout_button = Button(text="CONFIRM AND CHECKOUT", fg=WHITE, bg=NAVY_BLUE, width=31,
height=2, command=self.thank_you_page)
confirm_checkout_button.grid(column=7, row=0, sticky="e")
self.checkout_tree = ttk.Treeview()
self.build_df = self.books_data.copy()
df_column = self.books_data.columns.values
self.checkout_tree["column"] = list(self.books_data.columns)
self.checkout_tree["show"] = "headings"
vsb = ttk.Scrollbar(orient="vertical", command=self.checkout_tree.yview())
vsb.grid(column=8, row=1, sticky="ns")
self.checkout_tree.configure(yscrollcommand=vsb.set)
for column in self.checkout_tree['column']:
self.checkout_tree.heading(column, text=column)
for x in checkout_ids:
build_df = self.build_df[self.build_df.IDENTIFICATION_NO == (x)]
df_rows = build_df.to_numpy().tolist()
for row in df_rows:
self.checkout_tree.insert("", "end", values=row)
self.checkout_tree.grid(column=0,row=1, columnspan=8)
self.checkout_page.mainloop()
My plan was to save the book_ids to the user_name(entry.get) so the books will only display if it matches the user's entry
def return_book_page(self):
self.borrow_or_return_page.destroy()
return_page = Tk()
return_page.title("Return page")
return_page.config(padx=20,pady=20,bg=WHITE)
list_of_books_to_return = Label(text="List of books to return: ", fg=NAVY_BLUE, bg=WHITE,
font=("Viga", 32, "bold"))
list_of_books_to_return.grid(column=0,row=1, sticky="w")
please_text = Label(text="(Please check your library fines before returning the books)",
fg=NAVY_BLUE ,bg=WHITE, font=("viga", 12,))
please_text.grid(column=0,row=2,sticky="w")
check_fines = Button(text="LIBRARY FINES", fg=WHITE,bg=NAVY_BLUE, width=31,height=2,activebackground=NAVY_BLUE)
check_fines.grid(column=3,row=2, columnspan=2)
return_books = Button(text="RETURN BOOKS", fg=WHITE,bg="#6DA5A9", width=31,height=2, activebackground="#6DA5A9")
return_books.grid(column=6,row=2, columnspan=2)
self.return_book_tree = ttk.Treeview()
self.build_df = self.books_data.copy()
df_column = self.books_data.columns.values
self.return_book_tree["column"] = list(self.books_data.columns)
self.return_book_tree["show"] = "headings"
vsb = ttk.Scrollbar(orient="vertical", command=self.return_book_tree.yview())
vsb.grid(column=8, row=1, sticky="ns")
self.return_book_tree.configure(yscrollcommand=vsb.set)
borrow_dictionary = {
self.name_login.get(): checkout_ids
}
for column in self.return_book_tree['column']:
self.return_book_tree.heading(column, text=column)
for x in borrow_dictionary[self.name_login.get()]:
build_df = self.build_df[self.build_df.IDENTIFICATION_NO == (x)]
df_rows = build_df.to_numpy().tolist()
for row in df_rows:
self.checkout_tree.insert("", "end", values=row)
self.return_book_tree.grid(column=0, row=1, columnspan=8)
return_page.mainloop()
I tried creating a dictionary called borrow_dictionary containing the user_name and the list of ids of the book the user has chosen but I get a key error everytime I save it to a csv or use it in a loop to display the values of the tree
borrow_dictionary = {
self.name_login.get(): checkout_ids
}
for column in self.return_book_tree['column']:
self.return_book_tree.heading(column, text=column)
for x in borrow_dictionary[self.name_login.get()]:
build_df = self.build_df[self.build_df.IDENTIFICATION_NO == (x)]
df_rows = build_df.to_numpy().tolist()
for row in df_rows:
self.checkout_tree.insert("", "end", values=row)
How can I efficiently do this feature?
The program is supposed to add columns dynamically in order to present more data when corresponding button is pressed. The buttons can be pressed in different order and that effects the appearance of the next column/header. As you can see in the example program, the headers are not updated correctly. Only the last one is shown in the table. If the item (selected row and column) already has data, it should be updated, but currently the data is added only in new column, so one of the questions is how to update an item referred by selected row and header. When the row is deleted, all empty columns should be removed.
I'm trying to update the columns by concatenating tuples, but have no idea how to deal with the headers.
Any suggestions are very appreciated.
from random import randint
from tkinter import *
from tkinter.ttk import Treeview
def get_window():
root = Tk()
root.resizable(width=True, height=True)
root.geometry("823x458")
root.title("PsControl")
return root
def get_top_frame(root):
frame = Frame(root)
frame.name = 'top_frame'
frame.root = root
frame.pack(side=TOP, expand=False, fill=X)
button1 = Button(frame, text="Add Row", command=lambda: tv.insert('', 'end', text="hostname"))
button1.grid(row=0, column=1)
button1 = Button(frame, text="Add Cow 1", command=lambda: tv_insert("H1", randint(1, 100)))
button1.grid(row=0, column=2)
button2 = Button(frame, text="Add Cow 2", command=lambda: tv_insert("H2", randint(1, 100)))
button2.grid(row=0, column=3)
button3 = Button(frame, text="Add Cow 3", command=lambda: tv_insert("H3", randint(1, 100)))
button3.grid(row=0, column=4)
button4 = Button(frame, text="Add Cow 20", command=lambda: tv_insert("H4", randint(1, 100)))
button4.grid(row=0, column=5)
button5 = Button(frame, text="Delete row", command=lambda: tv.delete(tv.selection()))
button5.grid(row=0, column=6)
def get_bottom_frame(root):
global tv
frame = Frame(root, highlightbackground='#3E4149', highlightthickness=1, borderwidth=2)
frame.name = 'bottom_frame'
frame.root = root
h = Scrollbar(root, orient='horizontal')
h.pack(side=BOTTOM, fill=X)
v = Scrollbar(root)
v.pack(side=RIGHT, fill=Y)
frame.pack(side=BOTTOM, expand=True, fill=BOTH)
frame.config(background='#FFFFFF')
tv = Treeview(frame, xscrollcommand=h.set, yscrollcommand=v.set)
tv.column("#0", width=135, minwidth=35, stretch=NO)
tv.heading("#0", text='Host', anchor='w')
tv.pack(expand=True, fill='both')
h.config(command=tv.xview)
v.config(command=tv.yview)
def tv_insert(heading, insert_data):
selection = tv.selection()
columns = tv["columns"]
if columns == '':
tv["columns"] = (heading,)
tv.column(heading, width=135, minwidth=35, stretch=NO)
tv.heading(heading, text=heading, anchor='w')
tv.item(selection, values=insert_data)
else:
new_col = columns + (heading,)
tv["columns"] = new_col
tv.heading(heading, text=heading, anchor='w')
data = tv.item(selection, "values")
if data == '':
tv.item(selection, values=insert_data)
else:
new_data = data + (insert_data,)
tv.item(selection, values=new_data)
def delete_row():
selection = tv.selection()
tv.delete(selection)
root = get_window()
get_top_frame(root)
get_bottom_frame(root)
root.mainloop()
Thanks to #acw1668 answer here is the code that does the job as expected. Any suggestions for improvement are welcome.
def tv_insert(heading, insert_data):
selection = tv.selection()
columns = tv["columns"]
if columns == '': # if no columns, create column, heading and item.
tv["columns"] = (heading,)
tv.column(heading, width=135, minwidth=35, stretch=NO)
tv.heading(heading, text=heading, anchor='w')
tv.item(selection, values=(insert_data,))
else:
headings = [tv.heading(col) for col in columns] # save current headings
if heading not in columns:
new_col = columns + (heading,)
tv["columns"] = new_col
# restore previous headings
for h in headings:
tv.heading(h['text'], text=h['text'], anchor=h['anchor'])
# set new heading
tv.heading(heading, text=heading, anchor='w')
# add data/item with with size of the columns
len_col = len(new_col)
data = ['' for _ in range(len_col)] # Create an empty list
data[len_col - 1] = insert_data # Update the next
tv.item(selection, values=tuple(data))
else:
data = tv.item(selection, "values")
# if heading exist but no item on the the selected row
if data == '':
data = ['' for _ in range(len(headings))]
index = columns.index(heading)
data[index] = insert_data
tv.item(selection, values=tuple(data))
else:
data = list(data)
if len(data) < len(columns):
new_data = ['' for _ in range(len(columns))]
for i, d in enumerate(data):
new_data[i] = d
index = columns.index(heading)
new_data[index] = insert_data
tv.item(selection, values=tuple(new_data))
else:
index = columns.index(heading)
data[index] = insert_data
tv.item(selection, values=tuple(data))
Since you have assigned new columns to tv, the headings information is lost. You should save the current headings information before assigning the new columns and restore them after:
def tv_insert(heading, insert_data):
selection = tv.selection()
columns = tv["columns"]
if columns == '':
tv["columns"] = (heading,)
tv.column(heading, width=135, minwidth=35, stretch=NO)
tv.heading(heading, text=heading, anchor='w')
tv.item(selection, values=insert_data)
else:
headings = [tv.heading(col) for col in columns] # save current headings
new_col = columns + (heading,)
tv["columns"] = new_col
# restore previous headings
for h in headings:
tv.heading(h['text'], text=h['text'], anchor=h['anchor'])
# set new heading
tv.heading(heading, text=heading, anchor='w')
data = tv.item(selection, "values")
if data == '':
tv.item(selection, values=insert_data)
else:
new_data = data + (insert_data,)
tv.item(selection, values=new_data)
I am creating a Tkinter-based form where I want to store each item that the user types in as a separate variable. I understand how to generate the form, but I am lost on how to handle the program after the user presses the Enter button. I really just need everything stored as a string.
from tkinter import *
import pandas as pd
fields = ('Event', 'Event Folder', 'Session', 'Date: (MM/DD/YYYY)', 'StartTime: 24HR(HH:MM)', 'EndTime: 24HR(HH:MM)')
def saveVars(entries):
locals().update(entries)
return
def makeform(root, fields):
entries = {}
for field in fields:
row = Frame(root)
lab = Label(row, width=22, text=field+": ", anchor='w')
ent = Entry(row)
ent.insert(0,"")
row.pack(side = TOP, fill = X, padx = 5 , pady = 5)
lab.pack(side = LEFT)
ent.pack(side = RIGHT, expand = YES, fill = X)
entries[field] = ent
return entries
if __name__ == '__main__':
root = Tk()
ents = makeform(root, fields)
b1 = Button(root, text = 'Enter', command = lambda e = ents: saveVars(e))
b1.pack(side = LEFT, padx = 5, pady = 5)
root.mainloop()
What you need to do is build a function that does something with your entry fields. That said you may want to change up you code a little bit to make this easier. Instead of building your labels and entry fields in a function build them in the global namespace and then store the entry fields in a list.
import tkinter as tk
fields = ('Event', 'Event Folder', 'Session', 'Date: (MM/DD/YYYY)',
'StartTime: 24HR(HH:MM)', 'EndTime: 24HR(HH:MM)')
def do_something_with_entries():
for ndex, entry in enumerate(entry_list):
print(fields[ndex], ': ', entry.get())
if __name__ == '__main__':
root = tk.Tk()
entry_list = []
for field in fields:
row = tk.Frame(root)
lab = tk.Label(row, width=22, text=field + ": ", anchor='w')
ent = tk.Entry(row)
entry_list.append(ent)
row.pack(side='top', fill='x', padx=5, pady=5)
lab.pack(side='left')
ent.pack(side='right', expand='yes', fill='x')
tk.Button(root, text='Enter', command=do_something_with_entries).pack(side='left', padx=5, pady=5)
root.mainloop()
Results:
Here is an example using pandas:
import tkinter as tk
import pandas as pd
fields = ['Event', 'Event Folder', 'Session', 'Date: (MM/DD/YYYY)', 'StartTime: 24HR(HH:MM)', 'EndTime: 24HR(HH:MM)']
df = pd.DataFrame(columns=fields)
def do_something_with_entries():
global df
stored_values = []
for ndex, entry in enumerate(entry_list):
stored_values.append(entry.get())
series = pd.Series(stored_values, index=fields)
df = df.append(series, ignore_index=True)
if __name__ == '__main__':
root = tk.Tk()
entry_list = []
for field in fields:
row = tk.Frame(root)
lab = tk.Label(row, width=22, text=field + ": ", anchor='w')
ent = tk.Entry(row)
entry_list.append(ent)
row.pack(side='top', fill='x', padx=5, pady=5)
lab.pack(side='left')
ent.pack(side='right', expand='yes', fill='x')
tk.Button(root, text='Enter', command=do_something_with_entries).pack(side='left', padx=5, pady=5)
root.mainloop()
You are storing the questions as keys and Entry widgets as values of the entries dict.
Firstly, put the entries dict out of the makeform function (otherwise, only makeform function would be able to use it)
Secondly, create the answer dict. We are going to store the answers here.
Thirdly, create a fetch function which is to be called when the user clicks Enter button. It will go through the entries dict and set the values of the answers dict to the entered answers (using Entry.get(...) method)
Now you can process the form answers.
Here is an example:
from tkinter import *
import pandas as pd
fields = ('Event', 'Event Folder', 'Session', 'Date: (MM/DD/YYYY)',
'StartTime: 24HR(HH:MM)', 'EndTime: 24HR(HH:MM)')
entries = {}
answers = {}
def makeform(root, fields):
for field in fields:
row = Frame(root)
lab = Label(row, width=22, text=field + ": ", anchor='w')
ent = Entry(row)
row.pack(side=TOP, fill=X, padx=5, pady=5)
lab.pack(side=LEFT)
ent.pack(side=RIGHT, expand=YES, fill=X)
entries[field] = ent
return entries
def fetch():
for question in entries:
answers[question] = entries[question].get()
print(answers) # do something with the results now
if __name__ == '__main__':
root = Tk()
ents = makeform(root, fields)
root.bind('<Return>', fetch)
b1 = Button(root, text='Enter', command=fetch)
b1.pack(side=LEFT, padx=5, pady=5)
root.mainloop()
PS: You don't need to use a lambda-function as the button command. Simply use the fetch function as a command.
PS 1: And why do you call ent.insert(0, "")? It has no effect.