I am trying to build a simple auto clicker program which has start/stop buttons and a hotkey (using Tkinter and Pynput). Whenever I start the auto clicker using the start button, it works perfectly and I am able to stop it. However, when I start the auto clicker using the hotkey, I cannot stop it with the stop button as it freezes the entire program.
This is my main class for the GUI:
class App(tk.Frame):
def __init__(self, parent, *args, **kwargs):
super().__init__()
self.parent = parent
self.parent.bind("<Destroy>", self.exit)
self.clicker = Clicker(Button.left, 1)
self.clicker.start()
self.kb = Keyboard("<shift>+s", self.start_click)
self.kb.start()
btn_box = ttk.Combobox(self, values=BUTTONS, state="readonly")
btn_box.current(0)
btn_box.pack()
start = tk.Button(self, text="Start", command=self.start_click)
start.pack()
stop = tk.Button(self, text="Stop", command=self.stop_click)
stop.pack()
exit = tk.Button(self, text="Exit", command=self.exit)
exit.pack()
def start_click(self):
self.clicker.start_click()
def stop_click(self):
print("e")
self.clicker.stop_click()
def exit(self, event=None):
self.clicker.exit()
self.parent.quit()
And these are my Clicker and Keyboard classes:
class Clicker(threading.Thread):
def __init__(self, button, delay):
super().__init__()
self.button = button
self.delay = delay
self.running = False
self.prog_running = True
self.mouse = Controller()
def start_click(self):
print("start")
self.running = True
def stop_click(self):
print("stop")
self.running = False
def exit(self):
self.running = False
self.prog_running = False
def run(self):
while self.prog_running:
while self.running:
self.mouse.click(self.button)
time.sleep(self.delay)
time.sleep(0.1)
class Keyboard(threading.Thread):
def __init__(self, keybind, command):
super().__init__()
self.daemon = True
self.hk = HotKey(HotKey.parse(keybind), command)
def for_canonical(self, f):
return lambda k: f(self.l.canonical(k))
def run(self):
with Listener(on_press=self.for_canonical(self.hk.press),
on_release=self.for_canonical(self.hk.release)) as self.l:
self.l.join()
Does anyone know why it freezes when pressing the stop button after using the hotkey?
I had issues when I used self.quit() instead of self.destroy() but most probably, that's not it with your case.
Adding the shortcut using self.bind() may be a solution,
For example:
#Importing the tkinter module
from tkinter import *
#Creating a window
root = Tk()
#Creating a label
txt = Label(root, text="Opened! But I'm closing, you know...").grid(row=1, column=1)
#Defining an action and binding it(root is the window's name)
def actionn(*args):
root.destroy()
root.bind('<Shift-S>', actionn)
#Showing the window and the label
root.mainloop()
You can't bind shift like that, bind it with self.parent.bind('<Shift-S>')
I guess it freezes because the shortcut assigned doesn't work...
Related
I'm new to tkinter and I'm looking for a button that executes a function in loop as soon as the button is pressed; when released the button, function will no more be executed
I currently have a tk.Button(self, text="S+", command=sch_plus_callback) with
def sch_plus_callback():
self.ser.write(b'\x02\x56\x81\x80\x80\x80\x80\x80\x39\x35\x04')
Now I would like some sort of button that does
def sch_plus_callback():
while button_is_pressed:
self.ser.write(b'\x02\x56\x81\x80\x80\x80\x80\x80\x39\x35\x04')
Is there a way?
My full code is
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# self.insert_lengh_text_label = tk.Label(self, text="Max Packet Length")
self.sch_plus_button = tk.Button(self, text="S+", command=self.sch_plus_callback)
self.sch_plus_button.pack()
def sch_plus_callback(self):
self.ser.write(b'\x02\x56\x81\x80\x80\x80\x80\x80\x39\x35\x04')
I'm now using these methods but it's more a workaround then an actual solution
def sch_plus_stop_callback(self, event):
self.after_cancel(repeat)
def sch_plus_callback(self, *args,):
global repeat
try:
repeat = self.after(300, self.sch_plus_callback, args[0])
except:
pass
self.ser.reset_input_buffer()
self.ser.reset_output_buffer()
self.ser.write(b'\x02\x56\x81\x80\x80\x80\x80\x80\x39\x35\x04')
In the function, catch the event of releasing the left mouse button "ButtonRelease-1",using the method widget.bind(event, handler), by which you interrupt the loop.
https://python-course.eu/tkinter/events-and-binds-in-tkinter.php#:~:text=Events%20can%20be%20key%20presses,and%20methods%20to%20an%20event.&text=If%20the%20defined%20event%20occurs,called%20with%20an%20event%20object.
So that the loop does not block the tkinter, you need to run it through after(time, callback).
Tkinter loop link https://tkdocs.com/tutorial/eventloop.html.
Code as a sample:
from tkinter import *
def sch_plus_callback(event):
global repeat
repeat = root.after(500, sch_plus_callback, event)
text.insert(END, "Bye Bye.....\n")
def stop_callback(event):
root.after_cancel(repeat)
root = Tk()
text = Text(root)
text.insert(INSERT, "Hello.....\n")
text.pack()
button = Button(root, text="S+")
button.pack()
button.bind('<Button-1>', sch_plus_callback)
button.bind('<ButtonRelease-1>', stop_callback)
root.mainloop()
def sch_plus_stop_callback(self, event):
self.after_cancel(self.repeat)
def sch_plus_callback(self, event):
try:
self.repeat = self.after(300, self.sch_plus_callback, event)
I have a function that refreshes a string. It has 2 buttons, on and off. If you push on it will print 'Test' multiple times per second.
def blink():
def run():
while (switch):
print('Test')
thread = threading.Thread(target=run)
thread.start()
def on():
global switch
switch = True
blink()
def off():
global switch
switch = False
I also have a toggle function that's is one button that toggles 'True' and 'False'. It displays 'Test' when True.
def toggle():
if button1.config('text')[-1] == 'False':
button1.config(text='True')
Label(root, text='Test').place(x=30, y=0, relheight=1, relwidth=0.7)
else:
button1.config(text='False')
Label(root, text='').place(x=30, y=0, relheight=1, relwidth=0.7)
How do I combine these 2? What I want is that instead of having an on/off button, I have one toggle-able button.
I tried making a class:
class Toggle:
def blink():
def run():
while (switch):
print('Test')
Label(root, text='Test').place(x=30, y=0, relheight=1, relwidth=0.7)
else:
Label(root, text='').place(x=30, y=0, relheight=1, relwidth=0.7)
thread = threading.Thread(target=run)
thread.start()
def toggle():
if button1.config('text')[-1] == 'False':
button1.config(text='True')
global switch
switch = True
blink()
else:
button1.config(text='False')
global switch
switch = False
But I get an error:
File "C:\Users\Daniel\PycharmProjects\stockalarm test\main.py", line 29
global switch
^
SyntaxError: name 'switch' is assigned to before global declaration
I tried looking into it but I cant figure out what to do.
As mentioned in the comments, if you use a class then switch should be an attribute of the class instead of being a global variable.
Additionally, what you did is more CLI oriented and what I suggest below is to use a more tkinter oriented approach.
You want a "toggle-able" button, which is, I think, like a tk.Checkbutton with the indicatoron option set to False.
Instead of using the switch global variable, you can use a tk.BooleanVar connected to the state of the button1 checkbutton.
This depends on what you actually want to do in the run() function but in your example using threading.Thread is an overkill. You can use tkinter .after(<delay in ms>, <callback>) method instead.
I have made the Toggle class inherit from tk.Frame to put both the label and toggle button inside. Here is the code:
import tkinter as tk
class Toggle(tk.Frame):
def __init__(self, master=None, **kw):
tk.Frame.__init__(self, master, **kw)
self.label = tk.Label(self)
self.switch = tk.BooleanVar(self)
self.button1 = tk.Checkbutton(self, text='False', command=self.toggle,
variable=self.switch, indicatoron=False)
self.label.pack()
self.button1.pack()
def blink(self):
if self.switch.get(): # switch is on
print('Test')
self.after(10, self.blink) # re-execute self.blink() in 10 ms
def toggle(self):
if self.switch.get(): # switch on
self.button1.configure(text='True') # set button text to True
self.label.configure(text='Test') # set label text to Test
self.blink() # start blink function
else: # switch off
self.button1.configure(text='False')
self.label.configure(text='')
root = tk.Tk()
Toggle(root).pack()
root.mainloop()
I'm using python and tkinter to build a visualization tool that can refresh and visualize an updating object. Right now, the object can't change because the threading is not working. Any help or general knowledge would be appreciated. I'm relatively new to threading and tkinter.
example object I want to ingest
class color1:
def __init__(self, color):
self.color = color
def change_col(self, new_color):
self.color = new_color
def pass_col(self):
return(self)
my visualization code
class my_visual(threading.Thread):
def __init__(self, col1):
threading.Thread.__init__(self)
self.start()
self.col1 = col1
def viz(self):
self.root = Tk()
btn1 = Button(self.root, text = 'Refresh', command = self.refresh)
btn1.pack()
frame = Frame(self.root, width = 100, height = 100, bg = self.col1.color)
frame.pack()
btn2 = Button(self.root, text = 'Close', command = self.exit)
btn2.pack()
self.root.mainloop()
def refresh(self):
self.root.quit()
self.root.destroy()
self.col1 = self.col1.pass_col()
self.viz()
def exit(self):
self.root.quit()
self.root.destroy()
Code that works
c = color1('RED')
test = my_visual(c)
test.viz()
Code that doesn't work
In this version, the refresh works, but the threading doesn't. When the threading is working, the refresh won't pick up that the object has changed.
c.change_col('BLUE')
If you extend the threading.Thread class you need to override the run() method with your custom functionality. With no run method, the thread dies immediately. You can test whether a thread is alive with my_visual.is_alive().
The problem is that your test.viz() is an infinite loop because of self.root.mainloop(), so you cannot do anything once you called that function. The solution is to use a thread for test.viz(), and your thread for the class my_visual is no more necessary.
I added a time.sleep of 2 seconds before the refresh makes it blue, otherwise the color is blue at beginning.
Here you go :
import threading
from tkinter import *
import time
class color1:
def __init__(self, color):
self.color = color
def change_col(self, new_color):
self.color = new_color
def pass_col(self):
return(self)
class my_visual():
def __init__(self, col1):
self.col1 = col1
def viz(self):
self.root = Tk()
btn1 = Button(self.root, text = 'Refresh', command = self.refresh)
btn1.pack()
frame = Frame(self.root, width = 100, height = 100, bg = self.col1.color)
frame.pack()
btn2 = Button(self.root, text = 'Close', command = self.exit)
btn2.pack()
self.root.mainloop()
def refresh(self):
self.root.quit()
self.root.destroy()
self.col1 = self.col1.pass_col()
print("self.col1", self.col1, self.col1.color)
self.viz()
def exit(self):
self.root.quit()
self.root.destroy()
c = color1('RED')
test = my_visual(c)
t2 = threading.Thread(target = test.viz)
t2.start()
time.sleep(2)
print('Now you can change to blue when refreshing')
c.change_col('BLUE')
I am trying to make a status light on my tkinter GUI. At this point I just want it to rotate from green to red to show that the script hasn't frozen. The python traceback errors that I get all point to __libraries that I don't understand. I feel like this must be a namespace problem, but I'm ripping out my hair trying to put my finger on it.
The eventCheck() method worked great at creating a label that toggled between 0 and 1 before I created the canvas object and tried passing c into it. There is so little information out there on what I am trying to do, maybe there is a better way?
Here is a condensed version of my script:
import tkinter as tk
from tkinter import Canvas
import time
import threading
class myGUI(tk.Frame):
def __init__(self, master, event):
self.master = master
self.event = event
super().__init__(master)
self.label = tk.Label(self, text="")
self.label.grid()
self.after(1, self.eventCheck)
c = tk.Canvas(self, bg='white', width=80, height=80)
c.grid()
self.eventCheck(c)
def redCircle(self, c):
c.create_oval(20, 20, 80, 80, width=0, fill='red')
print("redCircle Called")
def greenCircle(self,c):
c.create_oval(20, 20, 80, 80, width=0, fill='green')
print("greenCircle Called")
def eventCheck(self, c):
self.label['text'] = self.event.is_set()
if self.label['text'] == 0:
self.redCircle(c)
else:
self.greenCircle(c)
self.after(2000, self.eventCheck(c))
def timingLoop(event):
while True:
event.set()
time.sleep(2)
event.clear()
time.sleep(2)
def main():
root = tk.Tk()
root.title("myFirst GUI")
event = threading.Event()
t=threading.Thread(target=timingLoop, args=(event,))
t.daemon = True
t.start()
app = myGUI(root, event)
root.mainloop()
if __name__=="__main__":
main()
I found two major issues with your code. First, this isn't doing what you think it should:
def eventCheck(self, c):
# ...
self.after(2000, self.eventCheck(c))
Because you passed the result of a call to self.eventCheck(c) to after() instead of the method self.eventCheck, this is an infinite recursion that takes place immediately.
The second issue is that if you comment out all your timing and event stuff, your interface never actually comes up, so there's never anything to see. I've condensed (simplified) your example script even further into one that basically works:
import tkinter as tk
import threading
import time
class myGUI:
def __init__(self, master, event):
self.master = master
self.event = event
self.label = tk.Label(master, text="")
self.label.pack()
self.canvas = tk.Canvas(master, bg='white', width=80, height=80)
self.canvas.pack()
self.eventCheck()
def redCircle(self):
self.canvas.create_oval(20, 20, 80, 80, width=0, fill='red')
print("redCircle Called")
def greenCircle(self):
self.canvas.create_oval(20, 20, 80, 80, width=0, fill='green')
print("greenCircle Called")
def eventCheck(self):
flag = self.event.is_set()
self.label['text'] = flag
if flag:
self.greenCircle()
else:
self.redCircle()
self.master.after(2000, self.eventCheck)
def timingLoop(event):
while True:
event.set()
time.sleep(2)
event.clear()
time.sleep(2)
def main():
root = tk.Tk()
root.title("myFirst GUI")
event = threading.Event()
t = threading.Thread(target=timingLoop, args=(event,))
t.daemon = True
t.start()
app = myGUI(root, event)
root.mainloop()
if __name__ == "__main__":
main()
Now you should be able to add back your Frame superclass. Make sure to add the frame that is myGUI to your root object.
I would like to create a timer that starts at 0 when a user presses a button and stop at whatever time it has displayed when the user presses the button again. So far, all of the questions user after that looks at the current time and updates in seconds from whatever time it is like so:
def timer(self):
now = time.strftime("%H:%M:%S")
self.label.configure(text=now)
self.after(1000, self.timer)
But I would like to start at zero, display the minutes and seconds. Is there anyway to achieve this?
Here's a simple stopwatch GUI. There's some room for improvement. ;)
import tkinter as tk
from time import time
class Stopwatch:
def __init__(self):
root = tk.Tk()
root.title('Stopwatch')
self.display = tk.Label(root, text='00:00', width=20)
self.display.pack()
self.button = tk.Button(root, text='Start', command=self.toggle)
self.button.pack()
self.paused = True
root.mainloop()
def toggle(self):
if self.paused:
self.paused = False
self.button.config(text='Stop')
self.oldtime = time()
self.run_timer()
else:
self.paused = True
self.oldtime = time()
self.button.config(text='Start')
def run_timer(self):
if self.paused:
return
delta = int(time() - self.oldtime)
timestr = '{:02}:{:02}'.format(*divmod(delta, 60))
self.display.config(text=timestr)
self.display.after(1000, self.run_timer)
Stopwatch()
The toggle method toggles the stopwatch on or off. The run_timer method updates the display Label with the time since the timer started, in minutes & seconds. For more accuracy, reduce the .after delay to say, 500, or 100. That will do unnecessary (and invisible) updates to the Label, but the displayed time will be a little more accurate, and the GUI will feel a little more responsive.
import tkinter as tk
import time
class GUI:
def __init__(self, master):
self.root = master
self.parent = tk.Frame(self.root)
self.parent.pack(fill = tk.BOTH)
self.parent.config(bg = "black")
self.now = time.time()
self.buttonVar = tk.IntVar()
self.buttonCycle = False
self.buttonVar.set(0)
self.button = tk.Button(root,
textvariable = self.buttonVar,
command = self.updateButton)
self.button.pack(fill = tk.BOTH)
self.button_cycle()
def updateButton(self):
if self.buttonCycle:
self.buttonCycle = False
self.now = time.time()
elif not self.buttonCycle:
self.buttonCycle = True
def button_cycle(self):
if self.buttonCycle:
now = time.time()
timeDifference = int(now - self.now)
self.buttonVar.set(timeDifference)
self.root.after(1000, self.button_cycle)
root = tk.Tk()
myApp = GUI(root)
root.mainloop()