Как вы запускаете свой собственный код вместе с циклом событий Tkinter? - PullRequest
104 голосов
/ 19 января 2009

Мой младший брат только начинает заниматься программированием, и для своего проекта Science Fair он симулирует стаю птиц в небе. Он написал большую часть своего кода, и он прекрасно работает, но птицам нужно двигаться каждый момент .

Tkinter, однако, тратит время на собственный цикл обработки событий, поэтому его код не запускается. Выполнение root.mainloop() выполняется, выполняется и продолжает работать, и единственное, что он запускает, - это обработчики событий.

Есть ли способ запустить его код параллельно основной лупе (без многопоточности, это сбивает с толку, и это должно быть простым), и если да, то что это?

Прямо сейчас он придумал уродливый хак, связав свою функцию move() с <b1-motion>, так что пока он удерживает кнопку нажатой и покачивает мышь, она работает. Но должен быть лучший способ.

Ответы [ 5 ]

123 голосов
/ 19 января 2009

Используйте метод 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."""
44 голосов
/ 02 декабря 2009

Решение , опубликованное Bjorn , приводит к появлению на моем компьютере сообщения «RuntimeError: Вызов Tcl из другой квартиры» (RedHat Enterprise 5, python 2.6.1). Бьорн, возможно, не получил это сообщение, поскольку, согласно в одном месте, которое я проверил , неправильная обработка потоков с помощью Tkinter непредсказуема и зависит от платформы.

Проблема, похоже, в том, что app.start() считается ссылкой на Tk, поскольку приложение содержит элементы Tk. Я исправил это, заменив app.start() на self.start() внутри __init__. Я также сделал так, чтобы все ссылки на Tk были либо внутри функции, которая вызывает 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)
17 голосов
/ 29 января 2011

При написании вашего собственного цикла, как в симуляции (я полагаю), вам нужно вызвать функцию update, которая делает то, что делает mainloop: обновляет окно с вашими изменениями, но вы делаете это в своем цикле .

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

while 1:
   task()  
4 голосов
/ 11 февраля 2009

Другим вариантом является запуск 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.

2 голосов
/ 25 октября 2016

Это первая рабочая версия того, что будет считывателем GPS и предъявителем данных. tkinter - очень хрупкая вещь с очень небольшим количеством сообщений об ошибках. Он не выносит вещи и не говорит, почему большую часть времени. Очень сложно поступить от хорошего разработчика форм WYSIWYG. Во всяком случае, это выполняется небольшая процедура 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()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...