Marc P.
Marc P.

Reputation: 702

Unexpected behavior of Tkinter's grid embedded in a canvas with more than 280 elements

I want to display a lot of thumbnails and be able to do different operations on them, depending on the content of the thumbnail. For this I took the example from Adding a scrollbar to a group of widgets in Tkinter and put labels in the first column and images in the second column of the embedded grid. As a sample thumbnail you can download this image and rename it to 160x120.jpg.

import Tkinter as tk
from PIL import Image, ImageTk

class Example(tk.Frame):
    def __init__(self, root):

        tk.Frame.__init__(self, root)
        self.canvas = tk.Canvas(root, borderwidth=0, background="#ffffff")
        self.frame = tk.Frame(self.canvas, background="#ffffff")
        self.vsb = tk.Scrollbar(root, orient="vertical", command=self.canvas.yview)
        self.canvas.configure(yscrollcommand=self.vsb.set)

        self.vsb.pack(side="right", fill="y")
        self.canvas.pack(side="left", fill="both", expand=True)
        self.canvas.create_window((4,4), window=self.frame, anchor="nw", 
                                  tags="self.frame")

        self.frame.bind("<Configure>", self.OnFrameConfigure)

        self.photos = []
        self.imgs = []
        self.images = []
        self.lbls = []

        self.populate()

    def populate(self):
        '''Put in some fake data'''

        # n_thumbs = 100 : As expected, one label and one image per row.
        # n_thumbs = 300 : Images are only displayed up to the 278th.
        # n_thumbs = 600 : Just 63 images are displayed. Two labels per row.

        n_thumbs = 100

        for i in range(n_thumbs):
            lbl = tk.Label(self.frame, text="img " + str(i), width=10)
            lbl.grid(row=i, column=0)
            photo = Image.open("160x120.jpg")
            img = ImageTk.PhotoImage(photo)
            image = tk.Label(self.frame, image=img)
            image.grid(row=i, column=1)
            self.lbls.append(lbl)
            self.photos.append(photo)
            self.images.append(image)
            self.imgs.append(img)

    def OnFrameConfigure(self, event):
        '''Reset the scroll region to encompass the inner frame'''
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))

if __name__ == "__main__":
    root=tk.Tk()
    Example(root).pack(side="top", fill="both", expand=True)
    root.mainloop()

Now, if you set n_thumbs = 100, everything works as expected, i.e. there is one label and one image per row.

If you set n_thumbs = 300, the list abruptly ends with the 278th image.

If you set n_thumbs = 600, only 63 images are shown, but you can still scroll down beyond the last image. Furthermore, suddenly there are two labels in each row.

How can these effects be explained? How can I display more than 300 images in a grid using Tkinter?

Upvotes: 2

Views: 349

Answers (1)

Bryan Oakley
Bryan Oakley

Reputation: 385980

I don't know for a fact, but my guess is that you're exceeding the limits of the tk canvas. With an image 120 pixels tall, you can display 272 images vertically before the containing frame exceeds 32k (32767) pixels tall. Perhaps there is a hard limit that the canvas can't be bigger than 32767x32767. Or maybe coordinates are constrained to be from -32767 to +32767. If some of your thumbnails weren't exactly 120 pixels tall, that might explain the limit you're seeing of 278. I was seeing a slightly different limit on my OSX system.

Again, I don't know that for a fact, but it seems likely.

If this is a hard limit, with a little work you can only show a screenful at a time, and load new images only when they are scrolled into view (and you can destroy images that have scrolled out of view). And, instead of using an embedded frame, draw the images directly on the canvas.

Upvotes: 3

Related Questions