如何使 ttk.Treeview 的行可编辑?

Posted

技术标签:

【中文标题】如何使 ttk.Treeview 的行可编辑?【英文标题】:How to make ttk.Treeview's rows editable? 【发布时间】:2013-09-04 21:47:15 【问题描述】:

有没有办法使用带有可编辑行的 ttk Treeview?

我的意思是它应该更像一张桌子。例如,双击该项目使#0 列“可编辑”。

如果这是不可能的,任何允许鼠标选择项目的方法都可以。我在 tkdocs 或其他文档中没有发现任何提及这一点。

【问题讨论】:

我开发了一种方法来单击树视图中的单元格并在单击的单元格顶部创建一个字段,以便可以编辑单元格值。但是,我用来完成此操作的一种树视图方法仅适用于我的 Mac,但不适用于 Windows。奇怪的是,从技术上讲,它甚至不应该适用于 Mac,但它确实可以。您没有列出您的平台,但如果您使用的是 Mac(并且不会在 Windows 上运行代码),请告诉我,我将发布详细信息。 我也做过同样的事情,它适用于 linux 和 windows,我没有机会在 Mac 上尝试它。实际上,我不必使文本可编辑,我已将 Entry 小部件设为只读。因此,如果您的“仅限 Mac 的解决方案”在显示条目弹出窗口时遇到问题,也许我的解决方案可以激发您的灵感。有关代码示例,请参阅我对此问题的回答。 我也遇到过类似的限制,主要是使用 Treeview 来模拟表格,因为 tkinter/ttk 中没有类似表格的小部件。如果您没有将 Treeview 用作“树”,则可以尝试 tkintertable (code.google.com/p/tkintertable)。它本质上允许电子表格功能,并且是相对最新的、有据可查的并且功能非常丰富。 【参考方案1】:

我不知道如何使行可编辑,但要捕获单击行,您可以使用 <<TreeviewSelect>> 虚拟事件。这通过bind() 方法绑定到例程,然后您使用selection() 方法获取所选项目的ID。

这些是来自现有程序的 sn-ps,但显示了基本的调用顺序:

# in Treeview setup routine
    self.tview.tree.bind("<<TreeviewSelect>>", self.TableItemClick)

# in TableItemClick()
    selitems = self.tview.tree.selection()
    if selitems:
        selitem = selitems[0]
        text = self.tview.tree.item(selitem, "text") # get value in col #0

【讨论】:

【参考方案2】:

经过长时间的研究,我还没有找到这样的功能,所以我猜应该有。 Tk 是一个非常简单的接口,它允许程序员从基础构建“高级”功能。所以这是我想要的行为。

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)

这是一个类中的方法,它将 ttk.Treeview 组合为 self._tree

而EntryPopup则是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'

【讨论】:

这与我正在使用的过程非常相似。就我而言,我使用identify_region 来确定用户在树视图中单击的位置,并且仅在用户单击单元格时才显示输入字段。我最初使用的文档没有说明该方法仅适用于 Tk 8.6 及更高版本,因此我尝试了它。奇迹般地它在我的 Mac 上起作用了。直到它在我的客户端的 Windows 系统上引发异常,我才发现 Python 的 Tkinter 和 ttk 模块当前使用 Tk 8.5。为什么它对我有用?不知道。无论如何,你给了我一些关于如何解决它的想法。谢谢!【参考方案3】:

这只是为在构造函数中设置的指定路径创建树。您可以将事件绑定到该树上的项目。事件函数以一种可以以多种方式使用该项目的方式保留。在这种情况下,双击它会显示项目的名称。希望这对某人有所帮助。

    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()

【讨论】:

【参考方案4】:

您还可以弹出一个工具窗口,其中包含与条目一起列出的可编辑字段以更新值。此示例有一个包含三列的树视图,并且不使用子类。

将您的双击绑定到此:

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)

然后确认更改:

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

以下是删除条目的方法:

def DeleteCurrentEntry(self, treeView):
    curr = treeView.focus()

    if '' == curr: return

    treeView.delete(curr)

【讨论】:

【参考方案5】:
from tkinter import ttk
from tkinter import *

root = Tk()
columns = ("Items", "Values")
Treeview = ttk.Treeview(root, height=18, show="headings", columns=columns)  # 

Treeview.column("Items", width=200, anchor='center')
Treeview.column("Values", width=200, anchor='center')

Treeview.heading("Items", text="Items")
Treeview.heading("Values", text="Values")

Treeview.pack(side=LEFT, fill=BOTH)

name = ['Item1', 'Item2', 'Item3']
ipcode = ['10', '25', '163']
for i in range(min(len(name), len(ipcode))):
    Treeview.insert('', i, values=(name[i], ipcode[i]))


def treeview_sort_column(tv, col, reverse):
    l = [(tv.set(k, col), k) for k in tv.get_children('')]
    l.sort(reverse=reverse)
    for index, (val, k) in enumerate(l):
        tv.move(k, '', index)
        tv.heading(col, command=lambda: treeview_sort_column(tv, col, not reverse))


def set_cell_value(event):
    for item in Treeview.selection():
        item_text = Treeview.item(item, "values")
        column = Treeview.identify_column(event.x)
        row = Treeview.identify_row(event.y)
    cn = int(str(column).replace('#', ''))
    rn = int(str(row).replace('I', ''))
    entryedit = Text(root, width=10 + (cn - 1) * 16, height=1)
    entryedit.place(x=16 + (cn - 1) * 130, y=6 + rn * 20)

    def saveedit():
        Treeview.set(item, column=column, value=entryedit.get(0.0, "end"))
        entryedit.destroy()
        okb.destroy()

    okb = ttk.Button(root, text='OK', width=4, command=saveedit)
    okb.place(x=90 + (cn - 1) * 242, y=2 + rn * 20)


def newrow():
    name.append('to be named')
    ipcode.append('value')
    Treeview.insert('', len(name) - 1, values=(name[len(name) - 1], ipcode[len(name) - 1]))
    Treeview.update()
    newb.place(x=120, y=(len(name) - 1) * 20 + 45)
    newb.update()


Treeview.bind('<Double-1>', set_cell_value)
newb = ttk.Button(root, text='new item', width=20, command=newrow)
newb.place(x=120, y=(len(name) - 1) * 20 + 45)

for col in columns:
    Treeview.heading(col, text=col, command=lambda _col=col: treeview_sort_column(Treeview, _col, False))


root.mainloop()

在做我的项目时经过这么多研究得到了这个代码,它对我帮助很大。 双击要编辑的元素,进行所需的更改,然后单击“确定”按钮 我想这正是你想要的

#python #tkinter #treeview #editablerow

New row Editable row

【讨论】:

以上是关于如何使 ttk.Treeview 的行可编辑?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Treeview 中编辑标题的样式(Python ttk)

DEV GridControl怎样使GridView中满足某个条件的行可编辑,其余行不可编辑?

ttk.Treeview - 无法更改行高

如何在 tkinter.ttk Treeview 上完全更改背景颜色

ExtJs 属性网格 - 选定的行可编辑?

如何使用 Tkinter 清除整个 Treeview