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)
Related
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...
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 have a Python program using Tkinter to show a value (var peso inside capturarpeso() function) in realtime.
But the while loop in capturarPeso() doesn't work, the loop only works the first time then the script is "waiting".
If I remove the TK component, it works perfectly. I simplified the script:
import tkinter as tk
from tkinter import *
import threading
import random
def capturarPeso():
global peso
while True:
peso = random.randrange(0, 101, 2)
print (peso)
return(peso)
def capturarPesoHilo():
hilo = threading.Thread(target=capturarPeso, name=None, group=None, args=(), kwargs=None, daemon=True)
hilo.start()
hilo.join()
class ActualizarPeso(Label):
def __init__(self, parent, *args, **kwargs):
Label.__init__(self, parent, *args, **kwargs)
self.tick()
def tick(self):
self.config(text= peso)
self.after(500, self.tick)
capturarPesoHilo()
window = tk.Tk()
window.title('Capturador pesos')
window.resizable(width=False, height=False)
pesoLabel = ActualizarPeso(window, font="Arial 60", fg="red", bg="black", width=8, height= 1)
pesoLabel.grid(row=15, column=0)
window.mainloop()
Any ideas on how to continue? Thank you
The function captuarPeso() has a return statement which will exit the while loop, this is why you only get 1 number printed to the screen.
Removing the return makes it so your program is stuck in that while loop which only prints peso because when you do hilo.join() to a thread it's actually waiting for the thread to exit before continuing, and since we got rid of the return in the first step, the thread never exits and so it's again stuck in a loop. To fix this I changed your while loop to while self.peso != -999: and after calling .mainloop() you set self.peso = -999 which will tell the program: the user has exited the Tkinter interface, exit my loop.
Since you used a class to put some of your tkinter gui in, why not put it all in? Generaly most people would put the entire tkinter interface in a class, I've gone ahead and restructured the program for you but tried to leave as much as the original by itself so you can analyze it and see how it works.
import tkinter as tk
import threading
import random
import time
class ActualizarPeso:
def __init__(self):
self.window = tk.Tk()
self.window.title('Capturador pesos')
self.window.resizable(width=False, height=False)
self.pesoLabel = self.crearLabel()
self.peso = 0
self.tick()
hilo1 = self.capturarPesoHilo()
self.window.mainloop()
self.peso = -999
hilo1.join()
def crearLabel(self):
pesoLabel = tk.Label(self.window, font="Arial 60", fg="red", bg="black", width=8, height=1)
pesoLabel.grid(row=15, column=0)
return pesoLabel
def tick(self):
self.pesoLabel.config(text=self.peso)
self.pesoLabel.after(500, self.tick)
def capturarPeso(self):
while self.peso != -999:
self.peso = random.randrange(0, 101, 2)
print(self.peso)
time.sleep(1)
def capturarPesoHilo(self):
hilo = threading.Thread(target=self.capturarPeso)
hilo.start()
return hilo
ActualizarPeso()
Let me know if you need something explained, and Happy Holidays!
I am new to Tkinter and I have this problem:
I want to update my ListBox with new values in a loop so the values are added to it during the loop and not only at the end.
Here is an example code of my problem:
import time
import tkinter as tk
class mainApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
listbox = tk.Listbox(self)
button3 = tk.Button(self, text="addValues",
command=lambda : self.addValues(listbox))
button3.pack()
def addValues(self,listbox):
for i in range(10):
time.sleep(1)
listbox.insert(tk.END, str(i))
listbox.pack()
app = mainApp("test")
app.mainloop()
Here I want the Frame to update each time a value is added to the ListBox.
You can't program a tkinter application the same procedural way you're used to because everything in it must occur while its mainloop() is running. Tkinter is an event-driven framework for writing GUIs.
Here's how to do what you want using the universal widget method after() it has to schedule a callback to a method that does the insertion after a short delay (and then sets up another call to itself if the LIMIT hasn't been reached).
import time
import tkinter as tk
LIMIT = 10
DELAY = 1000 # Millisecs
class mainApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
listbox = tk.Listbox(self)
listbox.pack()
button3 = tk.Button(self, text="addValues",
command=lambda : self.addValues(listbox))
button3.pack()
def addValues(self, listbox):
self.after(DELAY, self.insertValue, listbox, 0, LIMIT)
# ADDED
def insertValue(self, listbox, value, limit):
if value < limit:
listbox.insert(tk.END, str(value))
self.after(DELAY, self.insertValue, listbox, value+1, limit)
app = mainApp("test")
app.mainloop()
I am new to Python, and especially GUI Python, and am trying to figure out how to add two functions to my button.
For example, I want user to be able to:
click on button normally and button to execute function one
shift-click on button and button to execute function number two.
I am using tkinter for GUI.
Code for button:
Any help is appreciated.
b1 = Button(window, text = "Import", width = 12, command = functionOne)
b1.grid(row = 0, column = 2)
You can do something like this - Instead of setting the button's command keyword argument, just bind different events captured by the button to different functions. Here's some more info about events and binding events.
import tkinter as tk
class Application(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title("Test")
self.geometry("128x32")
self.resizable(width=False, height=False)
self.button = tk.Button(self, text="Try Me")
self.button.pack()
self.button.bind("<Button-1>", self.normal_click)
self.button.bind("<Shift-Button-1>", self.shift_click)
def normal_click(self, event):
print("Normal Click")
def shift_click(self, event):
print("Shift Click")
def main():
application = Application()
application.mainloop()
return 0
if __name__ == "__main__":
import sys
sys.exit(main())