3

I'm trying to do a GUI in python to control my robotic car. My question is how I do a function that determine a hold down button. I want to move the car when the button is pressed and held down and stop the car when the button is released.

from Tkinter import * 

hold_down = False 
root = Tk()

def button_hold(event):
      hold_down=true
      while hold_down== True: 
               print('test statement')
               hold_down = root.bind('<ButtonRelease-1>',stop_motor)

def stop_motor(event):
       hold_down= False
       print('button released')

button = Button(root, text ="forward")
button.pack(side=LEFT)
root.bind('<Button-1>',button_forward)
root.mainloop()

I'm trying to simulate what I found in this answer

I try to do it in a while loop with a boolean. When the user presses the button the boolean changes to True and code enters the while loop. When user releases the button the boolean changes to False and code exits from loop but in this code the boolean stay always true no matter if I released the button or not.

Edit: I want a function to be called until a condition occurs.The function to be called is hold_down() and the condition to check is the button is released.

Update: I found a way to make it work.

martineau
  • 119,623
  • 25
  • 170
  • 301
kiri_23
  • 41
  • 1
  • 1
  • 4

5 Answers5

11

Set a flag when the button is pressed, unset the flag when the button is released. There's no need for a loop since you're already running a loop (mainloop)

from Tkinter import * 
running = False
root = Tk()
def start_motor(event):
    global running
    running = True
    print("starting motor...")

def stop_motor(event):
    global running
    print("stopping motor...")
    running = False

button = Button(root, text ="forward")
button.pack(side=LEFT)
button.bind('<ButtonPress-1>',start_motor)
button.bind('<ButtonRelease-1>',stop_motor)
root.mainloop()

Assuming that you actually want to do something while the key is pressed, you can set up an animation loop using after. For example, to call a print statement once a second while the button is pressed you can add a function that does the print statement and then arranges for itself to be called one second later. The stop button merely needs to cancel any pending job.

Here's an example. The main difference to the original code is the addition of a move function. I also added a second button to show how the same function can be used to go forward or backward.

from Tkinter import * 
running = False
root = Tk()
jobid = None

def start_motor(direction):
    print("starting motor...(%s)" % direction)
    move(direction)

def stop_motor():
    global jobid
    root.after_cancel(jobid)
    print("stopping motor...")

def move(direction):
    global jobid
    print("Moving (%s)" % direction)
    jobid = root.after(1000, move, direction)

for direction in ("forward", "backward"):
    button = Button(root, text=direction)
    button.pack(side=LEFT)
    button.bind('<ButtonPress-1>', lambda event, direction=direction: start_motor(direction))
    button.bind('<ButtonRelease-1>', lambda event: stop_motor())

root.mainloop()
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • Shouldn't you bind the button and not the main window? – Steven Summers Dec 30 '15 at 04:01
  • @StevenSummers: Maybe. It's not clear what the OP wants. The original code binds to the master window. It depends on if they want the mouse button to click anywhere in the GUI, or only when over a button widget. Now that I re-read the question, perhaps you're right. I'll change my answer. – Bryan Oakley Dec 30 '15 at 12:12
  • I want when the user press the button (not anywhere in the GUI) and repeat the function still the user released the button. – kiri_23 Dec 30 '15 at 13:16
2

You might want to try the repeatinterval option. The way it works is a button will continually fire as long as the user holds it down. The repeatinterval parameter essentially lets the program know how often it should fire the button if so. Here is a link to the explanation:

http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/button.html

Search in-page for "repeatinterval".

Another name for this parameter is repeatdelay.

Joseph Farah
  • 2,463
  • 2
  • 25
  • 36
1

Building on Bryan Oakley's answer of using flags to simulate a press and hold button. The problem is that you can't have any while loops in your tkinter application to say while running move car forward.

Which is why I suggest using threads. This way you can have a while loop running in the background checking if the car should be moving foward.

from threading import Thread
from Tkinter import *    

running = False
root = Tk()

def start_motor(event):
    global running
    print("starting motor...")
    running = True

def stop_motor(event):
    global running
    running = False
    print("stopping motor...")

def move_forward():
    while True: # Thread will run infinitely in the background
        if running:
            print("Car is moving forward...\n")

button = Button(root, text ="forward")
button.pack(side=LEFT)
button.bind('<ButtonPress-1>',start_motor)
button.bind('<ButtonRelease-1>',stop_motor)

# Create and start the new thread
t = Thread(target = move_forward, args = ())
t.start()

root.mainloop()
Steven Summers
  • 5,079
  • 2
  • 20
  • 31
  • The background thread always takes 100% of CPU time — not good. – Olexa Sep 17 '17 at 22:28
  • 1
    To avoid consuming CPU time by background thread when not necessary, create and start the thread in `start_motor` after `running = True`, instead of creating and running it at startup, and change `move_forward` to run `while running` instead of `while True`. – Olexa Sep 17 '17 at 22:43
0

Try this...

from Tkinter import *  
root = Tk()
global hold_down

def button_hold(event):
    hold_down = True
    while hold_down: 
        print('test statement')

def stop_motor(event):
    hold_down = False
    print('button released')

button = Button(root, text ="forward")
button.pack(side=LEFT)
root.bind('<Button-1>',button_hold)
root.bind('<ButtonRelease-1>',stop_motor)
root.mainloop()
Danny
  • 384
  • 1
  • 5
  • 16
  • its get on a infinite loop. Its like it always stay true (stay in the loops) and no matter what if a released the button the program not jump to another instruction stay always in the loop. I try with the root.bind for ButtonReleased inside the while loop and did not work either . – kiri_23 Dec 30 '15 at 04:38
  • This cannot work. You prevent the event loop from ever being able to process the button release. – Bryan Oakley Dec 30 '15 at 12:20
-1

@ Danny Try the following code:

def stop_motor(event): print('button released') return False

This answer run print 'test statement' one time. The while loop run one time when the button is pressed.

@ Bryan Oakley Set a flag when the button is pressed, unset the flag when the button is released. There's no need for a loop since you're already running a loop (mainloop)

from Tkinter import * 
running = False

root = Tk()
def start_motor(event):
    global running
    running = True
    print("starting motor...")

def stop_motor(event):
    global running
    print("stopping motor...")
    running = False

button = Button(root, text ="forward")
button.pack(side=LEFT)
root.bind('<ButtonPress-1>',start_motor)
root.bind('<ButtonRelease-1>',stop_motor)
root.mainloop()

This answer above stays in a infinite loop when the button is pressed.

@ Joseph FarahYou might want to try the repeatinterval option. The way it works is a button will continually fire as long as the user holds it down. The repeatinterval parameter essentially lets the program know how often it should fire the button if so. Here is a link to the explanation:

http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/button.html

Search in-page for "repeatinterval".

Another name for this parameter is repeatdelay.

I set the repeat interval in the parameter option for the button widget but it doesn't repeat the command.

Thanks for all the answer . Still looking to solve this problem.

kiri_23
  • 41
  • 1
  • 1
  • 4
  • Can I see the code for your button_forward function? – Danny Dec 30 '15 at 03:38
  • The code for the button_forward function is the same as the code for the button_hold. I change their names but does exactly the same . – kiri_23 Dec 30 '15 at 03:58
  • What i want to do is this. I want to make that when the user(me) hold down the button 'foward' the program keep executing the code that make the car run foward and when I released the button stop the car - stop the running the code. – kiri_23 Dec 30 '15 at 03:59
  • @user38029: What does this mean: _"This answer above stays in a infinite loop when the button is pressed."+ ? My solution doesn't enter a loop. Plus, it does exactly what you asked -- it prints a message when you press the button, and prints another when you release. If you need something more, please clarify your question. Do you want some function to be called repeatedly while the key is pressed? – Bryan Oakley Dec 30 '15 at 12:24
  • @BryanOakley: Yes , I want a Function to be called repeatedly while the key is pressed. Because I want to simulate while the key is pressed continue running the car and when the key is released stop the car. sorry for the misunderstanding , it is clear now? I want a function to be called until a condition occurs . the function to be called is run_mottor() and the condition to check if the button is released. is there something you dont understand tell me . – kiri_23 Dec 30 '15 at 13:03