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)