Comment utilisez-vous votre propre code à côté de la boucle d'événement de Tkinter?

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

  •  19-08-2019
  •  | 
  •  

Question

Mon petit frère commence à peine à programmer et, pour son projet Expo-sciences, il simule une volée d'oiseaux dans le ciel. La plupart de ses codes ont été écrits, et cela fonctionne bien, mais les oiseaux doivent se déplacer à chaque instant .

Tkinter, cependant, raccroche le temps de sa propre boucle d’événements et son code ne fonctionnera donc pas. Faire root.mainloop() exécute, exécute et continue à fonctionner, et les seuls gestionnaires exécutés sont les gestionnaires d'événements.

Existe-t-il un moyen de faire en sorte que son code soit exécuté parallèlement à la boucle principale (sans lecture multiple, c'est compliqué et cela devrait rester simple), et si oui, de quoi s'agit-il?

À l’heure actuelle, il a trouvé un vilain piratage, liant sa move() fonction à <b1-motion>, de sorte que tant qu’il maintient le bouton enfoncé et fait bouger la souris, cela fonctionne. Mais il doit y avoir un meilleur moyen.

Était-ce utile?

La solution

Utilisez la méthode after sur l'objet 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()

Voici la déclaration et la documentation de la <=> méthode:

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

Autres conseils

La solution publiée par Bjorn génère un & "; RuntimeError: appel de Tcl depuis un appartement différent " message sur mon ordinateur (RedHat Enterprise 5, python 2.6.1). Bjorn n’a peut-être pas reçu ce message puisque, selon un lieu I vérifié , la gestion incorrecte des threads avec Tkinter est imprévisible et dépend de la plate-forme.

Le problème semble être que app.start() compte comme une référence à Tk, car app contient des éléments Tk. J'ai résolu ce problème en remplaçant self.start() par un __init__ dans mainloop(). J'ai également fait en sorte que toutes les références Tk se trouvent soit dans la fonction qui appelle <=> , soit dans des fonctions appelées par la fonction qui appelle <=> (this est apparemment essentiel pour éviter l'erreur & "; appartement différent &";).

Enfin, j'ai ajouté un gestionnaire de protocole avec un rappel, car sans cela, le programme se termine avec une erreur lorsque la fenêtre Tk est fermée par l'utilisateur.

Le code révisé est le suivant:

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

Lors de l'écriture de votre propre boucle, comme dans la simulation (je suppose), vous devez appeler la fonction update qui fait ce que le mainloop fait: met à jour la fenêtre avec vos modifications, mais vous le faites dans votre boucle .

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

while 1:
   task()  

Une autre option consiste à laisser tkinter s'exécuter sur un thread séparé. Une façon de le faire est la suivante:

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

Attention, la programmation multithread est difficile et il est très facile de se tirer une balle dans le pied. Par exemple, vous devez faire attention lorsque vous modifiez les variables de membre de la classe exemple ci-dessus afin de ne pas interrompre avec la boucle d'événement de Tkinter.

Ceci est la première version de travail de ce qui sera un lecteur GPS et un présentateur de données. tkinter est une chose très fragile avec trop peu de messages d’erreur. Il ne met pas de choses et ne dit pas pourquoi une grande partie du temps. Très difficile venant d'un bon développeur de formulaire WYSIWYG. Quoi qu’il en soit, cela exécute une petite routine 10 fois par seconde et présente les informations sur un formulaire. Il a fallu du temps pour y arriver. Quand j'ai essayé une valeur de minuterie de 0, la forme n'a jamais été soulevée. Ma tête me fait mal maintenant! 10 fois ou plus par seconde me suffisent. J'espère que ça aide quelqu'un d'autre. Mike Morrow

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()
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top