更新画布的元素,中断Tkinter的主循环

时间:2018-01-20 20:19:53

标签: python user-interface canvas tkinter tkinter-canvas

我是Python和Tkinter的新手,因此,我无法弄清楚如何实现这个......

实际上,我有这个GUI:

enter image description here

,使用以下代码创建:

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方法,该方法在调用时闪烁第1行。问题是我无法中断Tkinter的主循环以更新元素......

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

我可以使用generate_event方法创建一个假事件处理程序来修改字母的颜色而不会干扰循环吗?如果是,怎么样?

提前致谢

2 个答案:

答案 0 :(得分:2)

这将帮助您入门。将self.canvasRoot.bind("<<foo>>", self.flashRow)更改为self.canvasRoot.after(10, self.flashRow)。这将导致flashRow运行一次。在flashRow方法的底部,如果你想重复运行flashRow,计划它将使用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)

答案 1 :(得分:2)

widget.after(ms, callback, *args)在第一次被程序读取后调用callback(*args) ms毫秒。因此,您可以定义一个回调方法,例如self.flashRow,然后在__init__中使用您的时间框架调用它,如:

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

或:

self.flashRow
只要他们在<{em> 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)