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()? или оба в моих программах?

Ответы

Ответ 1

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

Эта подкоманда обновления очищает все текущие запланированные праздные события из очереди событий Tcl. Простаивающие события используются для отсрочки обработки пока "нет ничего другого", с типичным вариантом использования для они являются перепрограммированием Tk и пересчетами геометрии. Откладывая до тех пор, пока Tk не будет работать, дорогостоящие операции перерисовывания не будут выполняться до тех пор, пока все из кластера событий (например, отпускание кнопки, изменение текущее окно и т.д.) обрабатываются на уровне script. Это делает Tk кажутся намного быстрее, но если вы в середине делаете длинный он также может означать, что события бездействия не обрабатываются долгое время. Вызывая обновление idletasks, перерисовывает из-за внутренних изменения состояния обрабатываются немедленно. (Перерисовывается из-за системы события, например, будучи подтвержденными пользователем, необходимо полное обновление, которое должно быть обрабатывается.)

APN Как описано в "Обновлении", считается вредным, использование обновления для обработки перерисовки, не обрабатываемые обновлением idletasks, имеют много проблем. Джо Английский в публикации comp.lang.tcl описывается альтернатива:

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

В документах по обновлению:

Обновить? idletasks?

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

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

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

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

Что случилось с обновлением? Есть несколько ответов. Во-первых, он имеет тенденцию чтобы усложнить код окружающего GUI. Если вы работаете упражнения в программе "Обратный отсчет", вы почувствуете, насколько проще это может быть, когда каждое событие обрабатывается по собственному обратному вызову. Во-вторых, это источник коварных ошибок. Общая проблема заключается в том, что выполнение [обновление] имеет почти неограниченные побочные эффекты; по возвращении от [обновления], script может легко обнаружить, что ковер был вытащил из-под нее. Дальнейшее обсуждение этого вопроса феномен над 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() никогда не будет выполняться, и ваши виджеты никогда не будут отображаться. В программировании gui необходимо избегать бесконечных циклов, чтобы сохранить виджеты, реагирующие на ввод пользователя, например. щелчки мыши.

Итак, возникает вопрос: как вы выполняете что-то снова и снова, не создавая бесконечного цикла? У 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 comment
tk.mainloop()

Метод after() не блокирует (он фактически создает другой поток выполнения), поэтому выполнение продолжается в вашей программе python после вызова(), что означает, что 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 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. Я передаю ссылку на мой объект окна, который делает такие вещи, как кнопки ссылок, обратно для просмотра функций (которые также вызывает класс адаптера/контроллера).

Чтобы это сделать, конструктор объекта вида необходимо было завершить до создания объекта окна. После создания и отображения окна я хотел бы сделать некоторые начальные задачи с видом автоматически. Сначала я попытался сделать это после 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()