Как вы запускаете свой собственный код вместе с циклом событий Tkinter?

StackOverflow https://stackoverflow.com/questions/459083

  •  19-08-2019
  •  | 
  •  

Вопрос

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

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

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

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

Это было полезно?

Решение

Используйте метод 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()

Вот объявление и документация для метода <=>:

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."""

Другие советы

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

Кажется, проблема в том, что app.start() считается ссылкой на Tk, поскольку приложение содержит элементы Tk. Я исправил это, заменив self.start() на __init__ внутри mainloop(). Я также сделал так, чтобы все ссылки на Tk были либо внутри функции, которая вызывает <=> , либо внутри функций, которые вызываются функцией, которая вызывает <=> (это по-видимому, важно избежать & «другой квартиры &« ошибка).

Наконец, я добавил обработчик протокола с обратным вызовом, поскольку без этого программа завершает работу с ошибкой, когда окно 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)

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

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

while 1:
   task()  

Другой вариант - запустить 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.

Это первая рабочая версия того, что будет считывателем 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()
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top