pythontkintercanvasscrollvisible

Python TkInter: How can I get the canvas coordinates of the visible area of a scrollable canvas?


I need to find out what the visible coordinates of a vertically scrollable canvas are using python and tkinter.

Let's assume I have a canvas that is 800 x 5000 pixels, and the visible, vertically scrollable window is 800x800. If I am scrolled all the way to the top of the canvas, I would like to have a function that, when run, would return something like:

x=0
y=0,
w=800
h=800

But if I were to scroll down and then run the function, I would get something like this:

x=0
y=350
w=800
h=800

And if I resized the window vertically to, say, 1000, I would get:

x=0
y=350
w=800
h=1000

I tried this code:


            self.canvas.update()
            print(f"X={self.canvas.canvasx(0)}")
            print(f"Y={self.canvas.canvasy(0)}")
            print(f"W={self.canvas.canvasx(self.canvas.winfo_width())}")
            print(f"H={self.canvas.canvasy(self.canvas.winfo_height())}")

But it gives me the size of the whole canvas, not the visible window inside the canvas.

I have tried searching for the answer, but am surprised not to have found anyone else with the same question. Perhaps I just don't have the right search terms.

Context for anyone who cares:

I am writing a thumbnail browser that is a grid of thumbnails. Since there may be thousands of thumbnails, I want to update the ones that are visible first, and then use a thread to update the remaining (hidden) thumbnails as needed.


Solution

  • The methods canvasx and canvasy of the Canvas widget will convert screen pixels (ie: what's visible on the screen) into canvas pixels (the location in the larger virtual canvas).

    You can feed it an x or y of zero to get the virtual pixel at the top-left of the visible window, and you can give it the width and height of the widget to get the pixel at the bottom-right of the visible window.

    x0 = canvas.canvasx(0)
    y0 = canvas.canvasy(0)
    x1 = canvas.canvasx(canvas.winfo_width())
    y1 = canvas.canvasy(canvas.winfo_height())
    

    The canonical tcl/tk documentation says this about the canvasx method:

    pathName canvasx screenx ?gridspacing?: Given a window x-coordinate in the canvas screenx, this command returns the canvas x-coordinate that is displayed at that location. If gridspacing is specified, then the canvas coordinate is rounded to the nearest multiple of gridspacing units.


    Here is a contrived program that will print the coordinates of the top-left and bottom right visible pixels every five seconds.

    import tkinter as tk
    
    root = tk.Tk()
    canvas_frame = tk.Frame(root, bd=1, relief="sunken")
    statusbar = tk.Label(root, bd=1, relief="sunken")
    
    statusbar.pack(side="bottom", fill="x")
    canvas_frame.pack(fill="both", expand=True)
    
    canvas = tk.Canvas(canvas_frame, width=800, height=800, bd=0, highlightthickness=0)
    ysb = tk.Scrollbar(canvas_frame, orient="vertical", command=canvas.yview)
    xsb = tk.Scrollbar(canvas_frame, orient="horizontal", command=canvas.xview)
    canvas.configure(yscrollcommand=ysb.set, xscrollcommand=xsb.set)
    
    canvas_frame.grid_rowconfigure(0, weight=1)
    canvas_frame.grid_columnconfigure(0, weight=1)
    canvas.grid(row=0, column=0, sticky="nsew")
    ysb.grid(row=0, column=1, sticky="ns")
    xsb.grid(row=1, column=0, sticky="ew")
    
    for row in range(70):
        for column in range(10):
            x = column * 80
            y = row * 80
            canvas.create_rectangle(x, y, x+64, y+64, fill="gray")
            canvas.create_text(x+32, y+32, anchor="c", text=f"{row},{column}")
    
    canvas.configure(scrollregion=canvas.bbox("all"))
    
    def show_coords():
        x0 = int(canvas.canvasx(0))
        y0 = int(canvas.canvasy(0))
        x1 = int(canvas.canvasx(canvas.winfo_width()))
        y1 = int(canvas.canvasy(canvas.winfo_height()))
        statusbar.configure(text=f"{x0},{y0} / {x1},{y1}")
        root.after(5000, show_coords)
    
    show_coords()
    
    root.mainloop()