Tkinter'in olay döngüsünün yanında kendi kodunuzu nasıl çalıştırırsınız?


119

Küçük kardeşim programlamaya yeni başlıyor ve Bilim Fuarı projesi için gökyüzünde bir kuş sürüsünün simülasyonunu yapıyor. Kodunun çoğunu yazdı ve güzel çalışıyor, ancak kuşların her an hareket etmesi gerekiyor .

Ancak Tkinter, kendi olay döngüsü için zaman ayırır ve böylece kodu çalışmaz. Yapmak root.mainloop()çalışır, çalışır ve çalışmaya devam eder ve çalıştırdığı tek şey olay işleyicileridir.

Kodunun ana döngü ile birlikte çalışmasını sağlamanın bir yolu var mı (çoklu okuma olmadan, kafa karıştırıcı ve bu basit tutulmalıdır) ve eğer öyleyse, nedir?

Şu anda, çirkin bir hack ile geldi, move()işlevini ona bağladı <b1-motion>, böylece düğmeyi basılı tutup fareyi kıpırdattığı sürece işe yarıyor. Ama daha iyi bir yol olmalı.

Yanıtlar:


141

Nesne afterüzerindeki yöntemi kullanın 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()

afterYöntemin beyanı ve dokümantasyonu :

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
Zaman aşımını 0 olarak belirtirseniz, görev tamamlandıktan hemen sonra kendisini olay döngüsüne geri koyacaktır. bu, kodunuzu olabildiğince sık çalıştırırken diğer olayları engellemeyecektir.
Nathan

Saçlarımı saatlerce çekip açtıktan sonra, [X] düğmesine tıklandığında birlikte düzgün ve temiz bir şekilde kapanmasını sağlamaya çalıştıktan sonra, win32gui.FindWindow (Yok, 'pencere başlığı') hile yaptı! Ben tam bir
çaylağım

Bu en iyi seçenek değil; bu durumda çalışmasına rağmen, çoğu komut dosyası için iyi değildir (yalnızca 2 saniyede bir çalışır) ve zaman aşımını @Nathan tarafından gönderilen öneriye göre 0 olarak ayarlamak, çünkü yalnızca tkinter meşgul olmadığında çalışır ( bazı karmaşık programlarda sorunlara neden olabilir). threadingModüle bağlı kalmak en iyisi .
Anonim

59

Bjorn tarafından gönderildi çözüm bir de sonuçlar "RuntimeError: Farklı daire den Tcl arama" mesajının bilgisayarımda (RedHat Enterprise 5, piton 2.6.1). Bjorn bu mesajı almamış olabilir, çünkü kontrol ettiğim bir yere göre , Tkinter ile iş parçacığının yanlış kullanılması öngörülemez ve platforma bağlı.

app.start()Uygulama Tk öğeleri içerdiğinden, sorun Tk'ye bir referans olarak sayılıyor gibi görünüyor . Bunu app.start()bir self.start()iç ile değiştirerek düzelttim __init__. Ayrıca tüm Tk referanslar içeride ya böylece yaptım çağrıları o işlevinmainloop() veya içerde tarafından çağrılan fonksiyonlar çağrıları o fonksiyonu mainloop()(bu "farklı daire" hatasını önlemek için görünüşte kritiktir).

Son olarak, geri arama içeren bir protokol işleyicisi ekledim, çünkü bu olmadan program, Tk penceresi kullanıcı tarafından kapatıldığında bir hatayla çıkar.

Revize edilmiş kod aşağıdaki gibidir:

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

runYönteme argümanları nasıl iletirsiniz ? Nasıl yapacağımı
çözemiyorum

5
tipik olarak argümanları __init__(..)selfrun(..)
Andre Holzner

1
Kök hiç görünmez ve şu uyarıyı verir: `UYARI: NSWindow sürükleme bölgeleri yalnızca Ana İş Parçacığında geçersiz kılınmalıdır! Bu gelecekte bir istisna yaratacak ``
Bob Bobster

1
Bu yorum çok daha fazla tanınmayı hak ediyor. İnanılmaz.
Daniel Reyhanian

Bu bir hayat kurtarıcıdır. GUI dışındaki kod, gui'den çıktıktan sonra python betiğinden çıkamayacaksanız, tkinter iş parçacığının canlı olup olmadığını kontrol ediyor olmalıdır. Gibi bir şeywhile app.is_alive(): etc
m3nda

21

Simülasyonda olduğu gibi kendi döngünüzü yazarken (sanırım), updateyaptığı şeyi yapan işlevi çağırmanız gerekir mainloop: pencereyi değişikliklerinizle günceller, ancak bunu kendi döngünüzde yaparsınız.

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

while 1:
   task()  

10
Sen olmak zorunda çok programlama bu tür dikkatli. Herhangi bir olay taskçağrılmasına neden olursa , iç içe geçmiş olay döngüleri ile sonuçlanırsınız ve bu kötüdür. Olay döngülerinin nasıl çalıştığını tam olarak anlamadığınız sürece, ne updatepahasına olursa olsun aramaktan kaçınmalısınız .
Bryan Oakley

Bu tekniği bir kez kullandım - iyi çalışıyor ancak nasıl yaptığınıza bağlı olarak, kullanıcı arayüzünde bazı şaşırtıcı şeyler olabilir.
jldupont

@Bryan Oakley güncelleme bir döngü mü? Ve bu nasıl sorunlu olur?
Green05

6

Diğer bir seçenek, tkinter'in ayrı bir iş parçacığı üzerinde çalışmasına izin vermektir. Bunu yapmanın bir yolu şudur:

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

Yine de dikkatli olun, çok iş parçacıklı programlama zordur ve kendinizi ayağınızın dibine çekmek gerçekten çok kolaydır. Örneğin, yukarıdaki örnek sınıfın üye değişkenlerini değiştirirken dikkatli olmalısınız, böylece Tkinter'in olay döngüsünü kesintiye uğratmazsınız.


3
Bunun işe yarayacağından emin değilim. Benzer bir şey denedim ve "RuntimeError: ana iş parçacığı ana döngüde değil" mesajını alıyorum.
jldupont

5
jldupont: "RuntimeError: Tcl'yi farklı bir uygulamadan çağırıyorum" (muhtemelen farklı bir sürümde aynı hata) var. Düzeltme Tk'yi __init __ () 'de değil run ()' da başlatmaktı. Bu, Tk'yi mainloop () içinde çağırdığınız gibi aynı iş parçacığında başlattığınız anlamına gelir.
mgiuca

2

Bu, bir GPS okuyucu ve veri sunucusu olacak ilk çalışan sürümüdür. tkinter, çok az hata mesajı olan çok kırılgan bir şeydir. Bir şeyler koymaz ve neden çoğu zaman söylemez. İyi bir WYSIWYG form geliştiricisinden gelmesi çok zor. Her neyse, bu saniyede 10 kez küçük bir rutin çalıştırır ve bilgileri bir formda sunar. Bunun olması biraz zaman aldı. 0 zamanlayıcı değerini denediğimde, form hiç gelmedi. Kafam şimdi ağrıyor! Saniyede 10 veya daha fazla kez benim için yeterli. Umarım başka birine yardımcı olur. 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()
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.