Home > Software design >  Tkinter: move scrollbar automatically to the founded item when searching
Tkinter: move scrollbar automatically to the founded item when searching

Time:05-15

This is a little replica of my app where I've numerous text widget create with a for loop; I've also create a search entry and option to highlight the context inside the text widget to make it easier to find the text that one is looking for. I was wondering if there is a way to also automatically move the scrollbar to the searched text: so, if I'm at beginning of my app (where the number 0 is) and search for a number at the bottom (e.g, 81) the 81 number would be highlighted and the frame go down by itself to show me the number. This is my code right now:

class App(tk.Frame):
    def __init__(self, parent, *args, **kwargs):
        tk.Frame.__init__(self, parent, *args, **kwargs)

        self.entry = tk.Entry(self, justify="center", width=6)
        self.entry.pack(padx=(15,0), ipady=5, side="top", anchor="e")

        button = tk.Button(self, text="Search", width=13, command=self.search)
        button.pack(side="top", anchor="e")   

        self.frame = tk.Frame(self)
        self.frame.pack(fill="both", expand="true", side="bottom")

        self.canvas = tk.Canvas(self.frame, highlightthickness=0)
        self.canvas.pack(side="left", fill="both", expand="true")
        self.canvas.bind_all("<MouseWheel>", self.on_mousewheel)    
    
        self.pop()


    def on_mousewheel(self, event):
        shift = (event.state & 0x1) != 0
        scroll = -1 if event.delta > 0 else 1
        if shift:
            self.canvas.xview_scroll(scroll, "units")
        else:
            self.canvas.yview_scroll(scroll, "units")


    def pop(self):    
        self.list = list(range(101))
        self.loop = []
     
        self.scrollbar = ttk.Scrollbar(self.frame, orient="vertical", command=self.canvas.yview)
        self.scrollbar.pack(side="right", fill="y")

        self.canvas.configure(yscrollcommand=self.scrollbar.set)
        self.canvas.bind('<Configure>', lambda e: self.canvas.configure(scrollregion = self.canvas.bbox("all")))

        self.list_frame = tk.Frame(self.canvas)

        self.canvas.create_window((0,0), window=self.list_frame, anchor="nw")

        for index, number in enumerate(self.list): 
            self.frame_2 = tk.Frame(self.list_frame)
            self.frame_2.pack(pady=(15,0), fill="x", expand="yes", side="top")

            self.text = tk.Text(self.frame_2, wrap="word", height=1)
            self.text.pack(padx=15, side="left")
            self.text.tag_configure("normal")
            self.text.insert("end", number, "normal")
            self.text.config(state="disabled")
            
            self.text.tag_configure("select", foreground="white", background="green")

            self.canc_button = tk.Button(self.frame_2, text="Cancel", command=lambda x=index: self.delete(x))
            self.canc_button.pack(side="right")

            self.loop.append((self.text, self.canc_button))


    def search(self):   
        self.found = self.entry.get()

        for self.text in self.loop:
            if self.found:
                self.text[0].tag_remove("select", 1.0,"end-1c")

                idx = '1.0'
                while 1:
                    idx = self.text[0].search(self.found, idx, nocase=1, stopindex="end")
                    if not idx: break

                    lastidx = f"{idx} {int(len(self.found))}c"
                     
                    self.text[0].tag_add("select", idx, lastidx)

                    idx = lastidx 


if __name__ == "__main__":
    root = tk.Tk()
    App(root).pack(side="top", fill="both", expand=True)
    root.mainloop()

Thank you!

CodePudding user response:

This worked for me:

There are comments where I changed something, explaining what the added code does

class App(tk.Frame):
    # (rest of code)

    def search(self):
        self.found = self.entry.get()
        
        # variable to store index of Label with found text
        scrollto = None

        for i, self.text in enumerate(self.loop):
            if self.found:

                self.text[0].tag_remove("select", 1.0, "end-1c")

                idx = '1.0'
                while 1:
                    idx = self.text[0].search(self.found, idx, nocase=1, stopindex="end")
                    if not idx: break
                    
                    # if not already found text, set index
                    if scrollto is None:
                        scrollto = i

                    lastidx = f"{idx} {int(len(self.found))}c"

                    self.text[0].tag_add("select", idx, lastidx)

                    idx = lastidx
        
        # if found any result, scroll to it
        if scrollto is not None:
            fraction = scrollto/len(self.loop)
            self.canvas.yview(tk.MOVETO, fraction)
  • Related