问题描述
为了编辑 ttk.Treeview
的元素,我搜索了每当用户双击它时都会从树视图中弹出一个 ttk.Entry
和一个 ttk.ComboBox
。
为此,我找到了一个 tkk.Entry
派生类,我对其进行了一些改编以满足我的需要(也可能是最初编写它的人没有完成它)。
看来这个工作正常。
然后我从 ttk.ComboBox
派生了一个类,灵感来自前一个。
这是代码,包含两个类、树视图和一个主窗口,可让一切正常工作。
import tkinter as tk
from tkinter import ttk
class EntryPopup(ttk.Entry):
def __init__(self,parent,iid,col,text,**kw):
super().__init__(parent,**kw)
self.tv = parent
self.iid = iid
self.col = col
self.insert(0,text)
self.select_range(0,tk.END)
self.focus_force()
self.bind("<Return>",self.on_return)
self.bind("<Escape>",lambda *ignore: self.destroy())
self.bind("<FocusOut>",self.on_return)
def on_return(self,event):
if self.col == '#0':
self.tv.item(self.iid,text=self.get())
else:
self.tv.set(self.iid,self.col,self.get())
self.destroy()
class ComboPopup(ttk.ComboBox):
def __init__(self,**kw)
self.tv = parent
self.iid = iid
self.col = col
self.set(self.tv.set(iid,col))
self.focus()
self.bind("<Escape>",self.focus_out())
self.bind("<<ComboBoxSelected>>",self.new_selection())
def new_selection(self):
self.tv.set(self.iid,self.get())
def focus_out(self):
self.new_selection()
self.destroy()
class App(ttk.Frame):
def __init__(self,*args,**kwargs):
ttk.Frame.__init__(self,parent)
self.parent = parent
self.entryPopup = None
# Create Treeview
self.tree = ttk.Treeview(self.parent,column=('A','B'),selectmode='browse',height=7)
self.tree.pack(expand=True,fill='both',side='top')
# Setup column heading
self.tree.heading('#0',text=' Items',anchor='center')
self.tree.heading('#1',text=' A',anchor='center')
self.tree.heading('#2',text=' B',anchor='center')
self.tree.bind("<Double-Button>",self.on_double_click)
self.tree.insert('','end',text="First item",value=("A's value","B's value"))
self.tree.insert('',text="Second item","B's value"))
def on_double_click(self,event):
# What row and column was clicked on
rowid = self.tree.identify_row(event.y)
column = self.tree.identify_column(event.x)
# get cell position info
x,y,width,height = self.tree.bBox(rowid,column)
# y-axis offset
pady = height / 2
# place Entry popup properly
if column == '#0':
text = self.tree.item(rowid,'text')
self.entryPopup = EntryPopup(self.tree,rowid,column,text)
else:
self.entryPopup = ComboPopup(self.tree,values=("A","b","3"))
self.entryPopup.place(x=x,y=y + pady,width=width,height=1.25 * height,anchor='w')
if __name__ == '__main__':
root = tk.Tk()
root.geometry('900x600+300+300')
App(root)
root.mainloop()
因此,如果用户双击第一列的单元格,则会在那里生成一个 EntryPopup
对象,如果在另一列中,则它是一个 ComboPopup
对象。
在 EntryPopup
对象中,在 <Escape>
事件中,对象只是被销毁,没有任何改变;在 <Return>
或 <FocusOut>
上所做的任何更改都会保留。
我认为在 <FocusOut>
上也执行此操作很重要的原因是当用户进入另一个单元格(例如,通过单击它)时会发生这种情况。
现在在 ComboPopup
对象中,<FocusOut>
事件是一个问题。
显然,当用户首先尝试访问组合框时会触发此事件,这引发了我无法理解的错误:
文件“/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/tkinter/__init__.py”,第 1340 行,在 _bind 中 return self.tk.call(what + (sequence,)) _tkinter.TclError: 错误的窗口路径名“.!treeview.!combopopup”
另一方面,如果我将 <FocusOut>
的行为设置为与 <Escape>
一样,则不会出现错误,但我也永远不会在组合框中保留用户的选择。
最后,如果我将它设置为 <Return>
事件,则不会出现错误并且选择的值被保留,但组合框仍然存在,在这种情况下,我可以为每个单元格设置一个组合框,而它们只会去如果触发 <Escape>
事件,则再次丢失引入的数据。
我已经解决这个问题有一段时间了,我认为我自己无法找到解决方案。
也许有一个简单的解决方法?
解决方法
我想我有一个令人满意的方法来解决我在这篇文章中遇到的代码问题。
我的意思是,我没有完全修复 <FocusOut>
行为,而是将其替换为从树视图调用任何鼠标单击或双击。
请注意,如何控制同一个小部件上的“单击”和“双击”事件并不明显,因为在双击中有两次单击。
幸运的是,我找到了 this answer which addresses that problem(我觉得奇怪的是它在我的之前没有任何赞成票)。
上述所有内容都适用于这种新方法,结果代码为
import tkinter as tk
from tkinter import ttk
class EntryPopup(ttk.Entry):
def __init__(self,parent,iid,col,text,**kw):
super().__init__(parent,**kw)
self.tv = parent
self.iid = iid
self.col = col
self.insert(0,text)
self.select_range(0,tk.END)
self.focus_force()
self.bind("<Return>",self.on_return)
self.bind("<Escape>",self.on_escape)
def on_return(self,event):
self.close()
def on_escape(self,event):
self.destroy()
def close(self):
if self.col == '#0':
self.tv.item(self.iid,text=self.get())
else:
self.tv.set(self.iid,self.col,self.get())
self.destroy()
class ComboPopup(ttk.Combobox):
def __init__(self,**kw)
self.tv = parent
self.iid = iid
self.col = col
self.old_text = self.tv.set(iid,col)
self.set(self.old_text)
self.focus()
self.bind("<Escape>",self.on_escape)
self.bind("<<ComboboxSelected>>",self.new_selection)
self.bind("<Return>",self.on_return)
def new_selection(self,event=None):
self.tv.set(self.iid,self.get())
def on_return(self,event):
self.tv.set(self.iid,self.old_text)
self.destroy()
def close(self):
self.new_selection()
self.destroy()
class App(ttk.Frame):
def __init__(self,*args,**kwargs):
ttk.Frame.__init__(self,parent)
self.parent = parent
self.cellPopup = None
self.single_click = False
# Create Treeview
self.tree = ttk.Treeview(self.parent,column=('A','B'),selectmode='browse',height=7)
self.tree.pack(expand=True,fill='both',side='top')
# Setup column heading
self.tree.heading('#0',text=' Items',anchor='center')
self.tree.heading('#1',text=' A',anchor='center')
self.tree.heading('#2',text=' B',anchor='center')
self.tree.bind("<Double-Button-1>",lambda event: self.on_click(event,101))
self.tree.bind("<Button-1>",1))
self.tree.insert('','end',text="First item",value=("A's value 1","B's value 1"))
self.tree.insert('',text="Second item",value=("A's value 2","B's value 2"))
def destroy_cell_popup(self):
if self.single_click:
self.single_click = False
if self.cellPopup and self.cellPopup.winfo_exists():
self.cellPopup.close()
self.cellPopup = None
def on_click(self,event,extra=None):
""" Executed,when a row is single or double-clicked.
Opens EntryPopup or ComboPopup above the item's column,so it is possible to select text """
if extra == 1:
self.single_click = True
self.parent.after(200,self.destroy_cell_popup)
return
elif extra == 101:
self.single_click = False
if self.cellPopup and self.cellPopup.winfo_exists():
self.cellPopup.close()
self.cellPopup = None
# What row and column was clicked on
rowid = self.tree.identify_row(event.y)
column = self.tree.identify_column(event.x)
# Check if the double click was on some row or empty space
if rowid == '':
return
# get cell position info
x,y,width,height = self.tree.bbox(rowid,column)
# y-axis offset
pady = height / 2
# place Entry popup properly
if column == '#0':
text = self.tree.item(rowid,'text')
self.cellPopup = EntryPopup(self.tree,rowid,column,text)
else:
self.cellPopup = ComboPopup(self.tree,values=("A","b","3"))
self.cellPopup.place(x=x,y=y + pady,width=width,height=1.25 * height,anchor='w')
if __name__ == '__main__':
root = tk.Tk()
root.geometry('900x600+300+300')
App(root)
root.mainloop()
它似乎完美无缺。 现在,无论如何...