【问题标题】:Thread / Tkinter Python线程/Tkinter Python
【发布时间】:2018-07-10 22:00:49
【问题描述】:

我想制作一个脚本或一个小程序来提供我的 cps(每秒点击次数),但我发现一个小问题是制作一个 10 秒的计时器,同时单击左键单击按钮。我尝试了模块线程,但它不适用于 tkinter 我已经尝试了所有方法(让函数内的计时器执行几个函数以增加变量 ex ...中的计时器)但我从来没有设法同时做到这一点。我的程序应该看起来像什么可以使这个网站:www.mcrpg.com/kohi-click-test

Ps:要测试我的问题,请单击开始而不是测试开始。

import time
import os
from tkinter import *
from tkinter.constants import *
from threading import Thread

class Interface(Frame):

def run(self):
    thread1 = Thread(target = self.Démarrer )
    thread2 = Thread(target = self.timer)
    thread1.start()
    thread2.start()

    thread1.join()
    thread2.join()
    fenetre.update()

def timer(self):
    length = 10
    for i in range(1,(length+1)):
        print(i)
        self.Label2["text"] = "Le temps est {}".format(i)
        fenetre.update()
        time.sleep(1)

def MaApp(self):

    self.nb_clic += 1      
    self.cps = (self.nb_clic / 10)
    self.Label["text"]="Le Nombre de clic est de {}".format(self.nb_clic)
    self.Label1["text"] = "Votre cps est de {}".format(self.cps)
    fenetre.update()

def Démarrer(self):

    self.bouton_cliquer["text"]= "Clic Gauche"
    self.bouton_cliquer["command"] = self.MaApp
    fenetre.update()



def __init__(self, fenetre, **kwargs):

    Frame.__init__(self, fenetre, **kwargs)
    self.pack(fill=BOTH) 

    fenetre.geometry("400x200+300+300")

    fenetre.title("ClickTest")

    #Variable


    self.nb_clic = 0

    self.cps = (self.nb_clic / 10)

    self.temps = 0

    # Création de nos widgets



    self.Label = Label(self , text="Le Nombre de clic est de {}".format(self.nb_clic))
    self.Label.pack()

    self.Label1 = Label(self, text="Votre cps est de {}".format(self.cps))
    self.Label1.pack()


    self.Label2 = Label(self , text=("Le temps est {}").format(self.temps))
    self.Label2.pack()


    self.bouton_quitter = Button(self, text="Quitter",
                                 command=self.quit
                                 )
    self.bouton_quitter.pack(side="left")

    self.bouton_cliquer = Button(self, text="Démarrer" ,
                             command=self.run
                             )
    self.bouton_cliquer.pack(side="right")
    fenetre.update()


    # Bouton de Test
    self.bouton_cliquer2 = Button(self, text="Test Démarrer",
                                  command = self.Démarrer
                                  )
    self.bouton_cliquer2.pack()

    self.bouton_cliquer3 = Button(self, text="Test MaApp",
                                  command = self.MaApp
                                  )
    self.bouton_cliquer3.pack()

    self.bouton_cliquer4 = Button(self, text="Test Timer",
                                  command = self.timer
                                  )
    self.bouton_cliquer4.pack()
if __name__ == '__main__':
    fenetre = Tk()
    interface = Interface(fenetre)
    interface.mainloop()
    interface.destroy()

【问题讨论】:

  • 您不能使用后台线程中的任何 tkinter 对象。有解决方案,但它们都很痛苦。如果您愿意重新考虑您的程序以使其不需要线程,那将需要进行更深入的更改,但它们会更易于理解和管理。你想要哪个答案?

标签: python multithreading tkinter


【解决方案1】:

您的代码的第一个问题是,虽然 tkinter 是线程安全的,但从主线程以外的任何地方触摸任何 tkinter 小部件都是非法的。这意味着您尝试创建、修改和单击这些小部件的线程是非法的。当您尝试时实际发生的情况取决于您的平台、您的 Python 和 Tcl/Tk 版本以及它们的配置方式等等,但这从来都不是好事——它可能会挂起 GUI 线程、使程序崩溃、给您垃圾字符串或者,最糟糕的是,大约 95% 的时间都在工作,但偶尔会莫名其妙地做一些无法调试的错误。

正如this adjunct to the Effbot Tkinter book 中所讨论的那样,有一些方法可以解决这个问题,包括一个名为 mtTkinter 的库,它可以神奇地为您包装一切,除了它自 Python 2.4 左右以来没有更新(尽管有 unmaintained 3.x forks if you want to pick one up and hammer it into shape


您的代码的第二个问题是您的主 run 函数会阻塞主线程,直到两个线程都完成。如果你阻塞了主线程,它就没有运行你的主事件循环,这意味着你的应用程序没有响应。您所做的任何 GUI 更改都不会显示。任何鼠标点击和拖动都不会得到处理。最终,操作系统会生成一个沙滩球或沙漏之类的东西。


但您的代码一开始并不真正需要线程。

让我们看看你的timer 函数:

def timer(self):
    length = 10
    for i in range(1,(length+1)):
        print(i)
        self.Label2["text"] = "Le temps est {}".format(i)
        fenetre.update()
        time.sleep(1)

它需要在后台线程上运行的唯一原因是它可以循环length 秒,一次休眠一秒。但我们可以改为将其转换为length 回调,每个回调都会安排下一个回调并退出:

def timer(self, length=10, i=1):
    print(i)
    self.Label2["text"] = "Le temps est {}".format(i)
    fenetre.update()
    if i < length:
        tkinter.after(timer, length, i+1)

现在,您的run 程序不必在后台线程中生成它,它可以直接调用它:

self.timer()

你的其他功能更简单:

def Démarrer(self):
    self.bouton_cliquer["text"]= "Clic Gauche"
    self.bouton_cliquer["command"] = self.MaApp
    fenetre.update()

它根本不需要在线程上;你可以直接运行它。

但是你怎么做相当于join 的呢?好吧,你还没有告诉我们你在哪里打电话给run,所以我不知道join 之后会发生什么。但无论那里应该发生什么,只需将其拉出到一个单独的函数中,您就可以安排它在两个 after-using 反向循环完成后运行。

在这个例子中,由于一个函数几乎立即返回,而另一个函数将自身重新调度为length 秒,我们可能只在timer 之后调用它。所以:

def run(self):
    self.timer()
    self.Démarrer()

def timer(self, length=10, i=1):
    print(i)
    self.Label2["text"] = "Le temps est {}".format(i)
    fenetre.update()
    if i < length:
        tkinter.after(timer, length, i+1)
    else:
        self.do_whatever_you_wanted_after_the_join()

【讨论】:

  • 很棒的答案!线程可能很难实现;当你能避免它时,它总是更好。
猜你喜欢
  • 1970-01-01
  • 2018-06-26
  • 1970-01-01
  • 1970-01-01
  • 2013-09-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多