1. ホーム
  2. python

[解決済み] Tkinterのメインループを理解する

2022-03-04 14:07:18

質問

今まで、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()

しかし、このプログラムの次のステップ(時間でボールを動かす)をやろうとすると、私が読んでいる本には、次のように書いてあります。そこで、draw関数を次のように変更した。

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を使用しているためか)本のコードをコピーしてもプログラムが動かなかったので、Webで検索して知りました!

そこで、以下のようにやってみたのですが、うまくいきませんでした!!!!

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

どうしたんですか?何が tk.mainloop() ? とは何ですか? tk.update_idletasks()tk.update() とはどのように違うのですか? tk.mainloop() ? 上記のループを使うべきでしょうか? tk.mainloop() それとも両方?

どのように解決するのですか?

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の更新

このサブコマンドは、現在スケジュールされているすべてのアイドルイベントをフラッシュします。 をTclのイベント・キューから削除する。アイドル・イベントは、処理を延期するために使用される 他にすることがない」まで、典型的な使用例です。 Tkの再描画やジオメトリの再計算がこれにあたる。延期することで Tkがアイドル状態になるまで、高価な再描画は行われません。 イベント群(例:ボタンの離し方、イベントの変更など)のすべてが カレントウィンドウなど)がスクリプトレベルで処理されます。このため、Tk しかし、長い時間をかけて作業している最中であれば、より高速に見えるでしょう。 この場合、アイドルイベントが処理されないことになります。 を長い間使用します。idletasksの更新を呼び出すことで、内部処理による再描画を行うことができます。 の状態変化は直ちに処理されます。(システムによる再描画は即座に処理されます。 イベント、例えば、ユーザーによってアイコンが削除された場合、完全なアップデートが必要です。 処理されます)。

APN アップデートは有害であると考えられるで説明したように、アップデートを使用すると update idletasksで処理されない再描画には多くの問題があります。ジョー・イングリッシュ の投稿で、代替案を説明しています。

そこで update_idletasks() は、あるイベントのサブセットを処理させるために update() を処理させる。

から アップデートドキュメント :

update ?idletasks??idletasks??idletasks?

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

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

KBK (2000 年 2 月 12 日) -- 個人的な意見としては、[update] は。 コマンドは、ベストプラクティスの一つではありません。 避けることをお勧めします。私はこれまで、[update]の使い方として、以下のようなものはほとんど見たことがありません。 他の手段でより効果的にプログラムすることができるかもしれません。 イベント・コールバックの適切な使用。ちなみに、この注意は すべてのTclコマンド(vwaitとtkwaitは他の一般的なコマンドです。 イベントループに再帰的に入るものは、例外です。 グローバルレベルで1つの[vwait]を使って、イベントループの中で 自動的に起動しないシェルの場合。

update]が推奨される目的としては、よく見かけるのは

  1. 長時間実行される計算がある間、GUI を維持する。 を実行します。代替案として、カウントダウンプログラムを参照してください。2) ウィンドウが設定されるのを待ってから、次のようなことをする。 ジオメトリ管理 のようなイベントにバインドすることである。 ウィンドウのジオメトリをプロセスに通知するようなもの。参照 ウィンドウを中央に配置する方法もあります。

updateの何が問題なのでしょうか?答えはいくつかあります。第一に、それは は、周囲のGUIのコードを複雑にしてしまいます。もし、あなたが カウントダウン・プログラムの練習問題を見れば、いかに 各イベントが独自のコールバックで処理されるようになれば、より簡単になります。 第二に、陰湿なバグの原因になることです。一般的な問題としては update]を実行すると、ほぼ無制限に副作用が発生します。 スクリプトは、[update]からは、簡単に絨毯を敷かれたことに気づくことができます。 を引き出す。この点については Update considered harmfulにある現象です。

.....

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() メソッドでは ブロック (つまり、tk.mainloop() が次に実行され、ウィジェットが設定され表示されます。 つまり、tk.mainloop() が次に実行されるので、ウィジェットの設定と表示が行われます。 次のプログラムを実行し、キャンバス上のさまざまな場所でマウスをクリックしてみてください。

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()