Button Bind in Tkinter.ttk Treeview in another Class

Asked

Viewed 669 times

0

Problem

How to create a button bind class in ttk. Treeview?

Sample Code

When programming the main class, which defines Treeview and then create another class that defines bindings and events. It is not possible to create these bindings that are in another class, only in the same. As the following example:

import Tkinter
import ttk

class clique_bind(ttk.Treeview):
    def __init__(self, *args, **kwargs):
        ttk.Treeview.__init__(self, *args, **kwargs)
        print self.widgetName
        # create the entry on init but does no show it
        self.bind("<Key>", self._qual_tecla)

    def _qual_tecla(self, event):
        print("Tecla: " + event.keysym)

class principal(Tkinter.Tk):
    def __init__(self, parent):
        Tkinter.Tk.__init__(self, parent)
        self.parent = parent
        tree = ttk.Treeview()

        tree["columns"] = ("one", "two")
        tree.column("one", width=100)
        tree.column("two", width=100)
        tree.heading("one", text="coulmn A")
        tree.heading("two", text="column B")
        tree.insert("", 0, text="Line 1", values=("1A", "1b"))
        id2 = tree.insert("", 1, "dir2", text="Dir 2")
        tree.insert(id2, "end", "dir 2", text="sub dir 2", values=("2A", "2B"))
        tree.insert("", 3, "dir3", text="Dir 3")
        tree.insert("dir3", 3, text=" sub dir 3", values=("3A", " 3B"))
        tree.pack()

        tree.bind('<Button-3>', self._teste_direito)
        clique_bind(tree)

    def _teste_direito(self, event):
        print("Direito")

if __name__ == "__main__":
    App = principal(None)
    App.mainloop()

When performing the right mouse click bind on the main class, it is possible to check print("Direito"). But by creating the class clique_bind, where capturing keyboard buttons in another Class is not possible.

Complete Code

The complete code of what is being programmed, to better understand the problem.

Where the code to list directories used is based in the Tkinter documentation and searching on treeview in this answer. Some changes were made.

"""A directory browser using Ttk Treeview.

Based on the demo found in Tk 8.5 library/demos/browse
https://svn.python.org/projects/stackless/trunk/Demo/tkinter/ttk/dirbrowser.py

Search based on: https://stackoverflow.com/a/17271593/7690982
"""


import Tkinter
import ttk
import os
import glob

class SearchableTreeview(ttk.Treeview):

    def __init__(self, *args, **kwargs):
        ttk.Treeview.__init__(self, *args, **kwargs)
        # create the entry on init but does no show it
        self._toSearch = Tkinter.StringVar()
        self.focus()
        self.entry = Tkinter.Entry(self, textvariable=self._toSearch)
        self.bind("<Key>", self._keyOnTree)
        self._toSearch.trace_variable("w", self._search)
        self.entry.bind("<Return>", self._hideEntry)
        self.entry.bind("<Escape>", self._hideEntry)
        print("init")

    def _keyOnTree(self, event):
        print("keyontree")
        self.entry.place(relx=1, anchor=Tkinter.NE)
        if event.char.isalpha():
            self.entry.insert(Tkinter.END, event.char)
        self.entry.focus_set()

    def _hideEntry(self, event):
        print("hideentry")
        self.entry.delete(0, Tkinter.END)
        self.entry.place_forget()
        self.focus_set()

    def _search(self, *args):
        print("search")
        pattern = self._toSearch.get()
        #avoid search on empty string
        if len(pattern) > 0:
            self.search(pattern)

    def search(self, pattern, item=''):
        children = self.get_children(item)
        for child in children:
            text = self.item(child, 'text')
            if text.lower().startswith(pattern.lower()):
                self.selection_set(child)
                self.see(child)
                return True
            else:
                res = self.search(pattern, child)
                if res:
                    return True


class ListagemDir(Tkinter.Tk):
    def __init__(self, parent):
        Tkinter.Tk.__init__(self, parent)
        self.parent = parent
        self.DirTree()

    def DirTree(self):
        vsb = ttk.Scrollbar(orient="vertical")
        hsb = ttk.Scrollbar(orient="horizontal")

        tree = ttk.Treeview(columns=("fullpath", "type", "size"),
                            displaycolumns="size", yscrollcommand=lambda f, l: self.autoscroll(vsb, f, l),
                            xscrollcommand=lambda f, l: self.autoscroll(hsb, f, l))

        vsb['command'] = tree.yview
        hsb['command'] = tree.xview

        tree.heading("#0", text="Directory Structure", anchor='w')
        tree.heading("size", text="File Size", anchor='w')
        tree.column("size", stretch=0, width=100)

        self.populate_roots(tree)
        tree.bind('<<TreeviewOpen>>', self.update_tree)
        tree.bind('<Double-Button-1>', self.change_dir)

        # Arrange the tree and its scrollbars in the toplevel
        tree.grid(column=0, row=0, sticky='nswe')
        vsb.grid(column=1, row=0, sticky='ns')
        hsb.grid(column=0, row=1, sticky='ew')
        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(0, weight=1)

        tree.bind("<Button-3>", self._press3)

        searchable = SearchableTreeview(tree)

    def populate_tree(self, tree, node):
        if tree.set(node, "type") != 'directory':
            return

        path = tree.set(node, "fullpath")
        tree.delete(*tree.get_children(node))

        parent = tree.parent(node)
        special_dirs = [] if parent else glob.glob('.') + glob.glob('..')

        for p in special_dirs + os.listdir(path):
            ptype = None
            p = os.path.join(path, p).replace('\\', '/')
            if os.path.isdir(p): ptype = "directory"
            elif os.path.isfile(p): ptype = "file"

            fname = os.path.split(p)[1]
            id = tree.insert(node, "end", text=fname, values=[p, ptype])

            if ptype == 'directory':
                if fname not in ('.', '..'):
                    tree.insert(id, 0, text="dummy")
                    tree.item(id, text=fname)
            elif ptype == 'file':
                size = os.stat(p).st_size
                tree.set(id, "size", "%d bytes" % size)


    def populate_roots(self, tree):
        dir = os.path.abspath('.').replace('\\', '/')
        node = tree.insert('', 'end', text=dir, values=[dir, "directory"])
        self.populate_tree(tree, node)

    def update_tree(self, event):
        tree = event.widget
        self.populate_tree(tree, tree.focus())

    def change_dir(self, event):
        tree = event.widget
        node = tree.focus()
        if tree.parent(node):
            path = os.path.abspath(tree.set(node, "fullpath"))
            if os.path.isdir(path):
                os.chdir(path)
                tree.delete(tree.get_children(''))
                self.populate_roots(tree)

    def autoscroll(self, sbar, first, last):
        """Hide and show scrollbar as needed."""
        first, last = float(first), float(last)
        if first <= 0 and last >= 1:
            sbar.grid_remove()
        else:
            sbar.grid()
        sbar.set(first, last)

    def _press3(self, event):
        print("Tipo de Evento: " + event.keysym)


if __name__ == "__main__":
    App = ListagemDir(None)
    App.mainloop()

Trying

It was possible to perform the complete code in the same class, but I’m not getting it in different classes. Could be some error of class fundamentals or even Tkinter syntax.

"""A directory browser using Ttk Treeview.

Based on the demo found in Tk 8.5 library/demos/browse
https://svn.python.org/projects/stackless/trunk/Demo/tkinter/ttk/dirbrowser.py

Search based on: https://stackoverflow.com/a/17271593/7690982
"""


import os
import glob
import Tkinter
import ttk

class ListagemDir(Tkinter.Tk):
    def __init__(self, parent):
        Tkinter.Tk.__init__(self, parent)
        self.parent = parent
        self.DirTree()

    def DirTree(self):
        vsb = ttk.Scrollbar(orient="vertical")
        hsb = ttk.Scrollbar(orient="horizontal")

        tree = ttk.Treeview(columns=("fullpath", "type", "size"),
                            displaycolumns="size", yscrollcommand=lambda f, l: self.autoscroll(vsb, f, l),
                            xscrollcommand=lambda f, l: self.autoscroll(hsb, f, l))

        vsb['command'] = tree.yview
        hsb['command'] = tree.xview

        tree.heading("#0", text="Directory Structure", anchor='w')
        tree.heading("size", text="File Size", anchor='w')
        tree.column("size", stretch=0, width=100)

        self.populate_roots(tree)
        tree.bind('<<TreeviewOpen>>', self.update_tree)
        tree.bind('<Double-Button-1>', self.change_dir)

        # Arrange the tree and its scrollbars in the toplevel
        tree.grid(column=0, row=0, sticky='nswe')
        vsb.grid(column=1, row=0, sticky='ns')
        hsb.grid(column=0, row=1, sticky='ew')
        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(0, weight=1)

        tree.bind("<Button-3>", self._press3)

        self._toSearch = Tkinter.StringVar()
        tree.focus()
        self.entry = Tkinter.Entry(tree, textvariable = self._toSearch)
        tree.bind("<Key>", self._keyOnTree)
        self._toSearch.trace_variable("w", self._search)
        self.entry.bind("<Return>", self._hideEntry)
        self.entry.bind("<Escape>", self._hideEntry)
        print("init")

    def _keyOnTree(self, event):
        print("keyontree")
        self.entry.place(relx=1, anchor=Tkinter.NE)
        if event.char.isalpha():
            self.entry.insert(Tkinter.END, event.char)
        self.entry.focus_set()

    def _hideEntry(self, event):
        print("hideentry")
        self.entry.delete(0, Tkinter.END)
        self.entry.place_forget()
        self.focus_set()

    def _search(self, *args):
        print("search")
        pattern = self._toSearch.get()
        #avoid search on empty string
        if len(pattern) > 0:
            self.search(pattern)

    def search(self, pattern, item=''):
        widgets_children = self.winfo_children()
        #print widgets_children
        for widget_child in widgets_children:
            if isinstance(widget_child, ttk.Treeview):
                tree = widget_child
        children = tree.get_children(item)
        for child in children:
            text = tree.item(child, 'text')
            if text.lower().startswith(pattern.lower()):
                tree.selection_set(child)
                tree.see(child)
                return True
            else:
                res = self.search(pattern, child)
                if res:
                    return True

    def populate_tree(self, tree, node):
        if tree.set(node, "type") != 'directory':
            return

        path = tree.set(node, "fullpath")
        tree.delete(*tree.get_children(node))

        parent = tree.parent(node)
        special_dirs = [] if parent else glob.glob('.') + glob.glob('..')

        for p in special_dirs + os.listdir(path):
            ptype = None
            p = os.path.join(path, p).replace('\\', '/')
            if os.path.isdir(p): ptype = "directory"
            elif os.path.isfile(p): ptype = "file"

            fname = os.path.split(p)[1]
            id = tree.insert(node, "end", text=fname, values=[p, ptype])

            if ptype == 'directory':
                if fname not in ('.', '..'):
                    tree.insert(id, 0, text="dummy")
                    tree.item(id, text=fname)
            elif ptype == 'file':
                size = os.stat(p).st_size
                tree.set(id, "size", "%d bytes" % size)


    def populate_roots(self, tree):
        dir = os.path.abspath('.').replace('\\', '/')
        node = tree.insert('', 'end', text=dir, values=[dir, "directory"])
        self.populate_tree(tree, node)

    def update_tree(self, event):
        tree = event.widget
        self.populate_tree(tree, tree.focus())

    def change_dir(self, event):
        tree = event.widget
        node = tree.focus()
        if tree.parent(node):
            path = os.path.abspath(tree.set(node, "fullpath"))
            if os.path.isdir(path):
                os.chdir(path)
                tree.delete(tree.get_children(''))
                self.populate_roots(tree)

    def autoscroll(self, sbar, first, last):
        """Hide and show scrollbar as needed."""
        first, last = float(first), float(last)
        if first <= 0 and last >= 1:
            sbar.grid_remove()
        else:
            sbar.grid()
        sbar.set(first, last)

    def _press3(self, event):
        print("Tipo de Evento: " + event.keysym)


if __name__ == "__main__":
    App = ListagemDir(None)
    App.mainloop()

1 answer

1


The question is confused - once you put in the code and don’t say exactly what doesn’t work.

But there is no magic, or special syntax of Tkinter - it’s just Python: The methods bind Tkinter ask for a parameter with an event name and a "chargeable" object (callable) - it is obvious that if the code tries to use always self.nome_do_metodo in the second parameter, the code itself is limiting the event Handler to class methods: all you need is to put a reference to the method you want to call -

So in the case of your simplified example - where do you do:

class clique_bind(ttk.Treeview):
    def __init__(self, *args, **kwargs):
        ttk.Treeview.__init__(self, *args, **kwargs)
        print self.widgetName
        # create the entry on init but does no show it
        self.bind("<Key>", self._qual_tecla)

    def _qual_tecla(self, event):
        print("Tecla: " + event.keysym)

If your intention is that method _qual_tecla is associated with a click in the class principal, within the function __init__ after assigning an instance of clique_bind to the attribute self.tree, just do:

self.bind("<Key>", self.tree._qual_tecla) 

There is no secret: what you want to call is the method of the instance of the other class. What doesn’t, is, due to the way the question is set, make sure that’s what you want.

In addition to answering the question, there are style and practice questions in the code that could be commented on, but perhaps the most important is the suggestion to use Python 3 instead of Python 2. Python 2 is an incompatible version of the language that has not been updated for several years, And in less than 18 months, you won’t have any official support. All that would change in that code would be to import tkinter instead of Tkinter, and use the print as a function, and not as a command.

  • Therefore, if I want to use Treeview as an instance of another class. Is it not possible this way? For within the class clique_bind, this line returns True: isinstance(self, ttk.Treeview). So I don’t understand why I can’t use self to call the instance method of the other class. It’s really not possible to call?

  • 1

    I think you’re confusing the meaning of "instance". At least I’m not understanding what you want to ask, since the phrase "using Treeview as an instance of another class" makes no sense. Check here to make sure we’re talking about the same thing: (the page is from Java, but the definitions are correct for Object Orientation and are the same as in Python: http://www.linhadecodigo.com.br/artigo/pequeno-dicionario-basico-de-orientacao-a-objetos-em-java.aspx )

Browser other questions tagged

You are not signed in. Login or sign up in order to post.