0

I would like to make a script or a small program which gives me my cps (clicks per second) but I find a small problem to make a timer of 10 seconds and at the same time click on the button left click. I tried the module threading but it does not work with tkinter I have try everything (make the timer inside the function execute several functions to increment the timer in a variable ex ...) but I never manage to do it at the same time. My program should look like what can makes this site: www.mcrpg.com/kohi-click-test

Ps: to test my problem click on starting not test Start.

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()
1
  • You can't use any tkinter objects from a background thread. There are solutions, but they're all a pain. If you're willing to rethink your program so it doesn't need threads, that will require deeper changes, but they'll be easier to understand and manage. Which answer do you want? Commented Jul 10, 2018 at 22:06

1 Answer 1

4

The first problem with your code is that, while tkinter is thread-safe, it's illegal to touch any tkinter widgets from anything but the main thread. Which means your threads, which are trying to create and modify and click those widgets, are illegal. What actually happens when you try depends on your platform, your versions of Python and Tcl/Tk and how they're configured, and so on, but it's never good—it may hang the GUI thread, crash the program, give you garbage strings or, worst of all, work about 95% of the time but occasionally mysteriously do something wrong that's impossible to debug.

There are ways around that, as discussed in this adjunct to the Effbot Tkinter book, including a library called mtTkinter that magically wraps everything up for you except that it hasn't been updated since Python 2.4 or so (although there are unmaintained 3.x forks if you want to pick one up and hammer it into shape.


The second problem with your code is that your main run function blocks the main thread until both threads are done. If you're blocking the main thread, it isn't running your main event loop, which means your app is not responsive. Any GUI changes you make don't show up. Any mouse clicks and drags don't get handled. Eventually, the OS puts up a beachball or an hourglass or whatever.


But your code doesn't really need threads in the first place.

Let's look at your timer function:

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)

The only reason it needs to run on a background thread is so that it can loop for length seconds, sleeping for a second at a time. But we can instead turn it into length callbacks, each of which schedules the next callback and quits:

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)

Now, your run program doesn't have to spawn it on a background thread, it can just call it directly:

self.timer()

Your other function is even simpler:

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

It doesn't need to be on a thread at all; you can just run it directly as-is.

But how do you do the equivalent of that join? Well, you haven't shown us where you're calling run, so I don't know what's supposed to happen after the join. But whatever it is that's supposed to happen there, just pull it out into a separate function, and you can schedule that to run after the two after-using inverted loops are done.

In this example, since one function returns almost instantly, while the other reschedules itself for length seconds, we can probably just call that after the timer. So:

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()
Sign up to request clarification or add additional context in comments.

1 Comment

awesome answer! threading can be a pain to implement; it's always better when you can avoid it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.