Home > Back-end >  ttk Treeview, get item's bbox after scrolling into view
ttk Treeview, get item's bbox after scrolling into view

Time:10-13

I am working on an editable tkinter.ttk.Treeview subclass. For editing I need to place the edit widget on top of a choosen "cell" (list row/column). To get the proper coordinates, there is the Treeview.bbox() method.

If the row to be edited is not in view (collapsed or scrolled away), I cannot get its bbox obviously. Per the docs, the see() method is meant to bring an item into view in such a case.

Example Code:

from tkinter import Tk, Button
from tkinter.ttk import Treeview

root = Tk()
tv = Treeview(root)
tv.pack()
iids = [tv.insert("", "end", text=f"item {n}") for n in range(20)]

# can only get bbox once everything is on screen.
n = [0]
def show_bbox():
    n[0]  = 1
    iid = iids[n[0]]
    b = tv.bbox(iid)
    if not b:
        # If not visible, scroll into view and try again
        tv.see(iid)
        # ... but this still doesn't return a valid bbox!?
        b = tv.bbox(iid)
    print(f"bbox of item {n}", b)

btn = Button(root, text="bbox", command=show_bbox)
btn.pack(side="bottom")

root.mainloop()

(start, then click the button until you reach an invisible item)

The second tv.bbox() call ought to return a valid bbox, but still returns empty string. Apparently see doesnt work immediately, but enqeues the viewport change into the event queue somehow. So my code cannot just proceed synchronously as it seems.

How to solve this? Can see() be made to work immediately? If not, is there another workaround?

CodePudding user response:

The problem is that even after calling see, the item isn't visible (and thus, doesn't have a bounding box) until it is literally drawn on the screen.

A simple solution is to call tv.update_idletasks() immediately after calling tv.see(), which should cause the display to refresh.

Another solution is to use tv.after to schedule the display of the box (or the overlaying of an entry widget) to happen after mainloop has a chance to refresh the window.

def print_bbox(iid):
    bbox = tv.bbox(iid)
    print(f"bbox of item {iid}", bbox)

def show_bbox():
    n[0]  = 1
    iid = iids[n[0]]
    tv.see(iid)
    tv.after_idle(print_bbox, iid)
  • Related