【问题标题】:Update an element of a canvas interrupting the main loop of Tkinter更新画布的元素,中断 Tkinter 的主循环
【发布时间】:2018-01-20 20:19:53
【问题描述】:

我对 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 方法来创建一个假事件处理程序来修改字母的颜色而不中断循环吗?如果是,怎么做?

提前致谢

【问题讨论】:

  • 为什么需要假事件,为什么不使用实际事件?
  • 请将您的代码缩减为minimal reproducible example
  • 我相信视频中的效果正是after的用途。您可以定期调用一个方法来选择每次调用时使用算法闪烁的行/列。
  • 例如,我对“实际事件”的理解是鼠标或键盘事件。在这种情况下,我不希望与键盘或鼠标进行任何交互。事实上,我需要实时生成照明序列(即必须闪烁的行和列)。因此,在我准备之前,我不知道需要刷新哪些行或列...
  • 当然,但是什么时候会开始出现照明序列?

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


【解决方案1】:

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)

【讨论】:

    【解决方案2】:

    这将使您入门。将self.canvasRoot.bind("&lt;&lt;foo&gt;&gt;", 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)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-09-27
      • 1970-01-01
      • 2012-01-26
      • 2019-12-30
      • 2010-12-17
      • 2012-01-12
      • 1970-01-01
      相关资源
      最近更新 更多