Paul Allen
Paul Allen

Reputation: 31

Trying to keep a function constantly running while tkinter button is held

I currently have a button in tkinter to run a function when the button is released. I need the button to constantly add toa number at a certain rate the entire time the button is being held.

global var
var=1
def start_add(event,var):
    global running
    running = True
    var=var+1
    print(var)
    return var

def stop_add(event):
    global running
    print("Released")
    running = False
button = Button(window, text ="Hold")
button.grid(row=5,column=0)
button.bind('<ButtonPress-1>',start_add)
button.bind('<ButtonRelease-1>',stop_add)

i dont necessarily need any function to run when the button is released, just while the button is being held if this helps. Any help is much appreciated.

Upvotes: 3

Views: 1657

Answers (2)

Novel
Novel

Reputation: 13729

There is nothing builtin that can do this, but it would be easy to make your own Button that can. You are on the right track too, only thing you are missing is that you need to use after to make the loop and after_cancel to stop the loop:

try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk

class PaulButton(tk.Button):
    """
    a new kind of Button that calls the command repeatedly while the Button is held
    :command: the function to run
    :timeout: the number of milliseconds between :command: calls
      if timeout is not supplied, this Button runs the function once on the DOWN click,
      unlike a normal Button, which runs on release
    """
    def __init__(self, master=None, **kwargs):
        self.command = kwargs.pop('command', None)
        self.timeout = kwargs.pop('timeout', None)
        tk.Button.__init__(self, master, **kwargs)
        self.bind('<ButtonPress-1>', self.start)
        self.bind('<ButtonRelease-1>', self.stop)
        self.timer = ''

    def start(self, event=None):
        if self.command is not None:
            self.command()
            if self.timeout is not None:
                self.timer = self.after(self.timeout, self.start)

    def stop(self, event=None):
        self.after_cancel(self.timer)

#demo code:
var=0
def func():
    global var
    var=var+1
    print(var)

root = tk.Tk()
btn = PaulButton(root, command=func, timeout=100, text="Click and hold to repeat!")
btn.pack(fill=tk.X)
btn = PaulButton(root, command=func, text="Click to run once!")
btn.pack(fill=tk.X)
btn = tk.Button(root, command=func, text="Normal Button.")
btn.pack(fill=tk.X)

root.mainloop()

As @rioV8 mentioned, the after() call is not extremely accurate. If you set the timeout to 100 milliseconds, you can usually expect anywhere from 100 - 103 milliseconds between calls. These errors will stack up the longer the button is held. If you are trying to time exactly how long the button has been held down you will need a different approach.

Upvotes: 3

Miriam
Miriam

Reputation: 2711

@Novel's answer should work I think, but here is something more along the lines of what you were trying, that doesn't require a whole new class:

from tkinter import *
INTERVAL=5 #miliseconds between runs
var=1
def run():
    global running, var
    if running:
        var+=1
        print(var)
        window.after(INTERVAL, run)

def start_add(event):
    global running
    running = True
    run()

def stop_add(event):
    global running, var
    print("Released")
    running = False

window=Tk()
button = Button(window, text ="Hold")
button.grid(row=5,column=0)
button.bind('<ButtonPress-1>',start_add)
button.bind('<ButtonRelease-1>',stop_add)
mainloop()

Upvotes: 0

Related Questions