我正在尝试制作一个Windows样式的图像查看器,以便可以在tkinter中以图标或缩略图格式一次显示多个图像(例如,在Windows上执行“视图”>“布局”>“大图标”时)。但是,我要打开的文件夹包含数百张图像,这是一种缓慢加载所有图像的方法,并且无论如何可能会使用过多的内存。因此,我试图根据图像是否可见来延迟加载图像(即:如果用户向下滚动足以看到图像)。这是我到目前为止的内容:
from tkinter import *
from tkinter import filedialog
from PIL import Image
from PIL import ImageTk
import glob
import datetime
import os
class ImageListViewer(Frame):
def __init__(self, root, img_paths, *args, **kwargs):
self.img_paths = img_paths
self.root = root
self.canvas = Canvas(root)
self.scroll_y = Scrollbar(root, orient="vertical", command=self.canvas.yview)
self.frame = Frame(self.canvas, bg='red')
self.max_w, self.max_h = 200, 200
cols = 3
loading_img = Image.open('loading.png')
loading_img = self.fit_img(loading_img, self.max_w, self.max_h)
loading_photo = ImageTk.PhotoImage(loading_img)
# group of widgets
for i, path in enumerate(img_paths):
label = Label(self.frame, image=loading_photo, width=self.max_w, height=self.max_h, borderwidth=2, relief="sunken")
label.photo = loading_photo
# label = Label(self.frame, text=str(i), width=self.max_w, height=self.max_h, borderwidth=2, relief="sunken")
label.index = i
label.has_img = False
label.grid(row=i-i%cols, column=i%cols)
self.canvas.create_window(0, 0, anchor='nw', window=self.frame)
self.canvas.update_idletasks()
self.canvas.configure(scrollregion=self.canvas.bbox('all'),
yscrollcommand=self.scroll_intercepter)
self.canvas.pack(fill='both', expand=True, side='left')
self.scroll_y.pack(fill='y', side='right')
self.root.bind('h', self.debug)
super().__init__(root)
def scroll_intercepter(self, a, b):
for label in self.labels_in_view():
if not label.has_img:
self.lazy_load_img(label)
self.scroll_y.set(a, b)
def lazy_load_img(self, label):
img = Image.open(self.img_paths[label.index])
img = self.fit_img(img, self.max_w, self.max_h)
photo = ImageTk.PhotoImage(img)
label.config(image = photo)
label.photo = photo
# label.config(bg = 'red')
label.has_img = True
def labels_in_view(self, show=False):
if show:
print('Root:', self.root.winfo_width(), self.root.winfo_height())
for label in self.frame.winfo_children():
label_x = label.winfo_rootx() #- label.winfo_width()
label_y = label.winfo_rooty() #- label.winfo_height()
if show:
print(label.index, label_x, label_y)
x_in = 0 <= label_x < self.root.winfo_width()
y_in = 0 <= label_y < self.root.winfo_height()
if x_in and y_in:
yield label
def debug(self, event):
print([l.index for l in self.labels_in_view(show=True)], '\n')
@staticmethod
def fit_img(img, width, height, keep_aspect=True):
if keep_aspect:
w, h = img.size
ratio = min(width/w, height/h)
image = img.resize((int(w*ratio), int(h*ratio)))
else:
image = img.resize((width, height))
return image
if __name__ == "__main__":
root = Tk()
root.title("Image List Viewer")
root.geometry('400x400')
paths = glob.glob('*.JPG')
ImageListViewer(root, paths).pack()
root.mainloop()
labels_in_view方法尝试仅返回显示的标签(包含图像)。每个标签最初都加载有一个虚拟的“加载”图像。您还可以看到我绑定了“ h”以显示一些额外的调试信息。
这是问题所在,过去的数十张图像完全挂掉了。如果要滚动到所有图像的末尾,它将尝试从上到下加载所有中间图像,而不仅是那些可见的图像。
这当然是因为我如何拦截滚动条回叫然后延迟加载,但是我无法弄清楚其他任何方式。有没有更好的办法?也许也是更好的库,tkinter相当慢,尽管我不想从头开始?
任何帮助将不胜感激!谢谢。
编辑:图像仅应加载一次,因为label.has_img标志在图像加载后设置为true。