Button Bind in Tkinter.ttk TreeView in another Class

1

Problem

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

Example Code

When programming the main class, it defines the TreeView and then creates another class that defines bindings and events. It is not possible to create these bindings that are in another class, just 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 you bind the right-click on the main class, you can check print("Direito") . But when creating class clique_bind , where you capture the 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 and search in treeview in this answer . Some changes have been 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()

Attempt

It was possible to perform the complete code in the same class, but I'm not getting into different classes. It may be some class fundamentals error 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()
    
asked by anonymous 16.07.2018 / 14:18

1 answer

1

The question is confusing - once you put the code and it does not say exactly what does not work.

But there is no magic, or special Tkinter syntax - it's just Python:% tkinter% methods require a parameter with an event name and a callable object - it's obvious that if the code tries always use bind in the second parameter, the code itself is limiting the event handler to class methods: all you need to do is put a reference to the method you want to call -

So, in the case of your simplified example - where 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 you want the self.nome_do_metodo method to be associated with a click on the _qual_tecla class, within the principal fault function, after assigning an instance of __init__ to the clique_bind attribute, just do:

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

There is no secret: what you want to call is the instance method of the other class. What it does not, is, due to the way the question is, make sure this is what you want.

In addition to answering the question, there are questions of style and practice in the code that could be commented out, 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 already has not had updates for several years, and in less than 18 months will not have any more official support. All that would change in this code would be to import self.tree instead of tkinter , and use Tkinter as a function, not as a command.

    
18.07.2018 / 19:04