Как обновляются виджеты в Tkinter?

#python #user-interface #tkinter #widget

#python #пользовательский интерфейс #tkinter #виджет

Вопрос:

Хорошо, итак, я просто пытаюсь получить некоторые разъяснения о том, почему мой код работает не так, как я ожидал.

Я создаю графический интерфейс, и я хочу отображать текст в Label с помощью текстовой переменной. Я уже создал функцию, которая обновляет метку при вызове функции, но, конечно, это не моя проблема.

Моя проблема связана с тем, что я пытаюсь реализовать ярлык типа «печатать по одной букве за раз». Хотя он выводит на терминал так, как я хочу, виджет label обновляется только после завершения всей функции (визуально это то же самое, что просто печатать всю строку вместо печати буквы за раз).

Итак, чего мне не хватает, чего я не понимаю? Ребята, вы можете мне помочь? Позвольте мне опубликовать некоторый код, чтобы вы, ребята, могли увидеть, где моя ошибка.

Я попробовал оба из них независимо, и они оба выдали мне один и тот же результат, который был не тем, чего я желал.

 def feeder(phrase):
    """Takes a string and displays the content like video game dialog."""
    message = ""
    for letter in phrase:       
        time.sleep(.15)
        message  = letter
        information.set(message)
        #print message

def feeder2(phrase):
    """Same as feeder, but trying out recursion"""
    current.index  = 1
    if current.index <= len(phrase):
        information.set(phrase[:current.index])
        time.sleep(.1)
        feeder2(current.status())
  

Я не уверен, нужно ли мне публиковать больше кода, чтобы вы, ребята, могли лучше понять, но если это так, я это сделаю.

Эти 2 функции используются в этой функции

 def get_info():
    """This function sets the textvariable information."""
    #information.set(current)
    feeder2(current.status())
  

Который, в свою очередь, используется в этой функции

 def validate():
    """ This function checks our guess and keeps track of our statistics for us. This is the function run when we press the enter button. """
    current.turn  = 1
    if entry.get() == current.name:
        if entry.get() == "clearing":
                print "Not quite, but lets try again."
                current.guesses -= 1
        if entry.get() != "clearing":
            print "Great Guess!"
            current.points  = 1

    else:
        print "Not quite, but lets try again."
        current.guesses -= 1
    print current
    get_info()
    entry.delete(0, END)
    current.name = "clearing"
  

Ответ №1:

Пользовательский интерфейс будет обновляться каждый раз, когда вводится цикл событий. Это потому, что рисование выполняется с помощью событий (также известных как «задачи ожидания», потому что они выполняются, когда пользовательский интерфейс в противном случае простаивает).

Ваша проблема заключается в следующем: когда вы пишете цикл и выполняете time.sleep , цикл событий не будет введен во время выполнения этого цикла, поэтому перерисовка не произойдет.

Вы можете решить свою проблему по крайней мере парой различных способов. Во-первых, вы можете просто вызвать update_idletasks , который обновит экран. Это решит проблему с перерисовкой, но поскольку вы спите, пользовательский интерфейс не будет отвечать во время вашего цикла (поскольку нажатия кнопок и клавиш не являются «простаивающими задачами»).

Другим решением является написание функции, которая принимает строку, извлекает один символ из строки и добавляет его в виджет. Затем он организует повторный вызов самого себя через цикл событий. Например:

 import Tkinter as tk

class App(tk.Tk):
    def __init__(self,*args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        self.label = tk.Label(self, text="", width=20, anchor="w")
        self.label.pack(side="top",fill="both",expand=True)
        self.print_label_slowly("Hello, world!")

    def print_label_slowly(self, message):
        '''Print a label one character at a time using the event loop'''
        t = self.label.cget("text")
        t  = message[0]
        self.label.config(text=t)
        if len(message) > 1:
            self.after(500, self.print_label_slowly, message[1:])

app = App()
app.mainloop()
  

Этот тип решения гарантирует, что ваш пользовательский интерфейс остается отзывчивым, продолжая выполнять ваш код в цикле. Только вместо использования явного цикла вы добавляете работу к уже запущенному циклу событий.