Home > Back-end >  Functioning search bar for treeview in tkinter
Functioning search bar for treeview in tkinter

Time:10-05

I'm writing a program that makes a treeview out of data from the given .csv file. I want to get a functioning searchbar so that it only shows results that contain what is written in the searchbar and I'm having trouble. Any help would be appreciated.

from tkinter import *
from tkinter import ttk
import pandas as pd
import tkinter as tk

def filterTreeView(*args):
    ItemsOnTreeView = myTree.get_children()

    search = search_ent_var.get().capitalize()

    for eachItem in ItemsOnTreeView:
        if search in myTree.item(eachItem)['values'][2]:
            search_var = myTree.item(eachItem)['values']
            myTree.delete(eachItem)

            myTree.insert("", 0, value=search_var)

root = Tk()
root.title("NSW Traffic Penalty Data")

topFrame = Frame(root, bg="white")
topFrame.place(x=5, y=5, width=200, height=80)

treeFrame = Frame(root, bg="white")
treeFrame.place(x=5, y=100, width=1500, height=800)

lb1 = Label(topFrame, text="Search by", fg="black", bg="white")
lb1.grid(row=0, column=0)
search_ent_var = StringVar()
myentry = Entry(topFrame, textvariable=search_ent_var)
myentry.grid(row=0, column=1)
search_ent_var.trace("w", filterTreeView())

column = ['Financial Year', 'Month', 'Offence Code', 'Offence Description', 'Legislation', 'Section Clause',
          'Penalty Amount', 'Camera Offence', 'Camera Type', 'Camera Location', 'Camera Location Details', 'School Zone',
          'Speed Range', 'Speed Offence', 'Point to Point Offence', 'Red Light Camera Offence', 'Speed Camera Offence',
          'Seatbelt Offence', 'Mobile Phone Offence', 'Parking Offence', 'Criminal Infringement Notice Scheme Offence',
          'Food Safety Offence', 'Non-Motor Vehicle Offence', 'Number of Penalty Notices', 'Total Value of Penalty Notices']
data = pd.read_csv("penalty_data_set_2.csv")
data_rows = data.to_numpy().tolist()
myTree = ttk.Treeview(treeFrame, height=100, column=column)
myTree.place(relheight=1, relwidth=1)
myTree['show']='headings'

treescrolly = tk.Scrollbar(treeFrame, orient="vertical", command=myTree.yview)
treescrollx = tk.Scrollbar(treeFrame, orient="horizontal", command=myTree.xview)
myTree.configure(xscrollcommand=treescrollx.set, yscrollcommand=treescrolly.set)
treescrollx.pack(side="bottom", fill="x")
treescrolly.pack(side="right", fill="y")

for each in column:
    myTree.column(each, width=80)
    myTree.heading(each, text=each.capitalize())

for each in data_rows:
    myTree.insert("", "end", values = each)

root.mainloop()

CodePudding user response:

First you need to pass the function reference to .trace(...), so you need to change search_ent_var.trace("w", filterTreeView()) to search_ent_var.trace("w", filterTreeView).

Second you need to go through data_rows instead of the rows of myTree inside filterTreeView() function:

def filterTreeView(*args):
    search = search_ent_var.get().capitalize()
    # first clear the treeview
    myTree.delete(*myTree.get_children())
    # then insert matched items into treeview
    for item in data_rows:
        if search in item[2]:
            myTree.insert("", "end", values=item)

CodePudding user response:

You are in luck, I've been playing with some UI stuff in python myself. I wanted to work off the tree view and not maintain separate database in memory. Maybe, this reduces UI lag if the list gets bigger. I used the linked post in comment above as a guide to track the detached items.

Here is a complete working example ...

import tkinter
import tkinter.font as tkFont
from tkinter import TclError, ttk

data_header = (
    'First', 'Last', 'Game', 'Food',
)
data_rows = [
    ('Mickey',      'Mouse',    'Hide-and-Squeak',  'Tacos'),
    ('Minnie',      'Mouse',    'Hide-and-Squeak',  'Burgers'),
    ('Mortimer',    'Mouse',    'Hide-and-Squeak',  'Pizza'),
    ('Donald',      'Duck',     'Checkers',         'Pizza'),
    ('Daisy',       'Duck',     'Checkers',         'Pizza'),
    ('Ludwig',      'Von Drake','Poker',            'Sushi'),
    ('Scrooge',     'McDuck',   'Solitare',         'McNuggets'),
    ('Clarabelle',  'Cow',      'Poker',            'Tacos'),
]



def tree_sortby(tree, column, descending = False):
    """sort tree contents when a column header is clicked on"""
    data = [(tree.set(child, column), child) for child in tree.get_children('')]
    # different types could require a sorting function
    data.sort(reverse = descending)
    for ix, item in enumerate(data):
        tree.move(item[1], '', ix)
    # switch the heading so it will sort in the opposite direction
    tree.heading(column, command =
        lambda col = column: tree_sortby(tree, col, bool(not descending)))



class App(tkinter.Tk):

    _detached = set()

    def tree_reset(self):
        children = list(self._detached)   list(self.tree.get_children())
        self._detached = set()
        self.filter_re.set('')
        # enumerate so that order is preserved?
        for ix, item_id in enumerate(children):
            self.tree.reattach(item_id, '', ix)

    def tree_filter(self, *args):
        children = list(self._detached)   list(self.tree.get_children())
        self._detached = set()
        query = self.filter_re.get()
        i_r = -1
        for _, item_id in enumerate(children):
            text = ' '.join(self.tree.item(item_id, 'values'))
            if query in text:
                i_r  = 1
                self.tree.reattach(item_id, '', i_r)
            else:
                self._detached.add(item_id)
                self.tree.detach(item_id)


    def __init__(self):
        super().__init__()
        self.title('Tree Filter Test')
        self.geometry('200x200')
        self.resizable(True, True)
        self.minsize(200, 200)
        try:
            # windows only (remove the minimize/maximize button)
            self.attributes('-toolwindow', True)
        except TclError:
            print('Not supported on your platform')

        # define layout
        self.columnconfigure(0, weight=0)
        self.columnconfigure(1, weight=1)
        self.columnconfigure(2, weight=0)
        self.columnconfigure(3, weight=0)
        self.rowconfigure(0, weight=0)
        self.rowconfigure(1, weight=1)
        self.rowconfigure(2, weight=0)

        self.filter_re = tkinter.StringVar()
        ttk.Label(self, text = 'Filter: ').grid(column = 0, row = 0, sticky = tkinter.W)
        searchfield = ttk.Entry(self, textvariable = self.filter_re).grid(
            column = 1, row = 0, sticky='nsew')
        ttk.Button(self, text = 'Reset', command = self.tree_reset).grid(
            column = 2, row = 0, columnspan=2, sticky = tkinter.E)
        # TODO, why can't we just repond to changes is the edit field?
        self.filter_re.trace("w", self.tree_filter)

        self.tree = ttk.Treeview(self, column = data_header)
        scroll_x = ttk.Scrollbar(self, command = self.tree.xview, orient = tkinter.HORIZONTAL)
        scroll_x.grid(column = 0, row = 2, columnspan = 3, sticky = 'we')
        scroll_y = ttk.Scrollbar(self, command = self.tree.yview, orient = tkinter.VERTICAL)
        scroll_y.grid(column = 3, row = 1, sticky='ns')
        self.tree.configure(xscrollcommand = scroll_x.set, yscrollcommand = scroll_y.set)

        self.tree.column('#0', width = 1)

        for label in self.tree["column"]:
            self.tree.heading(label, text = label,
            command = lambda column = label: tree_sortby(self.tree, column))
            self.tree.column(label, width = tkFont.Font().measure(label.title()))

        for row in data_rows:
            self.tree.insert('', 'end', values = row)
            # TODO, expand each column to fit data

        self.tree.grid(column=0, row=1, columnspan=3, sticky='nsew')


if __name__ == "__main__":
    app = App()
    app.mainloop()
  • Related