Tkinter의 이벤트 루프와 함께 자신의 코드를 어떻게 실행합니까?


119

제 동생은 이제 막 프로그래밍을 시작하고 있습니다. 그의 Science Fair 프로젝트를 위해 그는 하늘의 새 무리를 시뮬레이션하고 있습니다. 그는 대부분의 코드를 작성했고 잘 작동하지만 새들은 매 순간 움직여야 합니다 .

그러나 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으로 지정하면 작업이 완료되는 즉시 이벤트 루프에 다시 배치됩니다. 이것은 가능한 한 자주 코드를 실행하는 동안 다른 이벤트를 차단하지 않습니다.
Nathan

[X] 버튼을 클릭했을 때 opencv와 tkinter가 제대로 작동하고 깔끔하게 닫히도록 몇 시간 동안 머리카락을 뽑은 후 win32gui.FindWindow (None, 'window title')와 함께 트릭을 수행했습니다! 나는 ;-) 그런 멍청한 놈이야
JxAxMxIxN

이것은 최선의 선택이 아닙니다. 이 경우에는 작동하지만 대부분의 스크립트 (2 초마다 실행)에 적합하지 않으며 tkinter가 사용 중이 아닐 때만 실행되기 때문에 @Nathan이 게시 한 제안에 따라 시간 제한을 0으로 설정합니다. 복잡한 프로그램에서 문제를 일으킴). threading모듈 을 고수하는 것이 가장 좋습니다.
Anonymous

59

비요른에 의해 게시 솔루션 A의 결과 "RuntimeError에 : 다른 아파트에서 Tcl의 호출"메시지가 내 컴퓨터에 (레드햇 엔터프라이즈 5, 파이썬 2.6.1). Bjorn은 내가 확인한 한 곳에 따르면 Tkinter로 스레딩을 잘못 처리하는 것은 예측할 수없고 플랫폼에 따라 다르기 때문에이 메시지를받지 못했을 수 있습니다 .

문제는 app.start()앱에 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

5
일반적으로는 인수를 전달할 것 __init__(..)에 저장, self그리고 그들을 사용run(..)
앙드레 HOLZNER

1
루트가 전혀 표시되지 않고 경고를 표시합니다.`경고 : NSWindow 드래그 영역은 메인 스레드에서만 무효화되어야합니다! 이것은 미래에 예외를 던질 것입니다`
Bob Bobster

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모든 비용으로 호출 을 피해야 합니다.
Bryan Oakley

이 기술을 한 번 사용했습니다. 잘 작동하지만 어떻게 수행 하느냐에 따라 UI에 약간의 충격이있을 수 있습니다.
jldupont 2011-08-12

@Bryan Oakley 업데이트가 루프입니까? 그리고 그것이 어떻게 문제가 될까요?
Green05

6

또 다른 옵션은 tkinter가 별도의 스레드에서 실행되도록하는 것입니다. 한 가지 방법은 다음과 같습니다.

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 : main thread is not in main loop"가 표시됩니다.
jldupont 2011-08-12

5
jldupont : "RuntimeError : Calling Tcl from different appartment"(다른 버전에서 동일한 오류 일 수 있음)가 표시됩니다. 수정은 __init __ ()가 아니라 run ()에서 Tk를 초기화하는 것입니다. 이것은
mainloop

2

이것은 GPS 리더 및 데이터 발표자가 될 첫 번째 작업 버전입니다. tkinter는 오류 메시지가 너무 적은 매우 취약한 것입니다. 그것은 물건을 올려 놓지 않고 왜 많은 시간을 말하지 않습니다. 좋은 WYSIWYG 양식 개발자에게서 오는 것은 매우 어렵습니다. 어쨌든 이것은 1 초에 10 번 작은 루틴을 실행하고 양식에 정보를 표시합니다. 그것을 실현하는 데 시간이 걸렸습니다. 타이머 값 0을 시도했을 때 양식이 나타나지 않았습니다. 이제 머리가 아파요! 초당 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.