簡體   English   中英

如何將小部件的 position 錨定到 python ZE5BA8B4C39C29BF15426EF5E028 中的 Treeview xview?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM