python 如何使ttk.treeview的行可编辑?

7xzttuei  于 2023-04-19  发布在  Python
关注(0)|答案(8)|浏览(488)

有没有什么方法可以在可编辑的行中使用ttk Treeview?
我的意思是它应该更像一个表。例如,双击项目使#0列'可编辑'。
如果这是不可能的,任何方式允许鼠标选择的项目将是罚款。我还没有发现任何提到这在tkdocs或其他文件。

h22fl7wq

h22fl7wq1#

经过长时间的研究,我还没有发现这样的功能,所以我想有任何。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'
h4cxqtbf

h4cxqtbf2#

您也可以弹出一个工具窗口,其中列出了可编辑的字段和条目,以更新值。本示例有一个三列的树视图,并且没有使用子类。
将您的双击绑定到以下内容:

def OnDoubleClick(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(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(treeView):
    curr = treeView.focus()

    if '' == curr: return

    treeView.delete(curr)
ztmd8pv5

ztmd8pv53#

我已经尝试了@dakov解决方案,但它不适合我,因为我的treeView有多个列,还有一些原因。

class Tableview(ttk.Treeview):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        tv.bind("<Double-1>", lambda event: self.onDoubleClick(event))

    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
        try:  # in case there was no previous popup
            self.entryPopup.destroy()
        except AttributeError:
            pass

        # what row and column was clicked on
        rowid = self.identify_row(event.y)
        column = self.identify_column(event.x)

        # handle exception when header is double click
        if not rowid:
            return

        # get column position info
        x,y,width,height = self.bbox(rowid, column)

        # y-axis offset
        pady = height // 2

        # place Entry popup properly
        text = self.item(rowid, 'values')[int(column[1:])-1]
        self.entryPopup = EntryPopup(self, rowid, int(column[1:])-1, text)
        self.entryPopup.place(x=x, y=y+pady, width=width, height=height, anchor='w')

EntryPopup类

class EntryPopup(ttk.Entry):
    def __init__(self, parent, iid, column, text, **kw):
        ttk.Style().configure('pad.TEntry', padding='1 1 1 1')
        super().__init__(parent, style='pad.TEntry', **kw)
        self.tv = parent
        self.iid = iid
        self.column = column

        self.insert(0, text) 
        # self['state'] = 'readonly'
        # self['readonlybackground'] = 'white'
        # self['selectbackground'] = '#1BA1E2'
        self['exportselection'] = False

        self.focus_force()
        self.select_all()
        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):
        rowid = self.tv.focus()
        vals = self.tv.item(rowid, 'values')
        vals = list(vals)
        vals[self.column] = self.get()
        self.tv.item(rowid, values=vals)
        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'
flmtquvp

flmtquvp4#

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 rowEditable row

06odsfpq

06odsfpq5#

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

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

kmpatx3s6#

你不应该这样做手动有准备使用包有这个功能和许多更如tkintertable它有一些疯狂的功能
还有pygubu-editable-treeview,如果你对pygubu感兴趣,
至于你不应该自己编写代码的原因,为了做一个好的treeview,你需要构建更多的功能,使你的gui更容易使用,但是这样的功能需要数百行代码来创建。(需要很长时间才能正确)除非你正在制作一个自定义的treeview小部件,否则不值得付出努力。

hfwmuf9z

hfwmuf9z7#

我已经调整了@DCOPTimDowd代码,使编辑单元格在视觉上更吸引人和更容易。
改进:

  • 可以分配不可编辑的列
  • 只需双击单元格即可编辑它。
  • 更有视觉吸引力。
class PopupEntry(tk.Entry):
    def __init__(self, parent, x, y, textvar,width = 10 ,entry_value='', text_justify = 'left', ):
        super().__init__(parent, relief = 'flat', justify = text_justify,bg='white', textvariable=textvar, font= "sublime 10")
        self.place(x=x, y=y, width=width)
        
        self.textvar = textvar
        self.textvar.set(entry_value)
        self.focus_set()
        self.select_range(0, 'end')
        # move cursor to the end
        self.icursor('end')

        self.wait_var = tk.StringVar(master=self)
        self._bind_widget()

        self.entry_value = entry_value
        self.wait_window()
    
    def _bind_widget(self):
        self.bind("<Return>", self.retrive_value)
        self.bind('<FocusOut>', self.retrive_value)

    def retrive_value(self, e):
        value = self.textvar.get()
        self.destroy()
        self.textvar.set(value)
        
        
class EditableTreeview(ttk.Treeview):
    def __init__(self, parent, columns, show, bind_key,data:list, non_editable_columns = None):
        super().__init__(parent, columns=columns, show=show)
        self.parent = parent
        self.column_name = columns
        self.data = data
        self.bind_key = bind_key
        self.non_editable_columns = non_editable_columns

        self.set_primary_key_column_attributes()
        self.set_headings()
        self.insert_data()
        self.set_edit_bind_key()
    
    def set_primary_key_column_attributes(self):
        self.column("#0",width=100,stretch=1)

    def set_headings(self):
        for i in self.column_name:
            self.heading(column=i, text=i)

    def insert_data(self):
        for values in self.data:
            self.insert('', tk.END, values=values)
    
    def set_edit_bind_key(self):
        self.bind('<Double Button-1>', self.edit)

    def get_absolute_x_cord(self):
        rootx = self.winfo_pointerx()
        widgetx = self.winfo_rootx()

        x = rootx - widgetx

        return x

    def get_absolute_y_cord(self):
        rooty = self.winfo_pointery()
        widgety = self.winfo_rooty()

        y = rooty - widgety

        return y
    
    def get_current_column(self):
        pointer = self.get_absolute_x_cord()
        return self.identify_column(pointer)

    def get_cell_cords(self,row,column):
        return self.bbox(row, column=column)
    
    def get_selected_cell_cords(self):
        row = self.focus()
        column = self.get_current_column()
        return self.get_cell_cords(row = row, column = column)

    def update_row(self, values):
        current_row = self.focus()

        currentindex = self.index(self.focus())

        self.delete(current_row)
        
        # Put it back in with the upated values
        self.insert('', currentindex, values = values)

    def check_region(self):
        result = self.identify_region(x=(self.winfo_pointerx() - self.winfo_rootx()), y=(self.winfo_pointery()  - self.winfo_rooty()))
        print(result)
        if result == 'cell':return True
        else: return False

    def check_non_editable(self):
        if self.get_current_column() in self.non_editable_columns:return False
        else: return True

    def edit(self, e):
        if self.check_region() == False: return
        elif self.check_non_editable() == False: return
        current_row_values = list(self.item(self.focus(),'values'))
        current_column = int(self.get_current_column().replace("#",''))-1
        current_cell_value = current_row_values[current_column]

        entry_cord = self.get_selected_cell_cords()
        entry_x = entry_cord[0]
        entry_y = entry_cord[1]
        entry_w = entry_cord[2]
        entry_h = entry_cord[3]

        entry_var = tk.StringVar()
        
        PopupEntry(self, x=entry_x, y=entry_y, width=entry_w,entry_value=current_cell_value, textvar= entry_var, text_justify='left')

        if entry_var != current_cell_value:
            current_row_values[current_column] = entry_var.get()
            self.update_row(values=current_row_values)

改进总是受欢迎的:github file
截图:

  • 双击单元格之前。x1c 0d1x
  • 双击单元格后。
prdp8dxp

prdp8dxp8#

我不知道如何使行可编辑,但要捕获单击行,您可以使用<<TreeviewSelect>>虚拟事件。这将通过bind()方法绑定到例程,然后使用selection()方法获取所选项目的id。
这些是来自现有程序的片段,但显示了调用的基本序列:

# 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

相关问题