Search function in TKinter's Treeview

I had wrote a function for searching names and various infos saved on a csv file.
Basically it works, but it just bring up the result on the top of the treeview only if I wrote the whole name or else and not while I’m still typing on the keyboard. Then, I would that during the search, the other infos in the list disappear temporary from the screen until I digit something else.
Here’s my code:

from tkinter import *
from tkinter import ttk
import csv


root = Tk()
frm_infospiti = Frame(root)
frm_infospiti.pack()


def elenco_completo():

    def chiudi_elenco():
        frm_infospiti.destroy()

    def cerca(*args):
        item_trv = tree_colonne.get_children()
        trova_digit = dato_da_cercare.get().capitalize()
        
        for oggetto in item_trv
            if trova_digit in tree_colonne.item(oggetto)['values']:
                search_var = tree_colonne.item(oggetto)['values']
                
                tree_colonne.insert("", 0, values=(search_var))   
    
    colonne = ["Cognome", "Nome", "Data di nascita"]        
    tree_colonne = ttk.Treeview(frm_infospiti, selectmode='extended', height=15)

    tree_colonne['columns'] = colonne


    with open('/path/to/file.csv', 'r', encoding='UTF-8') as elenco:
        lettore_elenco = csv.reader(elenco)
        for row in lettore_elenco:
            cognome = row[0]
            nome = row[1]
            nascita = row[2]

            tree_colonne.insert("", 0, values=(cognome, nome, nascita))

        for each in elenco:
            tree_colonne.insert("", END, values=[each])

    dato_da_cercare = StringVar() 
    
    entry = Entry(frm_infospiti, width=25, borderwidth=4, textvariable=dato_da_cercare)
    entry.pack(side='top')
    dato_da_cercare.trace('w', cerca)


    for i in colonne:
        tree_colonne.column(i)
        tree_colonne.heading(i)


    tree_colonne['show'] = 'headings'

    tree_colonne.column('#0')
    tree_colonne.heading('#0')

    tree_colonne.column('#1')
    tree_colonne.heading('#1', text="Cognome")

    tree_colonne.column('#2')
    tree_colonne.heading('#2', text="Nome")

    tree_colonne.column('#3')
    tree_colonne.heading('#3', text="Data di nascita")

    tree_colonne.pack()


    puls_chiudi_elenco = Button(frm_infospiti, text="Chiudi", command=chiudi_elenco)
    puls_chiudi_elenco.pack()


    if __name__=='__main__':
        root.mainloop()

How can I implement other functionalities described as above?

I forgot that everytime I search for something, after I found an item and digiting something else, if I re-search the previous item already found, the program return two time the same on screen… the more I re-search for an item, more times it prints that specific result. How can I avoid this?

I don’t know if this will help, but maybe you have a .insert method somewhere to an object that is simply accumulating the search results, rather than said object being cleaned with a .delete method.

As an example of my meaning, this is a snippet from one of my apps. It’s just one of the functions that said app needs:

def hashType():
    ht = hashSelect.get()
    if file:
        resultText['state']='normal'
        resultText.delete('1.0', tk.END)
        hd = hash_file(file, ht)
        resultText.insert('1.0', hd)
        resultText['state']='disabled'
    else:
        resultText['state']='normal'
        resultText.delete('1.0', tk.END)
        resultText['state']='disabled'

I tried in different ways but I’m still on it.
I am not able to understand your example because I cannot figure out any correlation between your code and mine.
I need to finish this stupid little software as soon as possible because it is taking too long time to me. I need to dedicate attention to another field of interest about audio programming (that’s why I started to learn to code) and I’m tired to spent my little time to something is not about my interests. I do this only for the little company where I work that needs a better tool to manage infos and not for any particular interest.
So please help me to finish this so I can start to ask something about more correlated stuff about what is in my really needs (and I guess that audio programming would be more interesting to the community too).
Thanks.

The correlation (if one in fact exists) is that you have the likes of tree_colonne.insert("", 0, values=(cognome, nome, nascita)), so unless that .insert() is removed in some way, it could very well explain:

You should be able to see that, with my code, if file returns True, I remove the contents of resultText, before introducing any new content, or if file returns False, I simply remove the contents of resultText; either way, when the information held in resultText is no longer valid, it is removed by using the .delete() method.

As I’ve not scrutinized your code, I don’t know if you’re doing that or if in fact you need to do that, but it was a thought that I had and which may or may not be of use to you – just trying to help, is all.

1 Like

I’m still stucked on this.
I found this video that shows what I would obtain, with the only difference that I need this function using a treeview, not a listbox. There is something that I can’t understand about what I have to modify on the code shown in the video to make it work properly.
I tried hundreads of times but I am a real moron so, I can’t realize what kind of mistake I keep doing.

Hi,

I looked over your code. It appears that it is incomplete.

You have the following function which isn’t called anywhere in order to create your window:

def elenco_completo()

Look into where you’re going to call this function to create the main window. Additionally, in your code
you’re attempting to read from a file that has yet to be created or doesn’t exist. Therefore, in your code, you have to check if the file already exists. If it doesn’t exist, then create it. If it does exist, don’t create one to avoid overwriting the contents of the existing one.

I would suggest redesigning your script using classes versus its current design approach. Also review more tutorials on creating tkinter widgets and windows.

Here’s some example code:

#!python3
# -*- encoding: utf-8 -*-
from dataclasses import dataclass, astuple
import csv
import tkinter as tk
import tkinter.ttk as ttk

@dataclass
class Person:
    surname: str
    forename: str
    birth: str
    iid: str | None = None

class App(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.title("Search data")
        self.state("zoomed")

        self.csv_path = "/path/to/file.csv"

        # Frame for the filter.
        frame = tk.Frame(self)
        frame.pack(side="top", fill="x")

        tk.Label(frame, text="Filter:").pack(side="left")

        self.filter_var = tk.StringVar()
        self.filter_entry = tk.Entry(frame, textvariable=self.filter_var)
        self.filter_entry.pack(side="left", fill="x")

        # Frame for the table.
        frame = tk.Frame(self)
        frame.pack(side="top", fill="x")

        frame.grid_rowconfigure(0, weight=1)
        frame.grid_rowconfigure(1, weight=0)
        frame.grid_columnconfigure(0, weight=1)
        frame.grid_columnconfigure(1, weight=0)

        yscrollbar = tk.Scrollbar(frame, orient="vertical")
        yscrollbar.grid(row=0, column=1, sticky="ns")

        xscrollbar = tk.Scrollbar(frame, orient="horizontal")
        xscrollbar.grid(row=1, column=0, sticky="we")

        columns = ["Surname", "Forename", "Date of birth"]

        self.table = ttk.Treeview(frame, selectmode="extended", show="headings", columns=columns, yscrollcommand=yscrollbar.set, xscrollcommand=xscrollbar.set)
        self.table.grid(row=0, column=0, sticky="nswe")

        yscrollbar.config(command=self.table.yview)
        xscrollbar.config(command=self.table.xview)

        for i, heading in enumerate(columns, start=1):
            self.table.heading("#%d" % i, text=heading)

        # Read the data.
        self.data = []

        with open(self.csv_path, encoding="utf-8") as file:
            for row in csv.reader(file):
                entry = Person(*row)
                self.data.append(entry)
                entry.iid = self.table.insert("", "end", values=astuple(entry))

        # Callback for when the filter changes.
        self.filter_var.trace("w", self.filter_changed)

        self.cur_filter = self.filter_var.get().casefold()

        # Put the input focus on the filter.
        self.filter_entry.focus()

    def filter_changed(self, *args):
        # Get the new filter.
        new_filter = self.filter_var.get().casefold()

        for entry in self.data:
            # Should this entry be shown?
            if self.should_show(entry, new_filter):
                # This entry should be shown.
                if not entry.iid:
                    # It"s current hidden, so show it.
                    entry.iid = self.table.insert("", "end", values=astuple(entry))
            else:
                # This entry should be hidden.
                if entry.iid:
                    # It"s current shown, so hide it.
                    self.table.delete(entry.iid)
                    entry.iid = None

        self.cur_filter = new_filter

        # Which entries are now being shown?
        entries_showing = [entry for entry in self.data if entry.iid]

        # Sort them in the table to match their relative order in self.data.
        for index, entry in enumerate(entries_showing):
            self.table.move(entry.iid, "", index)

    def should_show(self, entry, filter):
        return filter in (entry.surname + entry.forename).casefold()

App().mainloop()

As I said, I am a moron so, I forgot to post the rewritten code, following the video… here is:

import csv
from tkinter import *
from tkinter import ttk

root = Tk()

def check(e):
    query = entry_cerca.get()
    selections = []
    for child in tv_nomi.get_children():
        if query in tv_nomi.item(child)['values']:
            tv_nomi.delete()
            print(tv_nomi.item(child)['values'])
            selections.append(child)
    tv_nomi.selection_set(selections)


entry_cerca = Entry(root)
entry_cerca.pack()

tv_nomi = ttk.Treeview(root,
                   columns=('Cognome', 'Nome', 'Data di nascita'),
                   show='headings')

tv_nomi.column('#0')
tv_nomi.column('#1')
tv_nomi.column('#2')
tv_nomi.column('#3')

tv_nomi.heading('#0')
tv_nomi.heading('#1', text='Cognome')
tv_nomi.heading('#2', text='Nome')
tv_nomi.heading('#3', text='Data di nascita')

tv_nomi.pack()

with open('/path/to/file.csv',
          'r',
          encoding='UTF-8') as provelenco:
    ltr = csv.reader(provelenco)
    for inf in ltr:
        cg = inf[0]
        nm = inf[1]
        ns = inf[2]

        valori = [cg, nm, ns]
        tv_nomi.insert("", 0, values=([cg, nm, ns]))

entry_cerca.bind('<KeyRelease>', check)

root.mainloop()

To me it’s quite difficult for now understand well your code but it’s very interesting. I will study it for sure when time permit it. As I see, the use of classes is the best way to organize everything. Basically I don’t use it because this is my first attempt to write a piece of software, so to me is a long road.
The only field tthat this function seems to not be able to find is " date of birth", while name and surname work well.
Anyway, you solved my problem thanks a lot!

If you want the search to look at the date of birth too, use this:

    def should_show(self, entry, filter):
        return filter in (entry.surname + " " + entry.forename + " " + entry.birth).casefold()

Hi,

I am learning Python as well. I was learning Tkinter in tandem with Python. However, what I have found from my experience is that the stronger your Python base is, the better you will be with your Tkinter projects and Python development in general.

Two books that I would recommend are:

  1. Learning Python, 5th Edition (follow up book to this one is: Python Programming 4th Ed. - both by Mark Lutz)
  2. Tkinter GUI Application Development Cookbook

I would put priority on the first book. This way you have a much better understanding of the Tkinter projects especially since they emphasize a class design approach to project development. At first, working with classes might come as a shock when coming from a procedural/functional background. But in due time, after building a few scripts (maybe more than a few tbh :blush:), you’ll start feeling more comfortable working with them and will start building your scripts with classes in mind almost second nature.

The 2nd book gives many simple examples (at least up to the first half) on creating windows and widgets and the many different types (scrollbar, text box, radiobuttons, entry boxes, label frames, etc.).
They’re all written with an emphasis on class design development.

Just a rookie’s perspective.

You are bang on point here. I’ve encountered many python newcomers who have failed to realise that one can’t simply drive into the Tkinter library and expect to create anything more than a basic GUI, without first developing their python skills beyond the fundamentals. Designer apps, such as PAGE can be a huge help, but even so, python coding skills are still a prerequisite.

1 Like