解除ttk.Label小部件无法及时重绘?

问题描述 投票:0回答:2

这是我的最小代表性示例(MRE),介绍如何通过多线程方法创建带有图像的

ttk.Button
小部件。但是,我遇到了一个问题。每当
self.label
小部件被抬起时,它都无法立即重绘;在
self.label
完全出现之前,会出现一小段灰色斑块。运行
self.update_idletasks()
(参见第 97 行)无法解决此问题。只有运行
self.update()
才能解决这个问题(你必须取消第 98 行的注释)。然而,一些人认为使用
self.update()
可能是有害。不使用
self.update()
是否可以解决这个问题?如果是这样,怎么办?您还可以解释一下为什么会出现这个问题吗?谢谢你。

发布演示:

期望的结果演示:

MRE:

请将您拥有的任何

.jpg
文件保存到与此脚本相同的目录/文件夹中,并将其重命名为
testimage.jpg
。这个图形用户界面是如何工作的?单击
Run
按钮启动多线程。要重新运行,您必须先单击
Reset
按钮,然后单击
Run
按钮。当线程正在进行时,请勿单击
Reset
,反之亦然。

# 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("<B1-ButtonRelease>", app.run)
    bns.bnreset.bind("<B1-ButtonRelease>", app.reset)

    root.mainloop()
python tkinter tcl
2个回答
0
投票

为了让 Tk 正确运行,您必须为其事件循环提供服务,并且几乎连续地执行此操作。这是收到一些小事情的时候,比如来自操作系统的指令,说明何时何地真正绘制窗口。

您应该调用

tk.mainloop()
函数。


推荐阅读:


0
投票

谢谢@Thingamabobs。您的评论有效:

为什么不提起标签并用 after 来调用你的繁重计算 确保标签绘制正确?

我的修改如下。甚至不需要额外调用

self.update_idletasks()
self.update()
方法。这样,tkinter 的主事件循环也得到了服务。

修正:

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.label.lift(self.vsf)  # Put this statement here instead of in the self.create_groups_concurrently method.
    self.after_idle(self.create_groups_concurrently, gids, fids, fpaths)  # Then use the after_idle() method to call the method that start the threads.
© www.soinside.com 2019 - 2024. All rights reserved.