How to move popup window when scrolling a tkinter treeview?
I have done something similar before by binding a function to mousewheel
and recalculate all the new x & y location of your hovering widgets.
class EditableDataTable(tk.Frame): def __init__(self, parent): tk.Frame.__init__(self, parent) self.parent = parent self.tree = None self.entryPopup = None self.list_of_entries = [] ... self.tree.bind("<MouseWheel>", self._on_mousewheel) def _on_mousewheel(self, event): if self.list_of_entries: def _move(): for i in self.list_of_entries: try: iid = i.iId x, y, width, height = self.tree.bbox(iid, column="Col2") #hardcoded to col2 i.place(x=x, y=y+height//2, anchor='w', width=width) except ValueError: i.place_forget() except tk.TclError: pass self.master.after(5, _move) def createPopup(self, row, column): x,y,width,height = self.tree.bbox(row, column) # y-axis offset pady = height // 2 self.entryPopup = EntryPopup(self.tree, row, column) self.list_of_entries.append(self.entryPopup) self.entryPopup.place(x=x, y=y+pady, anchor='w', width=width)
Note that this only works on the second column, but should be easy enough to implement for the rest.
You will need to do the math and move the entry widget when the tree is scrolled. I have edited your code and I programmed the scrollbar buttons only. If you click the button the entry widget will scroll with the tree. I did not program the wheelmouse scrolling or dragging the scrollbar. You should be able to figure out the rest.
import tkinter as tkimport tkinter.font as tkfontfrom tkinter import ttkclass EntryPopup(ttk.Entry): def __init__(self, parent, itemId, col, **kw): super().__init__(parent, **kw) self.tv = parent self.iId = itemId self.column = col self['exportselection'] = False self.focus_force() self.bind("<Return>", self.onReturn) def saveEdit(self): self.tv.set(self.iId, column=self.column, value=self.get()) print("EntryPopup::saveEdit---{}".format(self.iId)) def onReturn(self, event): self.tv.focus_set() self.saveEdit() self.destroy()class EditableDataTable(tk.Frame): def __init__(self, parent): tk.Frame.__init__(self, parent) self.parent = parent self.tree = None self.entryPopup = None columns = ("Col1", "Col2") # Create a treeview with vertical scrollbar. self.tree = ttk.Treeview(self, columns=columns, show="headings") self.tree.grid(column=0, row=0, sticky='news') self.tree.heading("#1", text="col1") self.tree.heading("#2", text="col2") self.vsb = ttk.Scrollbar(self, orient="vertical", command=self.tree.yview) self.tree.configure(yscrollcommand=self.vsb.set) self.vsb.grid(column=1, row=0, sticky='ns') self.grid_columnconfigure(0, weight=1) self.grid_rowconfigure(0, weight=1) self.entryPopup = None self.curSelectedRowId = "" col1 = [] col2 = [] for r in range(50): col1.append("data 1-{}".format(r)) col2.append("data 2-{}".format(r)) for i in range(min(len(col1),len(col2))): self.tree.insert('', i, values=(col1[i], col2[i])) self.tree.bind('<Double-1>', self.onDoubleClick) self.vsb.bind('<ButtonPress-1>', self.func) def func(self, event): print(self.vsb.identify(event.x, event.y)) if hasattr(self.entryPopup, 'y'): item = self.vsb.identify(event.x, event.y) if item == 'uparrow': self.entryPopup.y += 20 elif item == 'downarrow': self.entryPopup.y -= 20 self.entryPopup.place(x=self.entryPopup.x, y=self.entryPopup.y, ) def createPopup(self, row, column): x, y, width, height = self.tree.bbox(row, column) # y-axis offset pady = height // 2 self.entryPopup = EntryPopup(self.tree, row, column) self.entryPopup.x = x self.entryPopup.y = y+pady self.entryPopup.place(x=x, y=y+pady, anchor='w', width=width) def onDoubleClick(self, event): rowid = self.tree.identify_row(event.y) column = self.tree.identify_column(event.x) self.createPopup(rowid, column)root = tk.Tk()for row in range(2): root.grid_rowconfigure(row, weight=1)root.grid_columnconfigure(0, weight=1)label = tk.Label(root, text="Double-click to edit and press 'Enter'")label.grid(row=0, column=0, sticky='news', padx=10, pady=5)dataTable = EditableDataTable(root)dataTable.grid(row=1, column=0, sticky="news", pady=10, padx=10)root.geometry("450x300")root.mainloop()