web-dev-qa-db-ja.com

メインループを理解するTkinter

今まで、私は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()?または私のプログラムの両方?

45
midkin

tk.mainloop()blocks。つまり、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()と同様にblockと言えることに注意してください。whileループの後には何も実行されないからです。

ただし、tk.mainloop()は単なる行の代替ではありません。

tk.update_idletasks()
tk.update()

むしろ、tk.mainloop()はwhileループ全体の代替です。

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

コメントへの応答:

tcl docs の意味は次のとおりです。

アイドルタスクを更新する

更新のこのサブコマンドは、現在スケジュールされているすべてのアイドルイベントをTclのイベントキューからフラッシュします。アイドルイベントは、「他にやることがない」まで処理を延期するために使用され、それらの典型的なユースケースはTkの再描画とジオメトリの再計算です。 Tkがアイドルになるまでこれらを延期することにより、イベントのクラスター(ボタンリリース、現在のウィンドウの変更など)からのすべてがスクリプトレベルで処理されるまで、高価な再描画操作は行われません。これにより、Tkははるかに高速に見えますが、長時間実行される処理を実行している場合は、アイドルイベントが長時間処理されないことも意味します。 update idletasksを呼び出すことにより、状態の内部変化による再描画がすぐに処理されます。 (たとえば、ユーザーがアイコン化を解除するなどのシステムイベントのために再描画するには、完全な更新を処理する必要があります。)

APNアップデートで説明されているように有害であるため、アップデートアイドルタスクで処理されない再描画を処理するアップデートの使用には多くの問題があります。 comp.lang.tcl投稿のJoe Englishは、代替手段について説明しています。

したがって、update_idletasks()により、update()により処理されるイベントのサブセットが処理されます。

ドキュメントの更新 から:

更新?アイドルタスク?

Updateコマンドは、すべての保留中のイベント(アイドルコールバックを含む)が処理されるまで繰り返しTclイベントループに入ることにより、アプリケーションを「最新」にするために使用されます。

Idletasksキーワードがコマンドの引数として指定されている場合、新しいイベントやエラーは処理されません。アイドルコールバックのみが呼び出されます。これにより、表示の更新やウィンドウレイアウトの計算など、通常延期される操作がすぐに実行されます。

KBK(2000年2月12日)-私の意見では、[更新]コマンドはベストプラクティスの1つではないため、プログラマは回避することをお勧めします。他の手段、一般的にはイベントコールバックの適切な使用によって、より効果的にプログラムできない[更新]の使用を見たことはほとんどありません。ところで、この注意は、シェル内でイベントループを起動するためにグローバルレベルで単一の[vwait]を使用することを除いて、イベントループに再帰的に入るすべてのTclコマンド(vwaitとtkwaitは他の一般的な犯人です)に適用されますそれは自動的に起動しません。

[更新]が推奨される最も一般的な目的は次のとおりです。1)長時間実行される計算の実行中にGUIを維持する。別の方法については、カウントダウンプログラムを参照してください。 2)ジオメトリ管理などを行う前に、ウィンドウが設定されるのを待ちます。別の方法は、ウィンドウのジオメトリのプロセスに通知するようなイベントをバインドすることです。別の方法については、ウィンドウの中央揃えを参照してください。

アップデートの何が問題になっていますか?いくつかの答えがあります。まず、周囲のGUIのコードが複雑になる傾向があります。 Countdownプログラムでエクササイズを行うと、各イベントが独自のコールバックで処理されるときにどれだけ簡単になるかを感じることができます。第二に、潜在的なバグの原因です。一般的な問題は、[更新]を実行すると、ほとんど制約のない副作用があることです。 [更新]から戻ると、スクリプトはその下からラグが引き出されたことを簡単に発見できます。この現象については、有害であると考えられる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's comment
tk.mainloop()

After()メソッドはblockをブロックしません(実際に別の実行スレッドを作成します)。したがって、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's comment.
root.mainloop()
68
7stud
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が使用されるように設計された方法です。

11
Bryan Oakley

複数のタイプの「ビュー」を備えたMVC/MVAデザインパターンを使用しています。 1つのタイプは、Tkウィンドウである「GuiView」です。リンクボタンなどの機能を実行するウィンドウオブジェクトへのビュー参照を渡します(アダプター/コントローラークラスも呼び出します)。

そのためには、ウィンドウオブジェクトを作成する前に、ビューオブジェクトコンストラクターを完了する必要がありました。ウィンドウを作成して表示した後、ビューでいくつかの初期タスクを自動的に行いたいと思いました。最初は、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()
1
BuvinJ