Демо-версия проблемы:
[img]https://i .stack.imgur.com/j5SgF.gif[/img]
Желаемый результат:

MRE:
Пожалуйста, сохраните любой файл .jpg, который у вас есть, в тот же каталог/папку, что и этот скрипт, и переименуйте его в testimage.jpg. Как работает этот графический интерфейс? Нажмите кнопку «Выполнить», чтобы начать многопоточность. Для повторного запуска необходимо сначала нажать кнопку «Сбросить», после чего нажать кнопку «Выполнить». НЕ НАЖИМАЙТЕ «Сброс», когда потоковая передача продолжается, и наоборот.
# Python modules
import tkinter as tk
import tkinter.ttk as ttk
import concurrent.futures as cf
import queue
import threading
from itertools import repeat
import random
from time import sleep
# External modules
from PIL import Image, ImageTk
def get_thumbnail_c(gid: str, fid: str, fpath: str, psize=(100, 100)):
# print(f"{threading.main_thread()=} {threading.current_thread()=}")
with Image.open(fpath) as img:
img.load()
img.thumbnail(psize)
return gid, fid, img
def get_thumbnails_concurrently_with_queue(
g_ids: list, f_ids: list, f_paths: list, rqueue: queue.Queue,
size: tuple):
futures = []
job_fn = get_thumbnail_c
with cf.ThreadPoolExecutor() as vp_executor:
for gid, fids, fpath in zip(g_ids, f_ids, f_paths):
for gg, ff, pp in zip(repeat(gid, len(fids)), fids,
repeat(fpath, len(fids))):
job_args = gg, ff, pp, size
futures.append(vp_executor.submit(job_fn, *job_args))
for future in cf.as_completed(futures):
rqueue.put(("thumbnail", future.result()))
futures.remove(future)
if not futures:
print(f'get_thumbnails_concurrently has completed!')
rqueue.put(("completed", ()))
class GroupNoImage(ttk.Frame):
def __init__(self, master, gid, fids):
super().__init__(master, style='gframe.TFrame')
self.bns = {}
self.imgs = {}
for i, fid in enumerate(fids):
self.bns[fid] = ttk.Button(self, text=f"{gid}-P{i}", compound="top",
style="imgbns.TButton")
self.bns[fid].grid(row=0, column=i, stick="nsew")
class App(ttk.PanedWindow):
def __init__(self, master, **options):
super().__init__(master, **options)
self.master = master
self.groups = {}
self.rqueue = queue.Queue()
self.vsf = ttk.Frame(self)
self.add(self.vsf)
self.label = ttk.Label(
self, style="label.TLabel", width=7, anchor="c", text="ttk.Label",
font=('Times', '70', ''))
self.label.place(
relx=0.5, rely=0.5, relwidth=.8, relheight=.8, anchor="center",
in_=self.vsf)
self.label.lower(self.vsf)
def create_grpsframe(self):
self.grpsframe = ttk.Frame(self.vsf, style='grpsframe.TFrame')
self.grpsframe.grid(row=0, column=0, sticky="nsew")
def run(self, event):
self.create_grpsframe()
gids = [f"G{i}" for i in range(50)]
random.seed()
fids = []
for gid in gids:
f_ids = []
total = random.randint(2,10)
for i in range(total):
f_ids.append(f"{gid}-P{i}" )
fids.append(f_ids)
fpaths = ["testimage.jpg" for i in range(len(gids))]
self.create_groups_concurrently(gids, fids, fpaths)
def reset(self, event):
self.grpsframe.destroy()
self.groups.clear()
def create_groups_concurrently(self, gids, fids, fpaths):
print(f"\ncreate_groups_concurrently")
self.label.lift(self.vsf)
# self.update_idletasks() # Can't fix self.label appearance issue
# self.update() # Fixed self.label appearance issue
for i, (gid, f_ids) in enumerate(zip(gids, fids)):
self.groups[gid] = GroupNoImage(self.grpsframe, gid, f_ids)
self.groups[gid].grid(row=i, column=0, sticky="nsew")
self.update_idletasks()
# sleep(3)
print(f"\nStart thread-queue")
jthread = threading.Thread(
target=get_thumbnails_concurrently_with_queue,
args=(gids, fids, fpaths, self.rqueue, (100,100)),
name="jobthread")
jthread.start()
self.check_rqueue()
def check_rqueue(self):
# print(f"\ndef _check_thread(self, thread, start0):")
duration = 1 # millisecond
try:
info = self.rqueue.get(block=False)
# print(f"{info=}")
except queue.Empty:
self.after(1, lambda: self.check_rqueue())
else:
match info[0]:
case "thumbnail":
gid, fid, img = info[1]
print(f"{gid=} {fid=}")
grps = self.groups
grps[gid].imgs[fid] = ImageTk.PhotoImage(img)
grps[gid].bns[fid]["image"] = grps[gid].imgs[fid]
self.update_idletasks()
self.after(duration, lambda: self.check_rqueue())
case "completed":
print(f'Completed')
self.label.lower(self.vsf)
class ButtonGroups(ttk.Frame):
def __init__(self, master, **options):
super().__init__(master, style='bnframe.TFrame', **options)
self.master = master
self.bnrun = ttk.Button(
self, text="Run", width=10, style='bnrun.TButton')
self.bnreset = ttk.Button(
self, text="Reset", width=10, style='bnreset.TButton')
self.columnconfigure(0, weight=1)
self.columnconfigure(1, weight=1)
self.bnrun.grid(row=0, column=0, sticky="nsew")
self.bnreset.grid(row=0, column=1, sticky="nsew")
if __name__ == "__main__":
root = tk.Tk()
root.geometry('1300x600')
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
ss = ttk.Style()
ss.theme_use('default')
ss.configure(".", background="gold")
ss.configure("TPanedwindow", background="red")
ss.configure('grpsframe.TFrame', background='green')
ss.configure('gframe.TFrame', background='yellow')
ss.configure('imgbns.TButton', background='orange')
ss.configure("label.TLabel", background="cyan")
ss.configure('bnframe.TFrame', background='white')
ss.configure('bnrun.TButton', background='violet')
ss.configure('bnreset.TButton', background='green')
app = App(root)
bns = ButtonGroups(root)
app.grid(row=0, column=0, sticky="nsew")
bns.grid(row=1, column=0, sticky="nsew")
bns.bnrun.bind("", app.run)
bns.bnreset.bind("", app.reset)
root.mainloop()
Подробнее здесь: https://stackoverflow.com/questions/783 ... w-promptly