更新一个 canvas 中断 Tkinter 主循环的元素

Update an element of a canvas interrupting the main loop of Tkinter

我对 Python 和 Tkinter 还很陌生,因此,我不知道如何实现它...

实际上,我有这个 GUI:

,使用以下代码创建:

from tkinter import *

class RCP:

    global handle_comm
    global handle_rect
    global handle_res
    global spellerFrame

    global color_rect_bg, color_rect_hl, color_text_bg, color_text_hl, color_text_wa

    def __init__(self, targets, w_width, w_height):
        self.n_row = len(targets)
        self.n_col = len(targets[0])
        self.w_width = w_width
        self.w_height = w_height
        self.targets = targets
        self.canvasRoot = Tk()
        self.canvasRoot.configure(background='grey')
        self.setDefaults()
        self.canvasRoot.bind("<<foo>>", self.flashRow)

        # Initializate the main loop
        self.createGrid()
        self.canvasRoot.mainloop()

    def setDefaults(self):
        global color_rect_bg, color_rect_hl, color_text_bg, color_text_hl, color_text_wa
        color_rect_bg = '#000000'
        color_rect_hl = '#ffffff'
        color_text_bg = '#757575'
        color_text_hl = '#ffffff'
        color_text_wa = '#ffff00'
        global font_ratio_bg, font_ratio_hl
        font_ratio_bg = 0.5
        font_ratio_hl = 0.7

    def createGrid(self):
        # Calculate the maximum cell and font size that do not deform the commands' display
        cell_size = min(self.w_height / self.n_row, self.w_width / self.n_col)
        font_size = int(round(font_ratio_bg * cell_size))
        result_size = int(round(cell_size/5))

        # Create the canvas for the result text
        global handle_res
        resultLabel = Canvas(self.canvasRoot, width=self.w_width, height=result_size, bd=0,
                         highlightthickness=0, relief='ridge', background='grey')
        resultLabel.grid(row=0, column=0, columnspan=self.n_col)
        handle_res = resultLabel.create_text(2, round(result_size/2),  text=' PRUEBA', fill=color_text_wa,
                            anchor='w', font=("Purisa", round(result_size/2), "bold"))

        # Create the frame for the speller
        global spellerFrame
        spellerFrame = Canvas(self.canvasRoot, width=self.w_width, height=self.w_height, bd=0,
                          highlightthickness=0, relief='ridge')
        spellerFrame.grid(row=1, column=0)

        # Create the grid of commands
        global handle_comm, handle_rect
        handle_comm = [[None for i in range(self.n_col)] for j in range(self.n_row)]
        handle_rect = handle_comm
        for row_index in range(self.n_row):
            for col_index in range(self.n_col):
                x1 = col_index * cell_size
                y1 = row_index * cell_size
                x2 = (col_index + 1) * cell_size
                y2 = (row_index + 1) * cell_size
                handle_rect[row_index][col_index] = spellerFrame.create_rectangle(x1, y1, x2, y2, fill=color_rect_bg)
                handle_comm[row_index][col_index] = \
                spellerFrame.create_text(((x1+x2)/2,(y1+y2)/2), text=self.targets[row_index][col_index],
                                             fill=color_text_bg, font=("Purisa", font_size, "bold"))

    def flashRow(self):
        '''Flashes the row specified as attribute'''
        global spellerFrame
        for col_index in range(self.n_col):
            spellerFrame.itemconfig(handle_comm[1][col_index], fill=color_text_hl)


targets = [['A','B','C','D'],['E','F','G','H'],['I','J','K','L']]
myRCP = RCP(targets, 800, 600)

问题是我需要在GUI已经显示后修改字母的颜色。这个视频展示了我想要达到的效果:https://www.youtube.com/watch?v=xvfxsNpaRGI

因此,我创建了 flashRow 方法,它在调用时闪烁第一行。问题是我无法中断 Tkinter 的主循环来更新元素...

我读过 after 命令,但我认为这不是一个合适的选项,因为我不知道什么时候需要调用 flashRow 方法先验的。

我可以使用 generate_event 方法来创建错误事件处理程序来修改字母的颜色而不中断循环吗?如果是,如何?

提前致谢

这将使您入门。将 self.canvasRoot.bind("<<foo>>", self.flashRow) 更改为 self.canvasRoot.after(10, self.flashRow)。这将导致 flashRow 运行 一次。在 flashRow 方法的底部,如果你想重复使用 flashRow 运行,schedule 它再次使用 after.

到 运行 本身
def flashRow(self):
     '''Flashes the row specified as attribute'''
     global spellerFrame
     for col_index in range(self.n_col):
         spellerFrame.itemconfig(handle_comm[1][col_index], 
         fill=color_text_hl)
     # Put conditions or change the delay to something else,
     # but this will reschedule flashRow in 100ms repeatedly.
     self.canvasRoot.after(100, self.flashRow)

widget.after(ms, callback, *args) 在程序第一次读取它后调用 callback(*args) ms 毫秒。所以你可以定义一个回调方法,例如self.flashRow,然后用你在__init__的时间范围调用它,比如:

self.canvasRoot.after(250, self.flashRow)

或:

self.flashRow

两者都应该没问题,只要它们在 mainloop 调用之前。然后在你的回调中,self.flashRow,你需要确保它在一个时间范围内递归调用自己,比如 250 毫秒:

self.canvasRoot.after(250, self.flashRow)

我配置了 self.flashRow 一些随机闪烁:

def flashRow(self):
    '''Flashes the row specified as attribute'''

    import random
    global spellerFrame
    _row = random.randint(0, 2)
    _color = random.choice((color_text_hl, 'grey'))
    print(_color)
    for col_index in range(self.n_col):
        spellerFrame.itemconfig(handle_comm[_row][col_index], fill=_color)
    self.canvasRoot.after(250, self.flashRow)

另请参阅您提供的非mcve代码的完整配置:

from tkinter import *

class RCP:

    global handle_comm
    global handle_rect
    global handle_res
    global spellerFrame

    global color_rect_bg, color_rect_hl, color_text_bg, color_text_hl, color_text_wa

    def __init__(self, targets, w_width, w_height):
        self.n_row = len(targets)
        self.n_col = len(targets[0])
        self.w_width = w_width
        self.w_height = w_height
        self.targets = targets
        self.canvasRoot = Tk()
        self.canvasRoot.configure(background='grey')
        self.setDefaults()

        # Initializate the main loop
        self.createGrid()
        self.canvasRoot.after(250, self.flashRow)
        self.canvasRoot.mainloop()

    def setDefaults(self):
        global color_rect_bg, color_rect_hl, color_text_bg
        global color_text_hl, color_text_wa
        color_rect_bg = '#000000'
        color_rect_hl = '#ffffff'
        color_text_bg = '#757575'
        color_text_hl = '#ffffff'
        color_text_wa = '#ffff00'
        global font_ratio_bg, font_ratio_hl
        font_ratio_bg = 0.5
        font_ratio_hl = 0.7

    def createGrid(self):
        # Calculate the maximum cell and font size that do not 
        # deform the commands' display
        cell_size = min(self.w_height / self.n_row, self.w_width / self.n_col)
        font_size = int(round(font_ratio_bg * cell_size))
        result_size = int(round(cell_size/5))

        # Create the canvas for the result text
        global handle_res
        resultLabel = Canvas(self.canvasRoot, width=self.w_width,
                        height=result_size, bd=0,
                        highlightthickness=0, relief='ridge', background='grey')
        resultLabel.grid(row=0, column=0, columnspan=self.n_col)
        handle_res = resultLabel.create_text(2, round(result_size/2),
                            text=' PRUEBA', fill=color_text_wa, anchor='w',
                            font=("Purisa", round(result_size/2), "bold"))

        # Create the frame for the speller
        global spellerFrame
        spellerFrame = Canvas(self.canvasRoot, width=self.w_width,
                                height=self.w_height, bd=0,
                                highlightthickness=0, relief='ridge')
        spellerFrame.grid(row=1, column=0)

        # Create the grid of commands
        global handle_comm, handle_rect
        handle_comm = [[None for i in range(self.n_col)] for j in range(self.n_row)]
        handle_rect = handle_comm
        for row_index in range(self.n_row):
            for col_index in range(self.n_col):
                x1 = col_index * cell_size
                y1 = row_index * cell_size
                x2 = (col_index + 1) * cell_size
                y2 = (row_index + 1) * cell_size
                handle_rect[row_index][col_index] = spellerFrame.create_rectangle(x1,
                                                    y1, x2, y2, fill=color_rect_bg)
                handle_comm[row_index][col_index] = \
                spellerFrame.create_text(((x1+x2)/2,(y1+y2)/2),
                                            text=self.targets[row_index][col_index],
                                            fill=color_text_bg,
                                            font=("Purisa", font_size, "bold"))

    def flashRow(self):
        '''Flashes the row specified as attribute'''

        import random
        global spellerFrame
        _row = random.randint(0, 2)
        _color = random.choice((color_text_hl, 'grey'))
        print(_color)
        for col_index in range(self.n_col):
            spellerFrame.itemconfig(handle_comm[_row][col_index], fill=_color)
        self.canvasRoot.after(250, self.flashRow)

targets = [['A','B','C','D'],['E','F','G','H'],['I','J','K','L']]
myRCP = RCP(targets, 800, 600)