How to make ttk.Treeview's rows editable? How to make ttk.Treeview's rows editable? tkinter tkinter

How to make ttk.Treeview's rows editable?


After long research I haven't found such feature so I guess there's any. Tk is very simple interface, which allows programmer to build 'high-level' features from the basics. So my desired behaviour this way.

def onDoubleClick(self, event):    ''' Executed, when a row is double-clicked. Opens     read-only EntryPopup above the item's column, so it is possible    to select text '''    # close previous popups    # self.destroyPopups()    # what row and column was clicked on    rowid = self._tree.identify_row(event.y)    column = self._tree.identify_column(event.x)    # get column position info    x,y,width,height = self._tree.bbox(rowid, column)    # y-axis offset    # pady = height // 2    pady = 0    # place Entry popup properly             text = self._tree.item(rowid, 'text')    self.entryPopup = EntryPopup(self._tree, rowid, text)    self.entryPopup.place( x=0, y=y+pady, anchor=W, relwidth=1)

This is method within a class which composes ttk.Treeview as self._tree

And EntryPopup is then very simple sub-class of Entry:

class EntryPopup(Entry):    def __init__(self, parent, iid, text, **kw):        ''' If relwidth is set, then width is ignored '''        super().__init__(parent, **kw)        self.tv = parent        self.iid = iid        self.insert(0, text)         # self['state'] = 'readonly'        # self['readonlybackground'] = 'white'        # self['selectbackground'] = '#1BA1E2'        self['exportselection'] = False        self.focus_force()        self.bind("<Return>", self.on_return)        self.bind("<Control-a>", self.select_all)        self.bind("<Escape>", lambda *ignore: self.destroy())    def on_return(self, event):        self.tv.item(self.iid, text=self.get())        self.destroy()    def select_all(self, *ignore):        ''' Set selection on the whole text '''        self.selection_range(0, 'end')        # returns 'break' to interrupt default key-bindings        return 'break'


You could also pop up a tool window with the editable fields listed with Entries to update the values. This example has a treeview with three columns, and does not use subclasses.

Bind your double click to this:

def OnDoubleClick(self, treeView):    # First check if a blank space was selected    entryIndex = treeView.focus()    if '' == entryIndex: return    # Set up window    win = Toplevel()    win.title("Edit Entry")    win.attributes("-toolwindow", True)    ####    # Set up the window's other attributes and geometry    ####    # Grab the entry's values    for child in treeView.get_children():        if child == entryIndex:            values = treeView.item(child)["values"]            break    col1Lbl = Label(win, text = "Value 1: ")    col1Ent = Entry(win)    col1Ent.insert(0, values[0]) # Default is column 1's current value    col1Lbl.grid(row = 0, column = 0)    col1Ent.grid(row = 0, column = 1)    col2Lbl = Label(win, text = "Value 2: ")    col2Ent = Entry(win)    col2Ent.insert(0, values[1]) # Default is column 2's current value    col2Lbl.grid(row = 0, column = 2)    col2Ent.grid(row = 0, column = 3)    col3Lbl = Label(win, text = "Value 3: ")    col3Ent = Entry(win)    col3Ent.insert(0, values[2]) # Default is column 3's current value    col3Lbl.grid(row = 0, column = 4)    col3Ent.grid(row = 0, column = 5)    def UpdateThenDestroy():        if ConfirmEntry(treeView, col1Ent.get(), col2Ent.get(), col3Ent.get()):            win.destroy()    okButt = Button(win, text = "Ok")    okButt.bind("<Button-1>", lambda e: UpdateThenDestroy())    okButt.grid(row = 1, column = 4)    canButt = Button(win, text = "Cancel")    canButt.bind("<Button-1>", lambda c: win.destroy())    canButt.grid(row = 1, column = 5)

Then confirm the changes:

def ConfirmEntry(self, treeView, entry1, entry2, entry3):    ####    # Whatever validation you need    ####    # Grab the current index in the tree    currInd = treeView.index(treeView.focus())    # Remove it from the tree    DeleteCurrentEntry(treeView)    # Put it back in with the upated values    treeView.insert('', currInd, values = (entry1, entry2, entry3))    return True

Here's how to delete an entry:

def DeleteCurrentEntry(self, treeView):    curr = treeView.focus()    if '' == curr: return    treeView.delete(curr)


This is just for creating a tree for the specified path that is set in the constructor. you can bind your event to your item on that tree. The event function is left in a way that the item could be used in many ways. In this case, it will show the name of the item when double clicked on it. Hope this helps somebody.

    import ttk    from Tkinter import*    import os*    class Tree(Frame):    def __init__(self, parent):        Frame.__init__(self, parent)        self.parent = parent        path = "/home/...."        self.initUI(path)    def initUI(self, path):        self.parent.title("Tree")        self.tree = ttk.Treeview(self.parent)        self.tree.bind("<Double-1>", self.itemEvent)        yScr = ttk.Scrollbar(self.tree, orient = "vertical", command = self.tree.yview)        xScr = ttk.Scrollbar(self.tree, orient = "horizontal", command = self.tree.xview)        self.tree.configure(yscroll = yScr.set, xScroll = xScr.set)        self.tree.heading("#0", text = "My Tree", anchor = 'w')        yScr.pack(side = RIGHT, fill = Y)        pathy = os.path.abspath(path)         rootNode = self.tree.insert('', 'end', text = pathy, open = True)        self.createTree(rootNode, pathy)        self.tree.pack(side = LEFT, fill = BOTH, expand = 1, padx = 2, pady = 2)        self.pack(fill= BOTH, expand = 1)     def createTree(self, parent, path)        for p in os.listdir(path)            pathy = os.path.join(path, p)            isdir = os.path.isdir(pathy)            oid = self.tree.insert(parent, 'end' text = p, open = False)            if isdir:               self.createTree(oid, pathy)    def itemEvent(self, event):        item = self.tree.selection()[0] # now you got the item on that tree        print "you clicked on", self.tree.item(item,"text")    def main():        root = Tk.Tk()        app = Tree(root)        root.mainloop()    if __name__ == '__main__'       main()