Tkinterのイベントループと一緒に独自のコードをどのように実行しますか?


119

私の弟はプログラミングを始めたばかりで、サイエンスフェアのプロジェクトでは、空を飛んでいる鳥の群れのシミュレーションを行っています。彼が書かれた彼のコードの大部分を得て、それがうまく動作しますが、鳥が移動する必要があるすべての瞬間を

ただし、Tkinterは独自のイベントループに時間を費やしているため、彼のコードは実行されません。やってroot.mainloop()実行、実行、およびランニング続け、そしてそれは、イベントハンドラで実行される唯一のものを。

彼のコードをメインループと一緒に実行する方法はありますか(マルチスレッド化なしでは混乱を招き、これは単純に保つ必要があります)。そうであれば、それは何ですか?

現在、彼は醜いハックを思いつき、自分のmove()機能をに結び付けました<b1-motion>。そのため、ボタンを押したままマウスを揺らす限り、機能します。しかし、もっと良い方法があるはずです。

回答:


141

オブジェクトでafterメソッドを使用しTkます。

from tkinter import *

root = Tk()

def task():
    print("hello")
    root.after(2000, task)  # reschedule event in 2 seconds

root.after(2000, task)
root.mainloop()

afterメソッドの宣言とドキュメントは次のとおりです。

def after(self, ms, func=None, *args):
    """Call function once after given time.

    MS specifies the time in milliseconds. FUNC gives the
    function which shall be called. Additional parameters
    are given as parameters to the function call.  Return
    identifier to cancel scheduling with after_cancel."""

30
タイムアウトを0に指定すると、タスクは終了直後にイベントループに戻ります。これは、コードを可能な限り頻繁に実行しながら、他のイベントをブロックしません。
ネイサン

openCvとtkinterが適切に連携し、[X]ボタンがクリックされたときにきちんと閉じられるように何時間も髪を引っ張った後、これとwin32gui.FindWindow(None、 'window title')がうまく機能しました!私はそのような
初心者

これは最良のオプションではありません。この場合は機能しますが、ほとんどのスクリプト(2秒ごとにのみ実行されます)には適しておらず、@ Nathanによって投稿された提案に従ってタイムアウトを0に設定します。これは、tkinterがビジーでないときにのみ実行されるためです(これは、一部の複雑なプログラムで問題が発生します)。threadingモジュールに固執するのが最善です。
匿名

59

ビョルンので掲示ソリューションで結果「のRuntimeError:別のアパートからのTclを呼び出す」のメッセージ私のコンピュータ上の(RedHatのエンタープライズ5、パイソン2.6.1)。私がチェックしたある場所によると、Tkinterでのスレッド処理の誤操作は予測不可能であり、プラットフォームに依存しているため、ビョルンはこのメッセージを受け取っていない可能性があります。

app.start()appにはTk要素が含まれているため、問題はTkへの参照としてカウントされるようです。これを内部に置き換えることで修正app.start()しました。また、すべてのTk参照が、呼び出す関数内または呼び出す関数によって呼び出される関数内にあるようにしました(これは、「異なるアパートメント」エラーを回避するために明らかに重要です)。self.start()__init__mainloop()mainloop()

最後に、コールバック付きのプロトコルハンドラーを追加しました。これがないと、ユーザーがTkウィンドウを閉じると、プログラムがエラーで終了します。

改訂されたコードは次のとおりです。

# Run tkinter code in another thread

import tkinter as tk
import threading

class App(threading.Thread):

    def __init__(self):
        threading.Thread.__init__(self)
        self.start()

    def callback(self):
        self.root.quit()

    def run(self):
        self.root = tk.Tk()
        self.root.protocol("WM_DELETE_WINDOW", self.callback)

        label = tk.Label(self.root, text="Hello World")
        label.pack()

        self.root.mainloop()


app = App()
print('Now we can continue running code while mainloop runs!')

for i in range(100000):
    print(i)

runメソッドに引数をどのように渡しますか?方法がわからないようです...
TheDoctor 2014年

5
通常__init__(..)、引数をに渡し、それらを保存してself使用しますrun(..)
Andre Holzner

1
ルートはまったく表示されず、次の警告が表示されます。 `警告:NSWindowドラッグ領域は、メインスレッドでのみ無効にする必要があります。これは将来的に例外をスローします `
ボブボブスター

1
このコメントはより多くの認識に値します。すごい。
Daniel Reyhanian

これは命の恩人です。GUIの終了後、Pythonスクリプトを終了できない場合は、GUIの外のコードでtkinterスレッドが有効かどうかを確認する必要があります。次のようなものwhile app.is_alive(): etc
m3nda

21

シミュレーションのように独自のループを作成する場合(私は想定します)、update関数が実行する機能を呼び出す必要がありmainloopます。変更内容でウィンドウを更新しますが、ループ内で実行します。

def task():
   # do something
   root.update()

while 1:
   task()  

10
あなたはする必要が非常にプログラミングのこの種には注意してください。イベントtaskが呼び出されると、ネストされたイベントループが発生し、それは悪いことです。イベントループがどのように機能するかを完全に理解していない限り、絶対に呼び出すことは避けてくださいupdate
ブライアンオークリー、2011

私はこのテクニックを1回使用しましたが、問題なく動作しますが、その方法によっては、UIに多少のずれが生じる場合があります。
jldupont 2011

@ブライアンオークリー更新ループですか?そして、それはどのように問題がありますか?
Green05

6

別のオプションは、tkinterを別のスレッドで実行させることです。これを行う1つの方法は次のとおりです。

import Tkinter
import threading

class MyTkApp(threading.Thread):
    def __init__(self):
        self.root=Tkinter.Tk()
        self.s = Tkinter.StringVar()
        self.s.set('Foo')
        l = Tkinter.Label(self.root,textvariable=self.s)
        l.pack()
        threading.Thread.__init__(self)

    def run(self):
        self.root.mainloop()


app = MyTkApp()
app.start()

# Now the app should be running and the value shown on the label
# can be changed by changing the member variable s.
# Like this:
# app.s.set('Bar')

ただし注意してください。マルチスレッドプログラミングは困難であり、自分の足を撃ち抜くのは本当に簡単です。たとえば、上記のサンプルクラスのメンバー変数を変更するときは、Tkinterのイベントループで中断しないように注意する必要があります。


3
これが機能するかどうかはわかりません。同様のことを試したところ、「RuntimeError:メインスレッドがメインループにありません」と表示されました。
jldupont 2011

5
jldupont:「RuntimeError:Calling Tcl from different appartment」(お​​そらく別のバージョンで同じエラー)が発生しました。修正は、__ init __()ではなくrun()でTkを初期化することでした。。あなたがメインループ()を呼び出すと、あなたが同じスレッドでのTkを初期化していることを、この手段
mgiuca

2

これは、GPSリーダーとデータプレゼンターになる最初の作業バージョンです。tkinterは非常に壊れやすいものであり、エラーメッセージが少なすぎます。それは物事を上げず、なぜ多くの場合理由を教えません。優れたWYSIWYGフォームの開発者から来ることは非常に困難です。とにかく、これは小さなルーチンを1秒間に10回実行し、フォームに情報を表示します。それを実現するためにしばらくかかりました。タイマー値0を試したところ、フォームが表示されませんでした。頭が痛い!1秒あたり10回以上で十分です。それが他の誰かの役に立つことを願っています。マイク・モロー

import tkinter as tk
import time

def GetDateTime():
  # Get current date and time in ISO8601
  # https://en.wikipedia.org/wiki/ISO_8601 
  # https://xkcd.com/1179/
  return (time.strftime("%Y%m%d", time.gmtime()),
          time.strftime("%H%M%S", time.gmtime()),
          time.strftime("%Y%m%d", time.localtime()),
          time.strftime("%H%M%S", time.localtime()))

class Application(tk.Frame):

  def __init__(self, master):

    fontsize = 12
    textwidth = 9

    tk.Frame.__init__(self, master)
    self.pack()

    tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth,
             text='Local Time').grid(row=0, column=0)
    self.LocalDate = tk.StringVar()
    self.LocalDate.set('waiting...')
    tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth,
             textvariable=self.LocalDate).grid(row=0, column=1)

    tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth,
             text='Local Date').grid(row=1, column=0)
    self.LocalTime = tk.StringVar()
    self.LocalTime.set('waiting...')
    tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth,
             textvariable=self.LocalTime).grid(row=1, column=1)

    tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth,
             text='GMT Time').grid(row=2, column=0)
    self.nowGdate = tk.StringVar()
    self.nowGdate.set('waiting...')
    tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth,
             textvariable=self.nowGdate).grid(row=2, column=1)

    tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth,
             text='GMT Date').grid(row=3, column=0)
    self.nowGtime = tk.StringVar()
    self.nowGtime.set('waiting...')
    tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth,
             textvariable=self.nowGtime).grid(row=3, column=1)

    tk.Button(self, text='Exit', width = 10, bg = '#FF8080', command=root.destroy).grid(row=4, columnspan=2)

    self.gettime()
  pass

  def gettime(self):
    gdt, gtm, ldt, ltm = GetDateTime()
    gdt = gdt[0:4] + '/' + gdt[4:6] + '/' + gdt[6:8]
    gtm = gtm[0:2] + ':' + gtm[2:4] + ':' + gtm[4:6] + ' Z'  
    ldt = ldt[0:4] + '/' + ldt[4:6] + '/' + ldt[6:8]
    ltm = ltm[0:2] + ':' + ltm[2:4] + ':' + ltm[4:6]  
    self.nowGtime.set(gdt)
    self.nowGdate.set(gtm)
    self.LocalTime.set(ldt)
    self.LocalDate.set(ltm)

    self.after(100, self.gettime)
   #print (ltm)  # Prove it is running this and the external code, too.
  pass

root = tk.Tk()
root.wm_title('Temp Converter')
app = Application(master=root)

w = 200 # width for the Tk root
h = 125 # height for the Tk root

# get display screen width and height
ws = root.winfo_screenwidth()  # width of the screen
hs = root.winfo_screenheight() # height of the screen

# calculate x and y coordinates for positioning the Tk root window

#centered
#x = (ws/2) - (w/2)
#y = (hs/2) - (h/2)

#right bottom corner (misfires in Win10 putting it too low. OK in Ubuntu)
x = ws - w
y = hs - h - 35  # -35 fixes it, more or less, for Win10

#set the dimensions of the screen and where it is placed
root.geometry('%dx%d+%d+%d' % (w, h, x, y))

root.mainloop()
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.