Last active
September 26, 2023 02:21
-
-
Save RamonWill/f5e9fbc9df2bdceaa176448512e16eea to your computer and use it in GitHub Desktop.
The code from my tutorial series on how to create a CSV/Dataframe viewer in Tkinter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Tutorial playlist https://www.youtube.com/playlist?list=PLCQT7jmSF-LrwYppkB3Xdbe6QC81-ozmT | |
import tkinter as tk | |
from pathlib import Path | |
from tkinter import ttk | |
from TkinterDnD2 import DND_FILES, TkinterDnD | |
import pandas as pd | |
class Application(TkinterDnD.Tk): | |
def __init__(self): | |
super().__init__() | |
self.title("CSV Viewer") | |
self.main_frame = tk.Frame(self) | |
self.main_frame.pack(fill="both", expand="true") | |
self.geometry("900x500") | |
self.search_page = SearchPage(parent=self.main_frame) | |
class DataTable(ttk.Treeview): | |
def __init__(self, parent): | |
super().__init__(parent) | |
scroll_Y = tk.Scrollbar(self, orient="vertical", command=self.yview) | |
scroll_X = tk.Scrollbar(self, orient="horizontal", command=self.xview) | |
self.configure(yscrollcommand=scroll_Y.set, xscrollcommand=scroll_X.set) | |
scroll_Y.pack(side="right", fill="y") | |
scroll_X.pack(side="bottom", fill="x") | |
self.stored_dataframe = pd.DataFrame() | |
def set_datatable(self, dataframe): | |
self.stored_dataframe = dataframe | |
self._draw_table(dataframe) | |
def _draw_table(self, dataframe): | |
self.delete(*self.get_children()) | |
columns = list(dataframe.columns) | |
self.__setitem__("column", columns) | |
self.__setitem__("show", "headings") | |
for col in columns: | |
self.heading(col, text=col) | |
df_rows = dataframe.to_numpy().tolist() | |
for row in df_rows: | |
self.insert("", "end", values=row) | |
return None | |
def find_value(self, pairs): | |
# pairs is a dictionary | |
new_df = self.stored_dataframe | |
for col, value in pairs.items(): | |
query_string = f"{col}.str.contains('{value}')" | |
new_df = new_df.query(query_string, engine="python") | |
self._draw_table(new_df) | |
def reset_table(self): | |
self._draw_table(self.stored_dataframe) | |
class SearchPage(tk.Frame): | |
def __init__(self, parent): | |
super().__init__(parent) | |
self.file_names_listbox = tk.Listbox(parent, selectmode=tk.SINGLE, background="darkgray") | |
self.file_names_listbox.place(relheight=1, relwidth=0.25) | |
self.file_names_listbox.drop_target_register(DND_FILES) | |
self.file_names_listbox.dnd_bind("<<Drop>>", self.drop_inside_list_box) | |
self.file_names_listbox.bind("<Double-1>", self._display_file) | |
self.search_entrybox = tk.Entry(parent) | |
self.search_entrybox.place(relx=0.25, relwidth=0.75) | |
self.search_entrybox.bind("<Return>", self.search_table) | |
# Treeview | |
self.data_table = DataTable(parent) | |
self.data_table.place(rely=0.05, relx=0.25, relwidth=0.75, relheight=0.95) | |
self.path_map = {} | |
def drop_inside_list_box(self, event): | |
file_paths = self._parse_drop_files(event.data) | |
current_listbox_items = set(self.file_names_listbox.get(0, "end")) | |
for file_path in file_paths: | |
if file_path.endswith(".csv"): | |
path_object = Path(file_path) | |
file_name = path_object.name | |
if file_name not in current_listbox_items: | |
self.file_names_listbox.insert("end", file_name) | |
self.path_map[file_name] = file_path | |
def _display_file(self, event): | |
file_name = self.file_names_listbox.get(self.file_names_listbox.curselection()) | |
path = self.path_map[file_name] | |
df = pd.read_csv(path) | |
self.data_table.set_datatable(dataframe=df) | |
def _parse_drop_files(self, filename): | |
# 'C:/Users/Owner/Downloads/RandomStock Tickers.csv C:/Users/Owner/Downloads/RandomStockTickers.csv' | |
size = len(filename) | |
res = [] # list of file paths | |
name = "" | |
idx = 0 | |
while idx < size: | |
if filename[idx] == "{": | |
j = idx + 1 | |
while filename[j] != "}": | |
name += filename[j] | |
j += 1 | |
res.append(name) | |
name = "" | |
idx = j | |
elif filename[idx] == " " and name != "": | |
res.append(name) | |
name = "" | |
elif filename[idx] != " ": | |
name += filename[idx] | |
idx += 1 | |
if name != "": | |
res.append(name) | |
return res | |
def search_table(self, event): | |
# column value. [[column,value],column2=value2].... | |
entry = self.search_entrybox.get() | |
if entry == "": | |
self.data_table.reset_table() | |
else: | |
entry_split = entry.split(",") | |
column_value_pairs = {} | |
for pair in entry_split: | |
pair_split = pair.split("=") | |
if len(pair_split) == 2: | |
col = pair_split[0] | |
lookup_value = pair_split[1] | |
column_value_pairs[col] = lookup_value | |
self.data_table.find_value(pairs=column_value_pairs) | |
if __name__ == "__main__": | |
root = Application() | |
root.mainloop() |
it seems that there are some bugs in function : search_table
(1)the name of the column can not contain space ,and can not contain special character , like "AA BBB"or "AA*BBB" will throw error
(2)if there is #N/A in a column , it will throw error
(3)if the column only contain number instead of str , it will throw error
the errors i got :
AttributeError: Can only use .str accessor with string values!
ValueError: Cannot mask with non-boolean array containing NA / NaN values
i solved part of it
replace : query_string = f"{col}.str.contains('{value}')"
with : query_string = f"`{col}`.str.contains('{value}').fillna(False)"
but if column contain number , it still throw errors :
AttributeError: Can only use .str accessor with string values!
alright i solved like this :
def find_value(self, pairs):
# pairs is a dictionary
new_df = self.stored_dataframe
for col, value in pairs.items():
try:
query_string = f"`{col}`.str.contains('{value}').fillna(False)"
new_df = new_df.query(query_string, engine="python")
except Exception :
query_string = f"`{col}`=={value}"
new_df = new_df.query(query_string, engine="python")
self._draw_table(new_df)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Good