[英]How can I anchor a widget's position to a Treeview xview in python tkinter?
我正在嘗試制作一個應用程序,用戶可以在其中編輯 TreeView 小部件中的信息,方法是在 TreeView 本身上的輸入框中鍵入內容(就像您可以在 Windows 文件資源管理器中執行的操作一樣)。
我通過查詢所選項目並使用TreeView.bbox(item,column="#0")
function 來獲取放置條目小部件的坐標,使其部分工作。 但是,如果用戶滾動 TreeView,我還希望這些小部件跟隨單元格。 初始放置行為是正確的,即如果相應列在當前視圖之外,則 Entry 小部件不可見,並且小部件的初始position 對應於放置小部件時相應單元格的 position。 但是當 TreeView xview 或 yview 更改時,我無法弄清楚如何更新小部件 position。
這是我目前正在做的一個簡化示例:
import tkinter as tk
from tkinter import ttk
class TreeViewWindow(tk.Tk):
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
self.xscroll = tk.Scrollbar(self,orient='horizontal')
self.yscroll = tk.Scrollbar(self,orient='vertical')
self.list = ttk.Treeview(self,columns=("#1","#2","#3"),
selectmode='extended',xscrollcommand=self.xscroll.set,yscrollcommand=self.yscroll.set)
self.xscroll.configure(command=self.list.xview)
self.yscroll.configure(command=self.list.yview)
for cc,ii in zip(self.list['columns'],range(len(self.list['columns']))):
self.list.column(cc,minwidth=150)
self.list.heading(cc,text=f'Column {ii}')
self.list.column("#0",minwidth=150)
self.list.heading("#0",text="Name")
self.list.grid(row=0,column=0,sticky='news')
self.xscroll.grid(row=1,column=0,sticky='ew')
self.yscroll.grid(row=0,column=1,sticky='ns')
self.columnconfigure(0,weight=1)
self.rowconfigure(0,weight=1)
self.list.bind("<F2>",self.__editentry)
self.disptext = tk.StringVar(self,value='')
self.display = tk.Label(self,textvariable=self.disptext)
self.display.grid(row=1,column=0,sticky='ne')
self.__inputvars = None
self.__inputboxes = None
# insert some items into the tree
for i in range(10):
self.list.insert('', 'end',iid='line%i' % i, text='line:%s' % i, values=('parent', i, 'hello'))
for j in ('a','b'):
self.list.insert('line%i' % i,'end',iid=f'line{i}{j}', text=f'line:{i}{j}', values = ('child',f'{j}','goodbye'))
self.mainloop()
def __editentry(self,event):
iid = self.list.selection()
if len(iid) == 0:
return
iid = iid[0]
if self.__inputboxes is not None:
self.__acceptentry()
cols = ('#0',) + self.list['columns']
self.__inputboxes = [None] * len(cols)
self.__inputvars = [tk.StringVar() for ii in range(len(cols))]
for cc,ii in zip(cols,range(len(cols))):
bbx = self.list.bbox(iid,cc)
if bbx == '':
continue
values = (self.list.item(iid,'text'),) + self.list.item(iid,'values')
if ii == 0:
self.__inputaccept = tk.Button(self.list,text="Y",command=self.__acceptentry)
self.__inputcancel = tk.Button(self.list,text="N",command=self.__rejectentry)
self.__inputaccept.place(anchor='nw',x=bbx[0],y=bbx[1])
self.__inputaccept.update_idletasks()
bbx = (bbx[0]+self.__inputaccept.winfo_width(),bbx[1],bbx[2]-self.__inputaccept.winfo_width(),bbx[3])
self.__inputcancel.place(anchor='nw',x=bbx[0],y=bbx[1])
self.__inputcancel.update_idletasks()
bbx = (bbx[0]+self.__inputcancel.winfo_width(),bbx[1],bbx[2]-self.__inputcancel.winfo_width(),bbx[3])
self.__inputboxes[ii] = tk.Entry(self.list,relief=tk.FLAT,textvariable=self.__inputvars[ii],bd=3,highlightthickness=1,highlightbackground='black',highlightcolor='black')
self.__inputvars[ii].set(values[ii])
self.__inputboxes[ii].place(anchor='nw',x=bbx[0],y=bbx[1],width=bbx[2],height=bbx[3])
self.__inputboxes[ii].bind('<FocusIn>',lambda event: event.widget.configure(highlightthickness=2,highlightbackground='red',highlightcolor='red'))
self.__inputboxes[ii].bind('<FocusOut>',lambda event: event.widget.configure(highlightthickness=1,highlightbackground='black',highlightcolor='black'))
self.__acceptbind = self.bind('<Return>',self.__acceptentry)
self.__rejectbind = self.bind('<Escape>',self.__rejectentry)
self.__acceptid = iid
def __acceptentry(self,event=None):
self.unbind('<Return>',self.__acceptbind)
self.unbind('<Escape>',self.__rejectbind)
values = tuple(vv.get() for vv in self.__inputvars)
for ii in range(len(self.__inputboxes)):
self.__inputboxes[ii].destroy()
self.__inputboxes = None
self.__inputvars = None
self.__inputaccept.destroy()
self.__inputcancel.destroy()
self.list.item(self.__acceptid,text=values[0],values=values[1:])
self.__acceptid = None
def __rejectentry(self,event=None):
self.unbind('<Return>',self.__acceptbind)
self.unbind('<Escape>',self.__rejectbind)
for ii in range(len(self.__inputboxes)):
self.__inputboxes[ii].destroy()
self.__inputaccept.destroy()
self.__inputcancel.destroy()
self.__inputboxes = None
self.__inputvars = None
self.__acceptid = None
if __name__=="__main__":
TreeViewWindow()
我在 Windows 上使用 Python 3.8。
好的,所以我通過在 TreeView 和滾動條之間插入我自己的滾動處理程序 function 找到了一種有點hacky的方法。 在每個滾動事件之后,如果條目小部件已放置在 TreeView 上,則它們的位置將更新為新的TreeView.bbox(itm,column)
在滾動事件之后的位置。 它似乎有點滯后,但它確實有效。 不過,我仍然想知道其他人是否有更好的解決方案。
工作代碼:
import tkinter as tk
from tkinter import ttk
class TreeViewWindow(tk.Tk):
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
self.xscroll = tk.Scrollbar(self,orient='horizontal',command=lambda *val, dir='x': self.__scrollhandler(*val,direction=dir))
self.yscroll = tk.Scrollbar(self,orient='vertical',command=lambda *val, dir='y': self.__scrollhandler(*val,direction=dir))
self.list = ttk.Treeview(self,columns=("#1","#2","#3"),
selectmode='extended',
xscrollcommand=lambda *val, dir='x',src='list': self.__scrollhandler(*val,direction=dir,source=src),
yscrollcommand=lambda *val, dir='y',src='list': self.__scrollhandler(*val,direction=dir,source=src))
for cc,ii in zip(self.list['columns'],range(len(self.list['columns']))):
self.list.column(cc,minwidth=150)
self.list.heading(cc,text=f'Column {ii}')
self.list.column("#0",minwidth=150)
self.list.heading("#0",text="Name")
self.list.grid(row=0,column=0,sticky='news')
self.xscroll.grid(row=1,column=0,sticky='ew')
self.yscroll.grid(row=0,column=1,sticky='ns')
self.columnconfigure(0,weight=1)
self.rowconfigure(0,weight=1)
self.list.bind("<F2>",self.__editentry)
self.disptext = tk.StringVar(self,value='')
self.display = tk.Label(self,textvariable=self.disptext)
self.display.grid(row=1,column=0,sticky='ne')
self.__inputvars = None
self.__inputboxes = None
self.__acceptid = None
# insert some items into the tree
for i in range(10):
self.list.insert('', 'end',iid='line%i' % i, text='line:%s' % i, values=('parent', i, 'hello'))
for j in ('a','b'):
self.list.insert('line%i' % i,'end',iid=f'line{i}{j}', text=f'line:{i}{j}', values = ('child',f'{j}','goodbye'))
self.mainloop()
def __scrollhandler(self,*val,direction='y',source='scroll'):
if source=='scroll' and direction=='x':
self.list.xview(*val)
elif source=='scroll' and direction=='y':
self.list.yview(*val)
elif source=='list' and direction=='x':
self.xscroll.set(*val)
elif source=='list' and direction=='y':
self.yscroll.set(*val)
self.list.update_idletasks()
self.__forgetentryboxes()
self.__placeentryboxes()
def __placeentryboxes(self,iid=None):
if iid is None:
iid = self.__acceptid
if iid is None:
return
cols = ('#0',) + self.list['columns']
for cc,ii in zip(cols,range(len(cols))):
bbx = self.list.bbox(iid,cc)
if bbx == '':
continue
if ii == 0:
self.__inputaccept.place(anchor='nw',x=bbx[0],y=bbx[1])
self.__inputaccept.update_idletasks()
bbx = (bbx[0]+self.__inputaccept.winfo_width(),bbx[1],bbx[2]-self.__inputaccept.winfo_width(),bbx[3])
self.__inputcancel.place(anchor='nw',x=bbx[0],y=bbx[1])
self.__inputcancel.update_idletasks()
bbx = (bbx[0]+self.__inputcancel.winfo_width(),bbx[1],bbx[2]-self.__inputcancel.winfo_width(),bbx[3])
self.__inputboxes[ii].place(anchor='nw',x=bbx[0],y=bbx[1],width=bbx[2],height=bbx[3])
def __forgetentryboxes(self):
if self.__inputboxes is not None:
for bx in self.__inputboxes:
bx.place_forget()
self.__inputaccept.place_forget()
self.__inputcancel.place_forget()
def __editentry(self,event):
iid = self.list.selection()
if len(iid) == 0:
return
iid = iid[0]
if self.__inputboxes is not None:
self.__acceptentry()
cols = ('#0',) + self.list['columns']
self.__inputvars = [tk.StringVar() for ii in range(len(cols))]
self.__inputboxes = [tk.Entry(self.list,relief=tk.FLAT,textvariable=var,bd=3,highlightthickness=1,highlightbackground='black',highlightcolor='black') for var in self.__inputvars]
self.__inputaccept = tk.Button(self.list,text="Y",command=self.__acceptentry)
self.__inputcancel = tk.Button(self.list,text="N",command=self.__rejectentry)
self.__placeentryboxes(iid)
values = (self.list.item(iid,'text'),) + self.list.item(iid,'values')
for bx,var,val in zip(self.__inputboxes,self.__inputvars,values):
var.set(val)
bx.bind('<FocusIn>',lambda event: event.widget.configure(highlightthickness=2,highlightbackground='red',highlightcolor='red'))
bx.bind('<FocusOut>',lambda event: event.widget.configure(highlightthickness=1,highlightbackground='black',highlightcolor='black'))
self.__acceptbind = self.bind('<Return>',self.__acceptentry)
self.__rejectbind = self.bind('<Escape>',self.__rejectentry)
self.__acceptid = iid
def __acceptentry(self,event=None):
self.unbind('<Return>',self.__acceptbind)
self.unbind('<Escape>',self.__rejectbind)
values = tuple(vv.get() for vv in self.__inputvars)
for ii in range(len(self.__inputboxes)):
self.__inputboxes[ii].destroy()
self.__inputboxes = None
self.__inputvars = None
self.__inputaccept.destroy()
self.__inputcancel.destroy()
self.list.item(self.__acceptid,text=values[0],values=values[1:])
self.__acceptid = None
def __rejectentry(self,event=None):
self.unbind('<Return>',self.__acceptbind)
self.unbind('<Escape>',self.__rejectbind)
for ii in range(len(self.__inputboxes)):
self.__inputboxes[ii].destroy()
self.__inputaccept.destroy()
self.__inputcancel.destroy()
self.__inputboxes = None
self.__inputvars = None
self.__acceptid = None
if __name__=="__main__":
TreeViewWindow()
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.