Понимание Tkinter mainloop
До сих пор я заканчивал свои программы на Tkinter с помощью: 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()
? или оба в моих программах?
Переведено автоматически
Ответ 1
tk.mainloop()
блокирует. Это означает, что выполнение ваших команд Python там останавливается. Вы можете увидеть это, написав:
while 1:
ball.draw()
tk.mainloop()
print("hello") #NEW CODE
time.sleep(0.01)
Вы никогда не увидите выходные данные инструкции print . Поскольку цикла нет, шарик не движется.
С другой стороны, методы 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:
Обновление idletasks
Эта подкоманда update удаляет все запланированные в данный момент события простоя из очереди событий Tcl. События простоя используются для отсрочки обработки до тех пор, пока “больше нечего будет делать”, при этом типичным вариантом их использования является перерисовка Tk и пересчет геометрии. Откладывая их до тех пор, пока Tk не простаивает, дорогостоящие операции перерисовки не выполняются до тех пор, пока все из кластера событий (например, нажатие кнопки, изменение текущего окна и т.д.) Не будут обработаны на уровне сценария. Это делает Tk намного быстрее, но если вы находитесь в процессе длительной обработки, это также может означать, что в течение длительного времени не обрабатываются события ожидания. При вызове update idletasks перерисовки из-за внутренних изменений состояния обрабатываются немедленно. (Перерисовки из-за системных событий, например, деиконификации пользователем, требуют полного обновления для обработки.)
APN, как описано в разделе Update считается вредным, использование update для обработки перерисовок, не обрабатываемых update idletasks, вызывает много проблем. Джо Инглиш в публикации comp.lang.tcl описывает альтернативу:
So update_idletasks()
вызывает обработку некоторого подмножества событий, которые update()
вызывают обработку.
Из документации по обновлению:
обновление ? idletasks?
Команда update используется для “обновления” приложения путем повторного ввода цикла событий Tcl до тех пор, пока не будут обработаны все ожидающие события (включая бездействующие обратные вызовы).
Если ключевое слово idletasks указано в качестве аргумента команды, то никакие новые события или ошибки не обрабатываются; вызываются только idle callbacks. Это приводит к немедленному выполнению операций, которые обычно откладываются, таких как обновления дисплея и вычисления макета окна.
KBK (12 февраля 2000 г.) -- Мое личное мнение таково, что команда [update] не является одной из лучших практик, и программисту настоятельно рекомендуется избегать ее. Я редко, если вообще когда-либо видел использование [update], которое нельзя было бы более эффективно запрограммировать другими средствами, обычно уместно использовать обратные вызовы событий. Кстати, это предостережение применимо ко всем командам Tcl (vwait и tkwait - другие распространенные виновники), которые рекурсивно входят в цикл событий, за исключением использования одного [vwait] на глобальном уровне для запуска цикла событий внутри оболочки, которая не запускает его автоматически.
Наиболее распространенные цели, для которых я видел рекомендуемые [обновление], следующие:
- Поддерживает работу графического интерфейса во время выполнения некоторых длительных вычислений. Альтернативный вариант см. в разделе Программа обратного отсчета. 2) Ожидает настройки окна, прежде чем выполнять в нем такие действия, как управление геометрией. Альтернативой является привязка к событиям, подобным тем, которые уведомляют процесс о геометрии окна. Альтернативный вариант см. в разделе Центрирование окна.
Что не так с обновлением? Есть несколько ответов. Во-первых, это имеет тенденцию усложнять код окружающего графического интерфейса. Если вы отработаете упражнения в программе обратного отсчета, вы почувствуете, насколько это может быть проще, когда каждое событие обрабатывается в отдельном обратном вызове. Во-вторых, это источник коварных ошибок. Общая проблема заключается в том, что выполнение [update] имеет почти неограниченные побочные эффекты; по возвращении из [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()
Ответ 2
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.
Ответ 3
Я использую шаблон проектирования MVC / MVA с несколькими типами "представлений". Один из типов - это "GuiView", который представляет собой окно Tk. Я передаю ссылку view на свой объект window, который выполняет такие действия, как ссылки на кнопки, обратно для просмотра функций (которые также вызывает класс адаптера / контроллера).
Для этого необходимо было завершить работу с конструктором объекта view до создания объекта window. После создания и отображения окна я хотел выполнить некоторые начальные задачи с view автоматически. Сначала я попытался выполнить их post mainloop(), но это не сработало, потому что mainloop () заблокирован!
Таким образом, я создал объект window и использовал tk.update() для его рисования. Затем я приступил к выполнению своих первоначальных задач и, наконец, запустил mainloop.
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()