Tkinter понимание mainloop

До сих пор я заканчивал свои программы на Tkiter: tk.mainloop() или ничего не появится! Смотрите пример:

from Tkinter import *
import random
import time

tk = Tk()
tk.title = "Game"
tk.resizable(0,0)
tk.wm_attributes("-topmost", 1)

canvas = Canvas(tk, width=500, height=400, bd=0, highlightthickness=0)
canvas.pack()

class Ball:
    def __init__(self, canvas, color):
        self.canvas = canvas
        self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
        self.canvas.move(self.id, 245, 100)
    def draw(self):
        pass

ball = Ball(canvas, "red")

tk.mainloop()

Однако, когда попытался сделать следующий шаг в этой программе (заставить мяч двигаться по времени), книга, из которой я читаю, говорит сделать следующее. Измените функцию рисования на:

def draw(self):
    self.canvas.move(self.id, 0, -1)

и добавьте следующий код в мою программу:

while 1:
    ball.draw()
    tk.update_idletasks()
    tk.update()
    time.sleep(0.01)

Но я заметил, что добавление этого блока кода, сделал использование tk.mainloop() бесполезно, так как все появилось бы даже без него!!!

На данный момент я должен отметить, что моя книга никогда не говорит о tk.mainloop() (может быть, потому что он использует Python 3), но я узнал об этом в поиске в Интернете, так как мои программы не работали, копируя код книги!

Поэтому я попытался сделать следующее, что не будет работать!!!

while 1:
    ball.draw()
    tk.mainloop()
    time.sleep(0.01)

В чем дело? Что значит tk.mainloop()? Что значит tk.update_idletasks() а также tk.update() и чем это отличается от tk.mainloop()? Должен ли я использовать вышеуказанный цикл? tk.mainloop()? или оба в моих программах?

3 ответа

Решение

tk.mainloop() блоки Это означает, что выполнение вашей программы на Python останавливается там. Вы можете увидеть это, написав:

while 1:
    ball.draw()
    tk.mainloop()
    print "hello"   #NEW CODE
    time.sleep(0.01)

Вы никогда не увидите вывод из оператора печати. Поскольку петли нет, мяч не двигается.

С другой стороны, методы update_idletasks() а также update() Вот:

while True:
    ball.draw()
    tk.update_idletasks()
    tk.update()

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

Бесконечный цикл, содержащий вызовы метода update_idletasks() а также update() может выступать в качестве замены для вызова tk.mainloop(), Обратите внимание, что весь цикл while можно сказать, что он блокируется так же, как tk.mainloop() потому что ничего после цикла while не будет выполнено.

Тем не мение, tk.mainloop() не заменяет только строки:

tk.update_idletasks()
tk.update()

Скорее, tk.mainloop() является заменой всего цикла while:

while True:
    tk.update_idletasks()
    tk.update()

Ответ на комментарий:

Вот что говорят tcl docs:

обновить idletasks

Эта подкоманда update сбрасывает все запланированные в настоящий момент незанятые события из очереди событий Tcl. Неактивные события используются для того, чтобы отложить обработку до тех пор, пока "больше ничего не нужно делать", причем типичным вариантом использования для них являются перерисовка и пересчет геометрии Tk. Откладывая их до простоя Tk, дорогостоящие операции перерисовки не выполняются до тех пор, пока все на уровне кластера событий (например, отпускание кнопки, изменение текущего окна и т. Д.) Не будет обработано на уровне сценария. Это заставляет Tk казаться намного быстрее, но если вы находитесь в процессе выполнения длительной обработки, это также может означать, что неактивные события не обрабатываются в течение длительного времени. При вызове обновлений idletas, перерисовки из-за внутренних изменений состояния обрабатываются немедленно. (Перерисовки из-за системных событий, например, деиконифицированные пользователем, требуют полного обновления для обработки.)

APN Как описано в Обновлении, которое считается вредным, использование обновления для обработки перерисовок, не обрабатываемых задачами обновления, имеет много проблем. Джо Инглиш в сообщении comp.lang.tcl описывает альтернативу:

Так update_idletasks() вызывает обработку некоторого подмножества событий, update() причины для обработки.

Из обновленных документов:

обновить? idletasks?

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

Если в качестве аргумента команды указано ключевое слово idletasks, новые события или ошибки не обрабатываются; только пустые обратные вызовы вызываются. Это приводит к немедленному выполнению операций, которые обычно откладываются, таких как обновления отображения и вычисления макета окна.

KBK (12 февраля 2000 г.) - Мое личное мнение таково, что команда [update] не является одной из лучших практик, и программисту рекомендуется избегать ее. Я редко когда-либо видел использование [update], которое нельзя было бы более эффективно запрограммировать другим способом, как правило, соответствующим использованием обратных вызовов событий. Кстати, это предупреждение относится ко всем командам Tcl (vwait и tkwait - другие распространенные виновники), которые рекурсивно входят в цикл событий, за исключением использования одного [vwait] на глобальном уровне для запуска цикла событий внутри оболочки это не запускает его автоматически.

Наиболее распространенные цели, для которых я рекомендовал [обновление], это: 1) Поддержание живого графического интерфейса во время выполнения некоторых длительных вычислений. Смотрите программу обратного отсчета для альтернативы. 2) Ожидание настройки окна, прежде чем выполнять такие операции, как управление геометрией. Альтернативой является привязка к таким событиям, как уведомление о процессе геометрии окна. См. Центрирование окна для альтернативы.

Что не так с обновлением? Есть несколько ответов. Во-первых, это усложняет код окружающего GUI. Если вы будете выполнять упражнения в программе обратного отсчета, вы почувствуете, насколько проще это сделать, когда каждое событие обрабатывается по своему собственному обратному вызову. Во-вторых, это источник коварных ошибок. Общая проблема заключается в том, что выполнение [update] имеет почти неограниченные побочные эффекты; по возвращении из [update] скрипт может легко обнаружить, что коврик был извлечен из-под него. Дальнейшее обсуждение этого явления в обновлении считается вредным.

.....

Есть ли шанс, что я смогу заставить мою программу работать без цикла while?

Да, но все становится немного сложнее. Вы можете подумать, что будет работать что-то вроде следующего:

class Ball:
    def __init__(self, canvas, color):
        self.canvas = canvas
        self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
        self.canvas.move(self.id, 245, 100)

    def draw(self):
        while True:
           self.canvas.move(self.id, 0, -1)

ball = Ball(canvas, "red")
ball.draw()
tk.mainloop()

Проблема в том, что ball.draw() заставит выполнение войти в бесконечный цикл в методе draw (), поэтому tk.mainloop() никогда не будет выполняться, и ваши виджеты никогда не будут отображаться. В графическом программировании необходимо избегать бесконечных циклов любой ценой, чтобы виджеты реагировали на ввод пользователя, например, щелчки мышью.

Итак, вопрос: как вы выполняете что-то снова и снова, не создавая бесконечный цикл? У Tkinter есть ответ на эту проблему: виджет after() метод:

from Tkinter import *
import random
import time

tk = Tk()
tk.title = "Game"
tk.resizable(0,0)
tk.wm_attributes("-topmost", 1)

canvas = Canvas(tk, width=500, height=400, bd=0, highlightthickness=0)
canvas.pack()

class Ball:
    def __init__(self, canvas, color):
        self.canvas = canvas
        self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
        self.canvas.move(self.id, 245, 100)

    def draw(self):
        self.canvas.move(self.id, 0, -1)
        self.canvas.after(1, self.draw)  #(time_delay, method_to_execute)




ball = Ball(canvas, "red")
ball.draw()  #Changed per Bryan Oakley's comment
tk.mainloop()

Метод after() не блокируется (он фактически создает другой поток выполнения), поэтому выполнение продолжается в вашей программе на python после вызова after(), что означает, что tk.mainloop() выполняется следующим образом, поэтому ваши виджеты настраиваются и отображается. Метод after() также позволяет вашим виджетам реагировать на действия других пользователей. Попробуйте запустить следующую программу, а затем щелкните мышью в разных местах холста:

from Tkinter import *
import random
import time

root = Tk()
root.title = "Game"
root.resizable(0,0)
root.wm_attributes("-topmost", 1)

canvas = Canvas(root, width=500, height=400, bd=0, highlightthickness=0)
canvas.pack()

class Ball:
    def __init__(self, canvas, color):
        self.canvas = canvas
        self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
        self.canvas.move(self.id, 245, 100)

        self.canvas.bind("<Button-1>", self.canvas_onclick)
        self.text_id = self.canvas.create_text(300, 200, anchor='se')
        self.canvas.itemconfig(self.text_id, text='hello')

    def canvas_onclick(self, event):
        self.canvas.itemconfig(
            self.text_id, 
            text="You clicked at ({}, {})".format(event.x, event.y)
        )

    def draw(self):
        self.canvas.move(self.id, 0, -1)
        self.canvas.after(50, self.draw)




ball = Ball(canvas, "red")
ball.draw()  #Changed per Bryan Oakley's comment.
root.mainloop()
while 1:
    root.update()

... примерно (очень!) похож на:

root.mainloop()

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

while the_window_has_not_been_destroyed():
    wait_until_the_event_queue_is_not_empty()
    event = event_queue.pop()
    event.handle()

... который, как вы можете видеть, не сильно отличается от вашего собственного цикла while. Итак, зачем создавать свой собственный бесконечный цикл, когда у tkinter уже есть тот, который вы можете использовать?

Поместите в самые простые сроки: всегда звоните mainloop как последняя логическая строка кода в вашей программе. Вот как Tkinter был разработан для использования.

Я использую шаблон проектирования MVC / MVA с несколькими типами "представлений". Один тип - это "GuiView", который является окном Tk. Я передаю ссылку на представление моему объекту window, который выполняет такие функции, как кнопки ссылки, для просмотра функций (которые также вызывает класс адаптера / контроллера).

Чтобы сделать это, конструктор объекта представления должен был быть завершен до создания объекта окна. После создания и отображения окна я хотел автоматически выполнить некоторые начальные задачи с представлением. Сначала я попытался сделать их post mainloop(), но это не сработало, потому что mainloop () заблокирован!

Таким образом, я создал объект окна и использовал tk.update(), чтобы нарисовать его. Затем я начал свои начальные задачи и, наконец, начал основной цикл.

import Tkinter as tk

class Window(tk.Frame):
    def __init__(self, master=None, view=None ):
        tk.Frame.__init__( self, master )
        self.view_ = view       
        """ Setup window linking it to the view... """

class GuiView( MyViewSuperClass ):

    def open( self ):
        self.tkRoot_ = tk.Tk()
        self.window_ = Window( master=None, view=self )
        self.window_.pack()
        self.refresh()
        self.onOpen()
        self.tkRoot_.mainloop()         

    def onOpen( self ):        
        """ Do some initial tasks... """

    def refresh( self ):        
        self.tkRoot_.update()
Другие вопросы по тегам